JVM原理之GC原理

  • 追踪回收算法(tracing collector)可达性分析算法
  • 按代回收算法(Generational Collector)
  • 复制回收算法(Coping Collector) (新生代)
  • 标记-清理算法 (老年代)
  • 标记-整理算法 (老年代)
  • 空间分配担保(Handle Promotion Failure)

什么是GC

GC(Garbage Collection)垃圾收集机制,这也是Java和C/C++之间的主要区别之一。对于一个Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对于内存泄漏和溢出的问题,不需要那么特别注意。

总的来说:GC机制对于JVM中的内存进行标记,确定哪一些内存需要回收。再根据一些回收策略,自动进行回收内存,永不停息的保证JVM中的内存空间。这也就防止了内存泄漏和溢出问题了。

JVM的内存分布

对于了解GC的原理,那么JVM的内存分布肯定是需要了解的。在Java运行的时候,JVM管理的内存区域有以下几块: JVM原理之GC原理

私有内存区:

域名 特性
程序计数器 指示当前程序执行到了哪一行,执行JAVA方法时记录正在执行的虚拟机字节码指令地址;执行本地方法时,计数器值为undefined
虚拟机栈 用于执行JAVA方法。栈帧存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。程序执行时栈帧入栈;执行完成后栈帧出栈
本地方法栈 用于执行本地方法,其它和虚拟机栈类似

共享内存区:

区域名称 特性
Java堆 堆区是理解Java GC机制最重要的区域,没有之一。JVM管理内存中,最大的一块。 堆区由所有线程共享,在虚拟机启动时创建。堆区的存在是为了存储对象实例
方法区 方法区是各个线程共享的区域,用于存储已经被虚拟机加载的 类信息 (即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、 final常量静态变量编译器即时编译的代码等。

Java堆的分布

GC主要针对堆内存,所以将堆内存进行详细阐述。

堆内存主要分为三块: 新生代(Youn Generation)、老年代(Old Generation)、持久代(Permanent Generation)

三代的特点不同,造就了他们使用的GC算法不同:

  1. 新生代适合生命周期较短,快速创建和销毁的对象;
  2. 老年代适合生命周期较长的对象;
  3. 持久代在Sun Hotpot虚拟机中就是指方法区(有些JVM根本就没有持久代这一说法)。

JVM原理之GC原理

新生代

新生代(Youn Generation):大致分为Eden区和Survivor区,Survivor区又分为大小相同的两部分:FromSpace和ToSpace。新建的对象都是从新生代分配内存,Eden区不足的时候,会把存活的对象转移到Survivor区。当新生代进行垃圾回收时会出发 Minor GC (也称作 Youn GC )。Eden占比80%,两块Survivor占比20%。

新生代的回收,引用Copy算法。回收过程大致如下:

  1. Eden区第一次内存满之后,执行MinorGC,清理消亡对象。之后将剩余存活对象复制到From Survivor区中(此时To区空白,两个Survivor总有一个空白)
  2. 第二次Eden区满之后,再次执行MinorGC,清除Eden中消亡对象。并且将From中的消亡对象清除,将Eden和From Survivor中存活的对象copy到To区。之后Eden再满,From和To区相互交换。
  3. 直到To区填满了,就将所有存活的对象移动到老年代。

注意:若没有填满,每次MinorGC的时候,给存活对象标记+1,根据–Xx:MaxTenuringThreshold(默认15)。标记大于1的时候,同样移进老年代。

老年代

老年代进行垃圾回收的时候,成为MajorGC/FullGC。

发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC

否则,如果小于的话,JVM就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败)。如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行Full GC(这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发Full GC,哪怕老年代还有很多内存,所以,最好不要这样做)

注意:FullGC比MinorGC的速度慢10被以上。因为FullGC的时候,用户线程暂停,降低系统性能、吞吐量。

永久代(方法区)

永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收。

对于无用的类进行回收,必须保证3点:

  1. 类的所有实例都已经被回收
  2. 加载类的ClassLoader已经被回收
  3. 类对象的Class对象没有被引用(即没有通过反射引用该类的地方)

JDK1.8之前,这些数据保存于此。JDK8,将永久代从堆中取出,数据存在本地内存地区(堆外空间)。

回收算法概述

追踪回收算法(tracing collector)可达性分析算法

通过一系列的称为 GC Roots 的对象作为起点, 然后向下搜索; 搜索所走过的路径称为 引用链/Reference Chain , 当一个对象到 GC Roots 没有任何 引用链 相连时, 即该对象不可达, 也就说明此对象是不可用的。

如图:对象5、6、7就是不可达的,需要被回收

JVM原理之GC原理

按代回收算法(Generational Collector)

当前主流VM垃圾收集都采用”分代收集”(Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为几块, 如JVM中的 新生代老年代永久代 . 这样就可以根据各年代特点分别采用最适当的GC算法:

  • 在新生代: 每次垃圾收集都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量存活对象的复制成本就可以完成收集 .
  • 在老年代: 因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用**“标记—清理” “标记—整理”**算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存 .

注意:除去按代回收,还有按区回收算法。

  • 分区收集

    上面介绍的分代收集算法是将对象的生命周期按长短划分为两个部分, 而分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的好处是 可以控制一次回收多少个小区间 .

    在相同条件下, 堆空间越大, 一次GC耗时就越长, 从而产生的停顿也越长. 为了更好地控制GC产生的停顿时间, 将一块大的内存区域分割为多个小块, 根据目标停顿时间, 每次合理地回收若干个小区间(而不是整个堆), 从而减少一次GC所产生的停顿.

复制回收算法(Coping Collector)(新生代)

把堆均分成两个大小相同的区域,只使用其中的一个区域,直到该区域消耗完。此时垃圾回收器终端程序的执行,通过遍历把所有活动的对象复制到另一个区域,复制过程中它们是紧挨着布置的,这样也可以达到消除内存碎片的目的。复制结束后程序会继续运行,直到该区域被用完。

但是,这种方法有两个缺陷:

  1. 对于指定大小的堆,需要两倍大小的内存空间,
  2. 需要中断正在执行的程序,降低了执行效率

标记-清理算法 (老年代)

该算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象(可达性分析), 在标记完成后统一清理掉所有被标记的对象。

JVM原理之GC原理

该算法会有以下两个问题:

1. 效率问题: 标记和清除过程的效率都不高;

2. 空间问题: 标记清除后会产生大量不连续的内存碎片, 空间碎片太多可能会导致在运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集.

标记-整理算法 (老年代)

标记清除算法会产生内存碎片问题, 而复制算法需要有额外的内存担保空间, 于是针对老年代的特点, 又有了 标记整理算法 . 标记整理算法的标记过程与标记清除算法相同, 但后续步骤不再对可回收对象直接清理, 而是让所有存活的对象都向一端移动,然后清理掉端边界以外的内存。

JVM原理之GC原理

空间分配担保(Handle Promotion Failure)

在执行Minor GC前, VM会首先检查老年代是否有足够的空间存放新生代尚存活对象, 由于新生代使用 复制收集算法 , 为了提升内存利用率, 只使用了其中一个 Survivor 作为轮换备份, 因此当出现大量对象在Minor GC后仍然存活的情况时, 就需要老年代进行分配担保, 让Survivor无法容纳的对象直接进入老年代, 但前提是老年代需要有足够的空间容纳这些存活对象.

但存活对象的大小在实际完成GC前是无法明确知道的。

因此Minor GC前, VM会先 首先检查老年代连续空间是否大于新生代对象总大小或历次晋升的平均大小 , 如果条件成立, 则进行Minor GC, 否则进行Full GC(让老年代腾出更多空间)。

然而取历次晋升的对象的平均大小也是有一定风险的, 如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然可能导致担保失败(Handle Promotion Failure, 老年代也无法存放这些对象了), 此时就只好在失败后重新发起一次Full GC(让老年代腾出更多空间).

转载

文章是站在各位大佬的肩膀上进行总结的,感谢。

http://www.importnew.com/23035.html

https://blog.csdn.net/antony9118/article/details/51375662

https://blog.csdn.net/anjoyandroid/article/details/78609971

http://baijiahao.baidu.com/s?id=1604308216748480477&wfr=spider&for=pc

原文 

http://liaowo.me/articles/2018/08/09/1533799942542.html

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » JVM原理之GC原理

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址