Java 虚拟机 内存区域划分和垃圾收集机制

Java 虚拟机 内存区域划分和垃圾收集机制,第1张

Java 虚拟机 内存区域划分和垃圾收集机制 Java 虚拟机 内存区域划分和垃圾收集机制 Java 的运行时数据区域

Java虚拟机在执行Java程序的过程中,会把它所管理的内存划分为若干个不同的数据区域。有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。

主要分为:方法区,堆,虚拟机栈,本地方法栈,程序计数器。其中方法区和堆属于线程共享的数据区,而其他为线程隔离的数据区。(即一个线程有一个,各线程之间的这个区域互不影响,独立存储)。

1. 程序计数器

学过 *** 作系统课程的人应该都知道,PC是用来指示下一要执行的语句。在Java中也不例外,它是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。

字节码解释器工作时就是通过改变这个计数器的值来选取下一条要执行的字节码指令。

每个线程都有一个独立的程序计数器,这也很好理解,因为Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的(轮转法),在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器。各线程的程序计数器互不影响,独立存储。属于线程私有的内存。

需要注意的是,如果一个线程在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是本地(Native)方法,这个计数器值则应为空。

2. Java虚拟机栈

线程私有,生命周期与线程相同。

虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack frame)用于存储局部变量表、 *** 作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

通常说的栈就是虚拟机栈,更多情况下说的是栈中的局部变量表部分,局部变量表存放了编译器可知的各种Java虚拟机基本数据类型和对象引用(指向对象起始地址的指针)和returnAddress(指向了一条字节码指令的地址)类型。

局部变量表中的存储空间以局部变量槽来表示,其中64为的long和double通常占两个变量槽,其余占一个。

局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法所需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间,不会改变局部变量表的大小。

3. 本地方法栈

线程私有,生命周期与线程相同

与虚拟机栈作用类似,区别是虚拟机栈执行Java方法服务,本地方方法栈是为本地方法服务。

4. Java堆 (Heap)

Java堆是虚拟机所管理的内存中最大的一块,Java堆是被所有线程所共享的一块内存区域,在虚拟机启动时创建。该内存区域的作用就是存放对象实例。

Java堆在逻辑上被视为连续的,但在物理上并不是连续的。

Java堆既可以被实现成固定大小的,也可以是可扩展的(现在大部分都是可扩展的)。

5. 方法区

线程共享的内存区域

主要用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

里面有运行时常量池,它具有动态性,Java语言并不要求常量一定只有编译期产生,比如String类的intern()方法。

虚拟机对象的创建

平时我们创建一个对象,只需要使用一个new关键字。那么内部的创建过程又是什么?

首先,当Java虚拟机遇到一条字节码new指令时,将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,检查一个符号引用代表的类是否已被加载,如果没有就先执行相应的类加载过程。

通过类加载检查后,虚拟机会为新生对象分配内存(一般在堆中分配),对象所需内存的大小在类加载完成后便可完全确定。在分配内存的过程中,有两种算法,分别是指针碰撞和空闲列表。一般取决于垃圾收集器的算法。同时还需考虑线程并发问题,一般通过CAS同步机制配上失败重试的方式 或者 使用TLAB,本地线程分配缓冲区(每个线程在Java堆中预先分配一小块内存,用完后重新分配时在使用同步机制)来解决。

内存分配完成后,虚拟机将分配到的内存空间都初始化为零值(不包括对象头)。

然后虚拟机会对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到累的元数据信息、对象的哈希吗等信息。

这个时候虚拟机的工作就完成了,开始执行构造函数。

对象可以划分为三个部分:对象头、实例数据、对齐填充。

对象头 主要包括两类信息:自身的运行时数据(如哈希码,线程持有的锁等) 和 类型指针(指向它的类型元数据,标记属于哪一个类)。

对象的访问定位 主要通过 句柄 和 直接指针 两种方式。

句柄 优势:使用句柄访问最大的好处就是reference中存储的是稳定句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要被修改。

直接指针 优势:访问速度快, 比句柄少一次访问内存的时间。

GC机制 如何判定一个对象是否已经死亡? 1. 引用计数算法

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

优势:原理简单,判定效率高

缺点:很难解决对象之间互相循环引用的问题

2. 可达性分析算法 当前基本采用可达性分析算法

这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集(一个GC Roots的集合),从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

有一些固定作为GC Roots的对象:虚拟机栈中(栈帧中的本地变量表)引用的对象、各个线程调用的方法堆栈中使用到的参数、局部变量、临时变量等、方法区中静态属性引用的对象、基本数据类型对应的class对象、常驻异常对象、被同步锁(synchronized关键字)持有的对象等。

对象死亡会经过两次标记过程。第一次是发现没有引用链连接它,打上标记,然后如果要执行finalize()方法的话,就进入F-Queue队列中,稍后收集器会对F-Queue中的对象进行第二次小规模标记,如果想要拯救这个对象,可以在finalize()方法中把自己赋值给某个类变量或者对象的成员变量。

分代收集理论

目前大部分虚拟机都遵循了分代收集理论。

建立在两个假说之上:

1)弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
2)强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。

垃圾收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。

在Java堆划分出不同的区域之后,垃圾收集器才可以每次只回收其中某一个或者某些部分的区域——因而才有了“Minor GC”“Major GC”“Full GC”这样的回收类型的划分;也才能够针对不同的区域安排与里面存储对象存亡特征相匹配的垃圾收集算法——因而发展出了“标记-复制算法”“标记-清除算法”“标记-整理算法”等针对性的垃圾收集算法。

1. Mark-Sweep Collection 标记清除算法 后两种算法都是对这个算法的改进

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。

它的主要缺点有两个:

第一个是执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;

第二个是内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

2. Mark-Copying Collection 标记复制算法 一般新生代采用这个算法

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

缺点:

如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销。

将可用内存缩小为了原来的一半,空间浪费大。

因为新生代中的对象有98%熬不过第一轮收集,所以后来优化为把内存空间划分为 8:1:1 三块。 每次分配都使用一块80%和一块10%,发生垃圾收集时,将存活的对象移到另外一块10%上,其余90%的空间一次性清除。这样只有10%的空间是浪费的。

3. Mark-Compact Collection 标记整理算法 一般老年代采用这个算法

其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。

在移动存活对象时必须全部暂停用户程序才能进行,是一种极为负重的 *** 作。如果采用标记清除算法又会产生大量内存碎片。

所以诞生了一种混合方案,让虚拟机大多数时间都采用标记-清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配时,再采用标记-整理算法手机一次,获得规整的内存空间。

参考书籍《深入理解Java虚拟机》

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5686726.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存