目录
一、什么是垃圾回收机制二、垃圾回收的原理三、垃圾回收的过程四、有哪些垃圾回收的算法?五、有哪些垃圾回收器?
一、什么是垃圾回收机制
Java的垃圾回收机制是JVM的自动内存管理机制,是一个运行在JVM后台的守护进程,由GC(Garbage Collection)实现。主要负责识别和回收不被使用的对象,以免造成内存泄漏、内存溢出的情况发生。
Java的回收机制是一个低优先级的进程,它可以根据内存的使用情况动态的调整其优先级。因此,垃圾回收的执行时间是不确定的。这样设计的原因是,垃圾回收也需要消耗CPU资源,如果频繁的进行垃圾回收,会对程序造成影响。
二、垃圾回收的原理
垃圾回收机制通过对象的可达性算法(引用链法)分析来判断对象是否在被使用,如果一个对象不再被引用,那它就被认为是无用的,可以被回收的。垃圾回收主要针对堆空间和方法区进行。
可达性算法:又被称为“根可达算法”、“引用链法”。主要原理是以GC Roots(根)为起点,从起点开始往下寻找,搜索到的对象被判定为存活。如果有对象数据,但是此对象和根节点没有相连(即无法从任何一个根节点到达此对象,根不可达),那么此对象会被判为不可用,会在本次GC时被清除。
在Java中,GC Roots包含以下几种:
1、虚拟机栈中引用的对象:当我们在程序中创建一个对象时,对象会在堆中开辟一块空间,同时会将这块空间的地址作为引用(指针)保存在虚拟机栈中;如果对象的生命周期结束了,那么引用就会从虚拟机栈中出栈。如果在虚拟机栈中还有引用,那么说明此对象还存活。
2、方法区中的静态属性引用的对象:当我们在类中定义了全局的静态变量,也就是使用了static关键字时,因为虚拟机栈是线程私有的,所以这种对象的引用会保存在公共的方法区中,因此将方法区中的静态变量作为GC Roots是必要的。
3、方法区中常量引用的对象:即使用了static final,因为这种方式引用的对象在初始化之后不会再更改,所以方法区常量池的引用的对象也应该作为GC Roots。
4、本地方法栈中(Native方法)引用的对象:在使用JNI技术时,有时候单纯的Java代码不能满足我们的编程需求,我们可能需要在Java中调用C、或者C++的代码,因此会用到native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为GC Roots。
5、被同步锁synchronized持有的对象。
根可达算法在执行过程中会进行两次标记。第一次标记用来判断GC Roots是否链向该对象,如果不可达,即会进行第一次标记;如果对象重写了**finallize()**方法,JVM会创建一个低优先级的线程用来执行此方法,如果在此方法中重新和GC roots引用链上的任何一个对象建立关联即可,比如把自己赋值给某个类变量或者是对象的成员变量,那么这个对象会被移出清除的队列中。
还有一种判断对象存活的方法:引用计数法 。就是给每一个对象设置一个引用计数器,每当有一个对象引用这个对象时,计数器就会+1,引用失效时,计数器-1。当一个对象的引用计数器为0时,说明此对象没有被引用,也就是“死对象”,需要被回收。此方法有一个问题,就是当两个对象互相引用,即对象A引用对象B,B中又引用A,此时两个对象的引用计数器都不会为0,就永远无法被清理。所以主流的虚拟机都没有采用这种方式。
三、垃圾回收的过程
在JVM开始运行的时候,所有区域都处于空闲状态。一个对象刚刚被new关键字创建出来的时候,会在伊甸区(Eden)也就是新生代区域中选择一个空闲的区域来进行存放。
当创建的对象过多,伊甸区达到饱和,这时候就会触发Minor GC,进行垃圾回收,GC会挑出一些空闲空间作为幸存区(Survive From),用复制算法将还存活的对象复制出来,存放在幸存区(Survive From),然后将之前的伊甸区释放出来。随着系统的持续运行,伊甸区再次达到饱和,再次触发Minor GC。再次挑选出一块空闲区域作为幸存区(Survive To),将伊甸区和原幸存区(Survive From)的存活对象使用复制算法复制到Survive To,当此次Minor GC结束后,Survive To会成为下一次Minor GC的Survive From。在新生代区中,伊甸区、Survive1、Survive2的比例约为8:1:1。
在幸存区(Survive)内,对象每经历过一次GC,年龄就会+1岁。当年龄达到一定的阈值(默认是15,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到老年代中。
直到老年代也饱和,无法存放新创建的对象,此时JVM会进行FullGC(Major GC),如果Major GC后还是无法保存对象,就会产生OOM(OutOfMemoryException)。一般在内存没有达到上限的时候,不会进行老年代的清理。
四、有哪些垃圾回收的算法?
1、复制算法(copying):将JVM堆内存中的可用内存划分为大小相等的两块空间,每次只使用其中的一块。当这一块内存使用达到饱和,就进行Minor GC,将存活的内存复制到另一块上面,然后再把已经使用过的内存空间一次性清理掉。这样每次都是对整个半区进行回收。特点:运行效率较高,不会产生内存碎片;缺点是消耗内存,实际可用内存只有一半,代价较高。
2、标记-整理(Mark-Compact):第一步利用可达性算法去遍历内存中的对象,把存活的对象和垃圾对象进行标记;第二步将所有存活的对象向一块区域移动,然后将此区域外的内存全部回收。特点:无碎片空间产生;缺点是执行效率相对较慢。
3、标记-清除(Mark-and-Sweep):第一步利用可达性算法遍历内存,将存活的对象和死亡对象分别进行标记;第二步再次遍历,将死亡对象回收掉。特点:标记和清除的效率都不太行;且标记清除后往往会产生大量的不连续的空间分片,可能会导致之后程序运行的时候需要分配大对象而找不到连续分片而不得不触发一次GC。
4、分代收集(Generational):将Java堆分为新生代和老年代,这样我们就可以根据各个年代的特点使用适当的回收算法。新生代有更高的回收频率,可以采用复制算法快速回收生命周期短的对象;长期存活的对象就进入老年代,老年代的垃圾回收效率可以相对低一些可以采用标记-整理或者标记清除进行回收。缺点是对长寿的对象,如果频繁的进行内存拷贝,效率会比较低。
五、有哪些垃圾回收器?
1、Serial GC(串行垃圾收集器):Serial GC是一种基于标记-整理和复制算法的垃圾回收器,对于新生代采用复制算法,老年代则采用标记-整理算法。它通过短暂的停止程序的所有线程来执行垃圾回收操作,因此被成为“Stop The World”。在垃圾回收过程中,Serial GC会使用单个线程来扫描和回收堆内存中的对象,这样可以简化实现并减少系统的开销。适用于小型应用和开发环境。
2、Parallel Scavenge GC(并行垃圾收集器):Parallel Scavenge GC是Serial GC的并行版本,它利用多个线程来并行执行垃圾回收。Parallel Scavenge GC关注的是吞吐量,力求高效率利用CPU。在垃圾收集器工作的时候,同样会停掉程序的其他线程,仅留下用来执行垃圾回收的线程。采用标记-复制算法来实现新生代的垃圾回收。
这个算法分为两个阶段:
—>第一阶段:标记阶段,在此阶段垃圾收集器会扫描新生代的所有对象,并将存活的对象进行标记;
—>第二阶段:将所有标记为存活的对象复制到一个新的区域,然后将原区域进行清除回收。这个阶段还会判断复制对象的大小,如果大小超过了新生代的一半,则直接复制到老年代中。JDK1.8中,对于新生代的垃圾回收,默认是使用Parallel Scavenge GC。
3、Concurrent Mark Sweep(CMS)(并发垃圾收集器):CMS收集器是HotSpot虚拟机第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户的线程(基本上)同时工作。
主要采用的是标记-清除出算法。
—>初始标记:暂停其他的所以线程,并记录下GC Roots直接能引用的对象,速度很快;
—>并发标记:此阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时比较长但是不需要停掉用户的线程。但是因为客户线程的继续运行,可能会导致已经标记过的对象的状态被改变;
—>重新标记:重新标记阶段就是为了修正上个阶段期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的耗时一般会比初始标记阶段稍微长一点,但是远远低于并发表及的耗时。这里主要是用到三色标记中的增量更新算法去做重新标记。
—>并发清理:开启用户线程,同时GC线程开始对未标记的区域做清除。这个阶段如果有新增的对象会被标记为黑色不做任何处理;
—>并发重置:重置本次GC过程中的标记数据。
优点:低停顿,可与用户线程并发执行;
缺点:
因为采用的是标记-清除算法,所以会导致大量的内存碎片产生。可能会对大对象分配的时候因为找不到连续的空间导致FullGC(Major GC),或者导致OOM异常;
对CPU数量要求高,CMS默认启动的回收线程数为(CPU数量+3)/4,当CPU数量不足4个时,可能会对用户线程影响较大;
对CPU资源敏感,因为在并发阶段虽然不会导致用户线程停顿,但是会占用一部分线程(CPU资源),可能会导致程序整体变慢,降低吞吐量。
JDK1.8中对于老年代的清理使用的是CMS收集器。
4、G1 GC(Garbage-First GC):G1 GC是一种向大堆内存和低停顿时间的垃圾回收器。它将堆内存划分成多个大小相等的区域,并使用一种叫做“Garbage-First”的算法来进行回收。G1 GC会动态的调整
每个区域的大小,并在回收时优先选择含有垃圾最多的区域进行回收,以尽量减少停顿的时间。G1 GC适用于大堆内存和对停顿时间有严格要求的应用场景。
结语:本篇文章主要整理一下自己学习和了解到的关于Java垃圾回收机制的一些知识。欢迎各位Coder交流。