JVM运行时数据区原理及GC总结

编程

运行时数据区的结构

  • 堆(heap):虚拟机中存储所有对象实例与数组的内存区域,虚拟机启动时创建,所有线程共享。

    该区域分为三部分:

    一、Young Generation(新生代区域)eden,主要存放新建的对象或数组数据,虚拟机对于此区域的垃圾回收称为MinorGC。

    二、Young Generation(新生代区域)Survivor Space(S1,S0或from space,to space)对象从新生代区到老年代区的过度区,用于暂时存放新生代区垃圾回收后存活的对象,最终转存到老年代区;也用于存储老年代区内存不足时的活跃对象。

    三、Old Generation(老年代区域)主要存放生命周期较长的存活对象及系统新建的大对象,新生代区经过几次垃圾回收仍存活的对象将移至此区域,虚拟机对于此区的垃圾回收称为MajorGC,与新生代区的回收互不干涉。

  • 程序计数器(Program Counter Register):存储虚拟机字节码指令的空间,内存较小,是每个线程都拥有的区域,多线程下互不干涉;当线程调用java方法时存储当前虚拟机字节码指令,调用本地方法时此内存为空。
  • 虚拟机栈(VM Stack):后入先出,用于存储当前线程的相关信息,其元素称为栈帧(Stack Frame),是虚拟机分配给线程的私有空间,当线程创建时虚拟机栈同时被创建,生命周期与线程相同。当线程调用某个Java方法时,虚拟机将创建一个栈帧放入其中,栈帧存储局部变量、操作栈、方法返回值、动态链接及异常分派等。线程调用java方法的过程对应栈帧入栈出栈的过程,运行过程中,只有一个栈帧是处于活跃状态且该栈帧是栈顶元素。
  • 本地方法栈(Native Method Stack):虚拟机为当前线程分配的私有空间,用于存储线程调用native方法时,native方法的局部变量,操作栈与返回值等。
  • 方法区(Method Area或Permanent Space):此区域用于存储已被虚拟机加载的类信息、常量(常量池)、静态变量等,是虚拟机启动时创建,所有线程共享。

虚拟机垃圾回收机制

     虚拟机垃圾收集的内存空间是堆内存和方法区内存,由于程序计数器、虚拟机栈和本地方法栈区域的内存空间随着线程的结束而被清除,所以不需要垃圾回收机制。

  • 判定对象可回收机制

    引用计数算法:给对象添加一个引用计数器,当对象增加一个引用时计数器+1,当一个引用失效时计数器-1,当计数器值为0时对象为可回收状态。若两个对象出现循环引用的情况时,对象的引用计数器永远不为0,那么会导致垃圾回收器无法回收,由此java虚拟机未使用此回收算法。

    可达性分析(根搜索)算法:通过GC Roots作为起始点进行搜所,能够搜索到的对象为存活状态,搜索不到的为可回收的状态,java中使用此算法扫描确定回收对象。GC Roots内容包括虚拟机栈中局部变量表中引用的对象、本地方法栈中引用的对象、方法区内类静态属性引用的对象、方法区内常量引用的对象、方法区内类引用的Class对象等

  • 堆内存回收过程

    虚拟机垃圾回收算法有以下三种:

    一、标记清除算法:此算法分为两个阶段,标记和清除,标记从CG Roots可达的对象,标记完成后,统一清除不可达对象;此算法有两项不足之处,其一是标记清除两个过程效率都不高,其二是导致内存中产生大量的不连续的碎片空间,无法有效的为大对象分配连续的内存,从而触发一次垃圾回收。

    二、标记整理算法:此算法标记过程与标记清除算法一致,而整理过程是将所有存活对象移向一端,然后将存活对象端边界以外的内存直接清理。

    三、复制算法:将可用内存按容量划分为大小相等的两块,每次只使用一块,当这块内存用完后将存活的对象复制到另一块区域,然后将使用过的那快内存一次清除;不足之处是内存只用一半,空间利用率不高。

    java中采用分代回收的策略,使用复制算法对YG区的对象进行高频率的扫描回收,以标记-清除或者标记-整理算法对OG区的对象进行较低频率的回收。

    Java虚拟机为新建对象分配内存空间过程:

    一、jvm在eden区为新建对象分配空间,若空间不足,则触发minorGC,将不活跃的对象回收

    二、minorGC后若eden内存还是不足,jvm将eden中的部分活跃对象迁移至survivor space中,survivor space作为中间交换区,若OG区内存充足,jvm将Survivor space中的对象移至OG区域,若OG区内存不足时,则对象留在survivor space中

    三、当OG区内存不足时,会触发majorGC,对OG区进行回收

    四、经过完全的垃圾回收后,若eden、survivor、old区空间仍不足,jvm将无法在eden区为新建对象分配空间,此时会报内存不足的错误(out of memory)

  • 方法区内存回收

    方法区主要是对于常量池和类的回收。对于类的回收是由于java中存在大量使用反射、动态代理、动态生成JSP、OSGi等自定义Classloader的场景,为保证内存不溢出,jvm针对类进行了回收,可通过-Xnoclassgc参数来控制类是否回收。方法区的对象由于生命周期较长,所以回收频率较低。

    类的回收需满足以下条件(当条件满足时,jvm也不一定会进行回收)

    一、堆内存中不存在该类的实例,所有实例对象已被回收

    二、加载此类的ClassLoader已被回收

    三、java中不存在对于该类的类对象的引用,无任何地方通过反射来获取该类的信息或访问该类的方法

  • 垃圾收集器

    垃圾收集按线程分为单线程回收和多线程回收,按cpu执行状态分并行回收和串行回收。其中串行回收是垃圾回收与系统流程交替执行,垃圾回收时系统出现停顿的现象,而并行回收是与系统流程同时执行,垃圾收集器中CMS、G1是并行回收,其余是串行回收。

    JVM中垃圾收集器分以下几种:

    一、Serial收集器:以串行的方式执行回收工作,是一个单线程的针对新生代区域的收集器,在单cpu环境下收集效率最高;运行在Client模式下的应用默认的新生代收集器,在不频繁触发垃圾回收的情况下,serial收集几十兆甚至一两百兆的内存所停顿的时间可控制在一百多毫秒以内,you"dian是简单高效。

    二、ParNew收集器:Serial收集器的多线程版本,是运行在Server模式下应用的首选,默认开启的收集线程与cpu数量一致。到目前为止只有它与Serial收集器能配合CMS收集器工作。

    三、Parallel Scavenge收集器:多线程的”吞吐量优先“收集器,以提升系统cpu吞吐量(运行用户代码的时间占总时间的比值)为目标。

    四、Serial Old收集器:Serial的老年代版本,用于运行在Client模式下的环境,采用标记整理算法;当运行在Server模式下时用途有二,其一是JDK1.5及以前版本中配合Parallel Scavenge收集器使用,其二是CMS收集器发生”Concurrent Mode Failure“时使用。

    五、Parallel Old收集器:Parallel Scavenge收集器的老年代版本,采用标记整理算法,一般用于注重吞吐量和cpu资源敏感的场合。

    六、CMS(Concurrent Mark Sweep)收集器:用于老年代区域的收集器,采用标记清除算法,具有并发收集低停顿的优点,适合用于注重用户体验的应用中,已在JDK9中标记废弃。

           回收垃圾分四个流程:

           1. 初始标记:暂停其他所有线程(Stop The World),标记从GC Roots搜索可达对象,速度很快;

           2. 并发标记:进行GC Roots Tracing过程,同时开启GC和用户线程,耗时较长;由于期间用户线程会不断更新引用域,GC Roots搜索可达性无法做到实时性,故此处将跟踪记录引用更新的地方。

           3. 再次标记:暂停其他所有线程,修正并发标记期间因用户线程运行而导致的标记产生变动的那一部分对象的标记记录,暂停时间比初始标记长,比并发标记时间短。

           4. 并发清理:开启用户线程和GC线程,对标记区域进行整理清除,回收垃圾对象。

           CMS收集器虽实现了GC与用户线程的同步进行,但因并行收集而存在以下缺点:

           一、因并发性而对cpu资源敏感,并发期内占用一部分线程资源,导致应用程序变慢,造成吞吐量的降低(不适用于对账系统)。

           二、并发清除时用户线程产生新的垃圾对象(浮动垃圾)而无法处理,导致”Concurrent mode Failure“问题。

           三、因使用标记清除算法,会产生大量的空间碎片,从而导致新建大对象无法分配空间引起一次Full GC。

    七、G1(Garbage first)收集器:面向运行在Server模式下的应用,适用于多cpu及大容量内存的机器,满足停顿时间要求性能高,同时具备高吞吐量的特性。JDK7版本推出使用,以替代CMS收集器为使命,JDK9中已设定为默认收集器。

           G1收集器是可对新生代区和老年代区一起回收。G1将堆内存划分为大小相等的独立区域(Region),原本的新生代区和老年代区不再是物理隔离,他们都是一部分不一定连续的Region的集合。整体上G1是采用标记整理算法,局部(两个Region之间)采用复制算法实现GC。

           G1可以实现可预测的停顿,可以明确指定A毫秒的时间内,垃圾收集的时间不超过B毫秒;其作用是可以有计划的避免在java堆的进行全区域的垃圾回收,跟踪每个Region获得其收集价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,由此保证在有限时间内完成更高效率的收集。

           为避免JVM全局扫描,每块Region中都有一个Remembered Set用于记录Region对象的引用对象所在的Region,从而GC Roots搜索中不会出现遗漏。

           回收流程:

           1. 初始标记:暂停其他所有线程(Stop The World),仅标记GC Roots搜索能直达的对象,速度很快。

           2. 并发标记:从GC Roots进行可达性分析,与其他线程并发执行,找出存活对象,由于期间用户线程可能产生新的存活对象,所以不一定能找到所有存活对象。

           3. 最终标记:Stop The World,修正并发标记时因用户线程运行而导致标记发生变化的那部分对象的标记记录;变化记录存在线程的Remembered Set Log中,此阶段将Remembered Set Log合并到Remembered Set中;停顿时间比初始标记稍长,但远比并发标记时间短。

           4. 筛选回收:并发执行,首先排序各Region的回收价值和成本,然后根据指定的停顿时间选择回收策略,最后按回收策略回收价值高的Region中的垃圾对象;采用复制算法,从一个或多个Region复制存活对象到另一个空的Region中,此过程中压缩并释放内存;降低停顿时间,增加吞吐量。

           以下情况时,使用G1系统性能优于使用CMS:

           1. 超过一半的堆内存被活跃对象占用

           2. 对象分配频率或年代提升频率变化很大

           3. GC停顿时间较长,大于0.5至1秒

以上是 JVM运行时数据区原理及GC总结 的全部内容, 来源链接: utcz.com/z/512637.html

回到顶部