JVM内存管理

文章目录

  • 1、运行时数据区域
    • 1.1 程序计数器(线程私有)
    • 1.2 JAVA虚拟机栈(线程私有)
    • 1.3 本地方法栈
    • 1.4 Java堆(线程共享)
    • 1.5 方法区(线程共享)
    • 1.6 直接内存(非运行时数据区域)
  • 2、Java对象组成
    • 2.1 对象头(Header)
    • 2.2 实例数据(Instance Data)
    • 2.3 对齐填充(Padding)
  • 3、对象访问方式
    • 3.1 句柄访问方式
    • 3.2 直接指针访问方式
  • 4、GC判断对象回收算法
    • 4.1 引用计数算法(JVM未使用)
    • 4.2 可达性分析算法(JVM使用)
  • 5、对象引用种类
  • 6、对象回收过程(两次标记)
  • 7、方法区回收
  • 8、GC回收算法(思想)
    • 8.1 标记-清除算法
    • 8.2 复制算法
    • 8.3 标记-整理算法
    • 8.4 分代收集算法(JVM常用)

1、运行时数据区域

  • 程序计数器
  • JAVA虚拟机栈
  • 本地方法栈
  • 方法区

在这里插入图片描述

1.1 程序计数器(线程私有)

简述:

  • 作用:当前线程所执行的字节码的行号指示器,字节码解释器通过改变计数器值选取下一条需要执行的字节码指令。
  • 线程私有:Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间方式实现,在任意时刻,一个处理器只会执行一条线程中的指令。为了线程切换后恢复到正确的执行位置,每条先后才能都需要有一个独立的程序计数器,线程之间的计数器互不影响,独立存储。

说明:

  • 线程正在执行java方法,此时计数器记录的是虚拟机字节码地址;
  • 线程正在执行native方法,此时计数器记录值为空,此内存为JVM规范中没有规定任何OutOfMemoryError情况的区域

1.2 JAVA虚拟机栈(线程私有)

简述:
          虚拟机栈描述的是java内存模型,每个方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法被调用到执行结束的过程,对应着一个栈帧从入栈到出栈的过程。

  • 局部变量表:存放了编译器可知的八种基本数据类型(boolean\byte\char\short\int\float\long\double)和对象引用类型。
  • 局部变量表中long和double占用2个局部变量空间(slot),其它数据类型占用1个,局部变量表所需内存空间在编译期间完成分配,在运行期间该方法局部变量表的大小不会改变。

说明:(异常状况)

  • StackOverflowError:栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
  • OutOfMemoryError:若虚拟机栈可扩展,当无法扩展无法申请到足够内存将抛出OutOfMemoryError异常

1.3 本地方法栈

简述:
          本地方法栈为JVM使用Native服务,虚拟机栈则为Java方法(字节码)服务。

说明:(异常状况)

  • StackOverflowError(暂不赘述)
  • OutOfMemoryError(暂不赘述)

1.4 Java堆(线程共享)

简述:

  • 在虚拟机启动时创建,存放对象实例。
  • 所有对象实例及数组都在堆上分配,但随着JIT编译器、逃逸分析技术、栈上分配、标量替换优化技术的发展,导致并非所有对象都在堆上分配。

Java堆是垃圾收集器管理的主要区域,即GC堆,从内存回收看,现在收集器基本都是分代收集算法。
Java堆分为:新生代老年代。新生代其中包含Eden空间、From Survivor空间、To Survivor空间等。

说明:(异常状况)

  • Java堆可以是物理上不连续的内存空间,只要逻辑连续即可。
  • OutOfMemoryError:若堆中没有内存完成实例分配且堆无法再扩展,则会抛出OutOfMemoryError异常

1.5 方法区(线程共享)

简述:
           存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
说明:

  • 方法区不等价于永久代,GC分代收集扩展到方法区,或使用永久代来实现方法区而已。其他虚拟机是不存在永久代概念;
  • 垃圾收集行为很少出现在方法区,该区域内存回收主要是对常量池和对类型的卸载,基本回收很少,效果不大理想。
  • OutOfMemoryError:当方法区无法满足内存分配需求时,则将抛出OutOfMemoryError异常

运行时常量池:

  • 是方法区的一部分,Class文件中除了类的版本、字段、方法、接口等描述信息外,还有常量池,用于存储编译期生成的各种字面量和符号引用,是类加载后存到方法区的运行时常量池。
  • 运行时常量池除了存储Class文件中描述的符号引用外,还将翻译出来的直接引用也存储在运营时常量池中。
  • Class文件常量池具备动态性,运行期间也可能将新的常量放入运行时常量池,常见的便是String类的intern()方法

1.6 直接内存(非运行时数据区域)

简述:
          直接内存不是JVM运行时数据区的一部分,也不是JVM规范定义中的内存区域。但是这部分内存也被频繁使用,也可能导致OutOfMemoryError异常
          在JDK 1.4中新引入了NIO(New Input/Output)类,是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,可以使用Native函数直接分配堆外内存,通过存储在Java堆中的DirectByteBuffer对象作为引用,去操作这块内存,可以避免在Java堆与Native堆来回复制数据。

2、Java对象组成

在JVM中,java对象在内存中分为3块区域:

  • (1)对象头(Header);
  • (2)实例数据(Instance Data);
  • (3)对齐填充(Padding)。

2.1 对象头(Header)

对象头包含两部分信息:
(1)存储对象自身运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据长度为32bit或64bit(32位或64位虚拟机),官方称为:Mark Word

标志位状态存储内容
01未锁定对象哈希码、对象分代年龄
00轻量级锁定指向锁记录的指针
10膨胀(重量级锁定)指向重量级锁的指针
11GC标记空,不需要记录信息
01可偏向偏向线程ID、偏向时间戳、对象分代年龄

(2) 类型指针即指向它的类元数据的指针,jvm通过其确定对象属于那个类的实例,并非所有虚拟机都必须在对象数据上保留类型指针,查找元数据信息不一定经过对象本身。Java数组对象头中还有一块用于记录数组长度的数据,普通的Java对象元数据信息能确定对象的大小,但数组的元数据无法确定数组大小。

2.2 实例数据(Instance Data)

          实例数据是对象真正存储的有效信息即程序代码中定义的各种类型的字段内容,无论是父类继承的,还是子类定义的,都将记录下来。这部分存储顺序会受到JVM的分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义顺序的影响,相同宽度的字段总是被分配到一起。
          从分配策略中可以看出,在父类中定义的变量尽会出现在子类之前,若CompactFields参数值为true(默认true),那么子类之中较窄的变量可能会插入到父类变量的空隙之中。

2.3 对齐填充(Padding)

          对齐填充并不是必然存在的,没有特别含义,仅仅起到占位符的作用。JVM自动内存管理系统要求对象起始地址必须是8字节的整数倍即对象大小必须是8字节的整数倍,对象头部分正好是8字节的倍数,因此当对象实例数据部分没有对齐时,则需要通过对齐填充来补全。

3、对象访问方式

          Java程序通过栈上的reference数据来操作堆上的具体对象,实际上reference类型在JVM中只规定了一个指向对象的引用,并没有规定是通过哪种方式去定位、访问堆中对象的具体位置,访问对象的方式取决于虚拟机JVM,目前主流访问方式主要分为两种:
(1)句柄访问方式;
(2)直接指针访问方式;

这两种对象访问方式各有优势:

访问方式优势劣势
句柄访问方式GC时对象移动只改变实例数据指针,reference本身不需要改动需要访问两次指针,获取实例数据和类型数据
直接指针访问方式访问速度快,只访问一次指针GC时对象移动,reference需要改动

3.1 句柄访问方式

          句柄访问方式是Java堆划分出来一块内存作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据(指针)和类型数据的具体地址信息(指针)
在这里插入图片描述

3.2 直接指针访问方式

          直接指针访问方式是reference中存储的就是对象地址,但Java堆对象布局就必须考虑如何放置访问类型数据的相关信息(指针);

在这里插入图片描述

4、GC判断对象回收算法

          JVM对堆进行GC前,需要判断对象是否存活(回收),那些已经“死去”(即不可能再被任何途径使用的对象)。

4.1 引用计数算法(JVM未使用)

定义:
          给对象中添加一个引用计数器,每当有一个地方引用,计数器就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

优缺点:
(1)引用计数算法(Reference Counting)实现简单,判定效率高,大部分场景是一个不错算法,如微软公司发COM(Component Object Model)计数、FlashPlayer、Python语言等等;(优点)
(2)很难解决对象之间相互循环引用问题,如两个对象中的某个字段互相引用 testA.field = testB testB.field = testA;(缺点)

4.2 可达性分析算法(JVM使用)

          通过一系列的“GC Roots”的对象作为起始点,从该节点开始向下搜索形成引用链(Reference Chain),当一个对象到GC Roots无任何引用链时(GC Roots与对象不可达),则说明此对象是不可用,可被回收。

在这里插入图片描述

在Java领域中,能作为*GC Roots对象的包括以下4种

  • (1)虚拟机栈(栈帧中的局部变量表)中引用的对象;
  • (2)方法区中类的静态属性引用的对象;
  • (3)方法区中常量引用的对象;
  • (4)本地方法栈中JNI(Native方法)引用的对象。

5、对象引用种类

无论是引用技术算法、还是可达性分析算法都是通过“引用数量”和“引用链是否可达”,判定对象是否存活都与引用有关。

  • (1)强引用(Strong Reference):强引用在程序代码中常见,如Test test = new Test()只要这个强引用还存在,GC收集器永远不会回收掉被引用的对象
  • (2)软引用(Soft Reference):描述一些还有用但并非必须的对象。对于软引用关联的对象,只要系统内存还足够,则不会回收。在系统将要发生内存溢出(OOM)之前,将会把这些对象列入回收范围之中进行第二次回收,若此次回收还没足够内存就会抛出内存溢出异常;
  • (3)弱引用(Weak Reference):描述非必须对象,强度比软引用更弱,只能生存到下一次GC发生之前,一旦GC将会被会回收掉
  • (4)虚引用(Phantom Reference):幽灵引用或幻影引用,最弱的引用关系。对象是否存在虚引用,不会对其生存时间造成任何影响,也无法通过虚引用获取对象实例。虚引用的唯一目的就是在这个对象被GC后收到一个系统通知

6、对象回收过程(两次标记)

    在可达性分析算法中,一旦java对象与GC Roots不可达时,也并非是必死的,要宣布一个对象真正被回收需要经历两个标记过程。
(1)第一个过程:若对象在进行可达性分析后发现没有与GC Roots 相连接的引用链,则进行第一次标记且进行一次筛选(是否有必要执行finalize()方法)。没有必要执行的条件:当对象没有覆盖 finalize() 方法 或 finalize() 方法已经被虚拟机调用过。若被判定为有必要执行finalize()方法,则该对象将会被放置在F-Queue队列中,然后会由一个虚拟机自动建立的、低优先级的Finalizer线程去执行(JVM触发)。

说明:

  • (1)队列中的成员互不影响,各自执行,不会串联等待,防止某个对象执行finalize()方法缓慢或死循环导致队列其他对象永久性等待,最终导致内存回收系统崩溃。
  • (2)finalize()方法是对象逃脱死亡的最后一次机会,后面GC对F-Queue中的对象进行第二次小规模标记,在第一次执行finalize()方法时与其他引用链上的任何一个对象建立关联即可自救(移除即将回收的集合)。

(2)第二个过程:在第一个过程筛选标记后,对F-Queue队列中的对象进行小规模标记,然后进行回收。

public class OOMTest {public static OOMTest obj = null;public void test(){System.out.println("test方法调用!");}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("finalize method executed!");System.out.println("对象this:"+ this);obj = this;}public static void main(String[] args) throws InterruptedException {obj = new OOMTest();obj = null;// 第一次调用gc,会调用finalize()方法,进行自救System.gc();long startTime = System.currentTimeMillis();// 休眠2秒,gc里面的finalize()方法优先级较低Thread.sleep(TimeUnit.SECONDS.toMillis(2));System.out.println("休眠时间:" + (System.currentTimeMillis() - startTime)/1000);System.out.println("对象obj:"+obj);if(obj != null){obj.test();}else{System.out.println("gc 第一次已回收!");}// 第二次进行gc,不会再调用finalize()方法,无法进行自救obj = null;System.gc();if(obj != null){obj.test();}else{System.out.println("gc 第二次已回收!");}}
}
输出结果:finalize method executed!对象thiscom.jvm.OOMTest@1bb3fcd休眠时间:2对象obj:com.jvm.OOMTest@1bb3fcdtest方法调用!gc 第二次已回收!

7、方法区回收

          Java虚拟机规范中确实说过不要求虚拟机在方法区中实现垃圾收集,并且方法区中垃圾收集性价比较低,堆中的新生代,一次垃圾回收能实现70%~90%的空间,永久代的垃圾收集效率远远低于堆中的回收

永久代的垃圾收集主要分为两部分:
(1)废弃常量:没有其他地方引用这个字面量(常量池);
(2)无用的类:

  • (1)堆中没有该类的任何实例;
  • (2)加载该类的ClassLoader已经回收;
  • (3)该类的Class 对象没有被其他地方引用,无法通过反射来访问该类方法;

8、GC回收算法(思想)

8.1 标记-清除算法

最基础的收集算法:标记——清除(Mark-Sweep)算法,分为两个阶段即标记和清除。
(1)标记:首先标记出所有需要回收的对象;
(2)清除:将标记好的对象统一回收;
缺点:
(1)效率问题:标记和清除两个过程效率不高;
(2)空间问题:标记清除之后产生大量不连续的内存碎片,可能会导致分配大对象时无法分配到足够大的内存而触发GC。
在这里插入图片描述

8.2 复制算法

          为了解决效率问题,复制(copying)算法横空出世,将可用内存划分为大小相等的两块,每次使用其中的一块,当这块内存用完了就将活着的对象复制到另外一块儿,然后将使用过的这块内存全部清理掉。

优点:
          每次对使用过程中的那块内存进行整个回收,内存分配时无需考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存,实现简单、运行高效(存活对象少时)。
缺点:
           可用内存缩小了一半。
运用:
          一般的JVM在新生代中使用复制算法,将新生代分为Eden空间和两块Survivor空间(8:1:1),每次使用Eden和其中一块Survivor,回收时将Eden和Survivor中还存活的对象一次性复制到另外未使用过的Survivor空间上。当复制过程中,用于活着的对象在新的Survivor空间无法存储时,会通过分配担保机制进入老年代
在这里插入图片描述

8.3 标记-整理算法

          复制算法在对象存活率较高时需要进行多次复制操作,效率就会急速下降,若其中一半内存不够用则需要额外的空间进行分配担保,保证在极端情况下也能存活,在老年代就不适用复制算法
          根据老年代的特点,“标记-整理”(Mark-Compact)算法随之诞生,标记过程与最基础算法——“标记-清除”中的标记过程一致,只是清除过程改为整理过程即将存活的对象向另外一端移动,然后直接清理掉端边界以外的内存。

在这里插入图片描述

8.4 分代收集算法(JVM常用)

Java堆分为新生代和老年代,然后根据其特点采用最适当的收集算法。
(1)新生代特点:垃圾收集时发现大批对象可回收,少量对象存活,采用 复制算法
(2)老年代特点:对象存活率高,没有额外空间分配担保,采用 标记—整理算法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/89662.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023牛客暑期多校训练营9-Non-Puzzle: Segment Pair

2023牛客暑期多校训练营9-Non-Puzzle: Segment Pair https://ac.nowcoder.com/acm/contest/57363/I 文章目录 2023牛客暑期多校训练营9-Non-Puzzle: Segment Pair题目大意解题思路代码 题目大意 解题思路 对于每一对 [ l i , r i ] [l_i,r_i] [li​,ri​]和 [ l i ′ , r i …

Linux命令200例:adduser用于创建新用户

🏆作者简介,黑夜开发者,全栈领域新星创作者✌。CSDN专家博主,阿里云社区专家博主,2023年6月csdn上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 &…

[LeetCode - Python] 11.乘最多水的容器(Medium);26. 删除有序数组中的重复项(Easy)

1.题目: 11.乘最多水的容器(Medium) 1.代码 1.普通双指针对撞 贪心算法 class Solution:def maxArea(self, height: List[int]) -> int:# 对撞双指针# 对比记录最大面积,并移动短板,重新计算;left,…

SpringBoot整合WebSocket详解

环境:Springboot3.0.5 WebSocket介绍 WebSocket协议RFC 6455提供了一种标准化的方式,通过一个TCP连接在客户端和服务器之间建立全双工、双向的通信通道。它是一个不同于HTTP的TCP协议,但设计为在HTTP之上工作,使用80和443端口&am…

Excel革命,基于电子表格开发的新工具,不是Access和Power Fx

深谙其道 在日常工作中,Excel是许多人不可或缺的办公工具。 是微软的旗下产品,属于Microsoft 365套件中的一部分,强大的数据处理和计算功能,被普遍应用在全球各行各业的人群当中,是一款强大且普及的电子表格软件。 于…

Do not access Object.prototype method ‘hasOwnProperty‘ from target object

调用 hasOwnProperty 报错:不要使用对象原型上的方法,因为原型的方法可能会被重写 if (this.formData.selectFields.hasOwnProperty(selectField)) {delete this.formData.selectFields[selectField];} else {this.formData.selectFields[selectField] …

【FastColoredTextBox】C# 开源文本编辑控件

主界面截图 使用Demos演示 FastColoredTextBox 是一个用于在 C# 程序中实现高亮语法着色、代码编辑和文本显示的自定义控件。它提供了许多功能,包括: 语法高亮:FastColoredTextBox 支持多种语言的语法高亮,可以根据语法规则将不同…

安路FPGA的赋值报错——移位处理,加括号

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 在使用移位符号用来当作除以号使用时,发现如下问题 其中 cnt_8K 为偶数和奇数时输出的数据不一样 reg [10:0] cnt_8K; reg [10:0] ram1_addra; always(posedge clk_16M) begin if(ram_out_flag )begin if(…

【Servlet】(Servlet API HttpServlet 处理请求 HttpServletRequest 打印请求信息 前端给后端传参)

文章目录 Servlet APIHttpServlet处理请求 HttpServletRequest打印请求信息前端给后端传参 Servlet API Servlet中常用的API HttpServlet 实际开发的时候主要重写 doXXX 方法, 很少会重写 init / destory / service destory 服务器终止的时候会调用. //下面的注解把当前类和…

【ARM】Day1

作业1:思维导图 作业2: 作业3:用for循环实现1~100之间和5050

ROS-PyQt小案例

前言:目前还在学习ROS无人机框架中,,, 更多更新文章详见我的个人博客主页【前往】 ROS与PyQt5结合的小demo,用于学习如何设计一个界面,并与ROS中的Service和Topic结合,从而控制多个小乌龟的运动…

事务和事务的隔离级别

1.4.事务和事务的隔离级别 1.4.1.为什么需要事务 事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位(不可再进行分割),由一个有限的数据库操作序列构成(多个DML语句,select语句不包含事务&…

CSDN编程题-每日一练(2023-08-14)

CSDN编程题-每日一练(2023-08-14) 一、题目名称:小股炒股二、题目名称:王子闯闸门三、题目名称:圆小艺 一、题目名称:小股炒股 时间限制:1000ms内存限制:256M 题目描述: …

算法通关村第七关——递归和迭代实现二叉树前中后序遍历

1.递归 1.1 熟悉递归 所有的递归有两个基本特征: 执行时范围不断缩小,这样才能触底反弹。终止判断在调用递归的前面。 写递归的步骤: 从小到大递推。分情况讨论,明确结束条件。组合出完整方法。想验证就从大到小画图推演。 …

LinuxC编程——进程间通信(二)(信号、共享内存)

目录 一、信号1.1 概念1.2 信号的响应方式⭐⭐⭐1.3 几种常见的信号1.4 函数练习 二、共享内存2.1 共享内存的特点2.2 共享内存创建步骤⭐⭐2.3 共享内存创建所需函数 信号主要用来通知进程异步事件的发生。最初信号设计的目的是为了处理错误,它们也用来作为最基本的…

【设计模式】前端控制器模式

前端控制器模式(Front Controller Pattern)是用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理。该处理程序可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种…

Java Review - 关于代理的二三事儿

文章目录 Pre概述静态代理概述Code 动态代理概述实现方式一 - JDK代理或接口代理概述Code 实现方式二 - CGLib 子类代理 (Code Generation Library)概述pom依赖Code Pre Java-JDK动态代理 Java-CGLib动态代理 概述 代理模式是一种结构型设计模式,其目的是为其他对…

我们常说这个pycharm里有陷阱,第三方库导入失败,看这里!

最近有小伙伴遇到了明明安装了 python 第三方库,但是在 pycharm 当中却导入不成功的问题。 ​ 一直以来,也有不少初学 python 的小伙伴,一不小心就跳进了虚拟环境和系统环境的【陷阱】中。 本文就基于此问题,来说说在 pycharm 当…

PPT颜色又丑又乱怎么办?

一、设计一套PPT时,可以从这5个方面进行设计 二、PPT颜色 (一)、PPT常用颜色分类 一个ppt需要主色、辅助色、字体色、背景色即可。 (二)、搭建PPT色彩系统 设计ppt时,根据如下几个步骤,依次选…

基于vue3+webpack5+qiankun实现微前端

一 主应用改造(又称基座改造) 1 在主应用中安装qiankun(npm i qiankun -S) 2 在src下新建micro-app.js文件,用于存放所有子应用。 const microApps [// 当匹配到activeRule 的时候,请求获取entry资源,渲染到containe…