一、什么是GC
JVM GC是:JVM的垃圾回收算法,现在的JVM基本采用分代收集,Young区收集平凡,Old区收集较少,Perm(永久代)基本不回收;JVM进行GC时大部分是对新生代的回收,少量的全局回收。
GC按照作用的区域分为:
Minor GC:作用于新生代
Major GC(Full GC):作用于老年代,偶尔也会回收老年代和永久代。
二、如何定位垃圾
1、引用计数法
引用计数算法很简单,它实际上是通过在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加一,如果删除对该对象的引用,那么它的引用计数就减一,当该对象的引用计数为0时,那么该对象就会被标记为垃圾对象。
第10行 str引用了“ABC” 则“ABC”的计算器等于1。第11行str释放了该引用,所以“ABC”的计数器就减一。
优点:实现简单,判定高效,可以很好解决大部分场景的问题。
缺点:
- 很难解决对象之间相互循环引用的问题,当两个对象不再被访问时,因为相互引用对方,导致引用计数不为0;
- 开销较大,频繁且大量的引用变化,带来大量的额外运算;主流的JVM都没有选用引用计数算法来管理内存;
2、可达性分析法(根搜索算法)
可达性分析法:通过一系列"GC Roots"对象作为起始点,开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,认为该对象不可达,则证明该对象是不可用的;
优点:
- 更加精确和严谨,可以分析出循环数据结构相互引用的情况;
- 主流的调用程序语言(Java、C#等)在主流的实现中,都是通过可达性分析来判定对象是否存活的。
缺点:
- 实现比较复杂;
- 需要分析大量数据,消耗大量时间;
- 分析过程需要GC停顿(引用关系不能发生变化),即停顿所有Java执行线程(称为"Stop The World",是垃圾回收重点关注的问题);
哪些对象可以作为GC ROOT对象(GCRoot 可以是一个也可以是多个):
- 1、虚拟机栈(栈桢中的局部变量区,也叫局部变量表)中引用的变量
- 2、方法区中类的静态属性引用的对象
- 3、方法区中常量引用的对象
- 4、本地方法栈中JNI(Native)引用的对象
三、垃圾回收算法
1、标记-复制:它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次理掉。
JVM实现原理:Survivor区,一块叫From,一块叫To,对象存在Eden和From块。当进行GC时,Eden存活的对象全移到To块,而From中,存活的对象按年龄值确定去向,当达到一定值(年龄阈值,通过-XX:MaxTenuringThreshold可设置,默认=15)的对象会移到年老代中,没有达到值的复制到To区,然后直接清空Eden和From。之后,From和To交换角色,新的From即为原来的To块,新的To块即为原来的From块,且新的Form块中对象年龄加1.
优点:内存分配时也不用考虑内存碎片等问题;实现简单,运行高效;可以利用指针碰撞(bump-the-pointer)实现快速内存分配
缺点:
- 空间浪费:可用内存缩减为原来的一半,太过浪费(解决:可以改良,不按1:1比例划分);
- 效率随对象存活率升高而变低:当对象存活率较高时,需要进行较多复制操作(对象的引用地址需要复制),效率将会变低,所以该算法不适合对象存活率较高的场景或者区域。
应用场景:
- 现在商业JVM都采用这种算法(通过改良缺点1)来回收新生代;
- 如Serial收集器、ParNew收集器、Parallel Scavenge收集器、G1(从局部看)。
2、标记-清除:首先标记出需要回收的对象,标记完成之后统一清除对象。
标记:从根集合开始扫描,标记存货的对象
清除:扫描整个堆内存空间,回收未被标记的对象,使用free-list记录可以使用的区域
优点:基于最基础的可达性分析算法,它是最基础的收集算法;而后续的收集算法都是基于这种思路并对其不足进行改进得到的;
缺点:
- 效率问题:标记和清除都需要扫描,两个过程的效率都不高;
- 空间问题:标记清除后会产生大量不连续的内存碎片,这会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次垃圾收集动作。
- stop-the-Word:在标记时需要暂停JVM用户进程
应用场景:针对老年代
3、标记-整理
标记-整理:标记操作和“标记-清理”算法一致,后续操作不只是直接清理对象,而是在清理无用对象前,先将存活的对象都向一端移动,并更新引用其对象的指针,然后直接清理掉端边界以外的内存。
标记:和“标记-清理”算法一致
整理:扫描整个堆内存空间,将存活的对象都向一端移动,并更新引用其对象的指针,然后直接清理掉边界以外的内存。整理的目的就是整合零散分布的空间碎片为一个连续的空间。
优点:
- 不会像复制算法,效率随对象存活率升高而变低
- 不会像标记-清除算法,产生内存碎片,因为清除前,进行了整理,存活对象都集中到空间一侧;
缺点:主要是效率问题:除像标记-清除算法的标记过程外,还多了需要整理的过程,效率更低;
应用场景:回收老年代;
4、标记-清楚-整理(Mark-Sweep-Compact)
该算法是标记清除和标记整理的结合,标记-清除会产生碎片,标记-整理每次都进行整理效率不高;标记-清楚-整理 是如果老年代内存中没有一块连续续的空间可以存放将要进入对象,就进行整理;如果内存中的空间可以存放将要进入的对象,就进行标记-清除,这样就节省了整理的步骤可以提高效率。总结一句话:不是所有的时候都需要整理的,因为整理也付出代价。主要应用于老年代
总结: 没有最好的算法,只有最合适的引用场景
下一节:GC算法的实现