GC垃圾回收机制
1.1GC概述
垃圾回收是一种自动的存储管理机制。 当一些被占用的内存不再需要时,就应该予以释放,以让出空间,这种存储资源管理,称为垃圾回收(Garbage Collection)。 垃圾回收器可以让程序员减轻许多负担,也减少程序员犯错的机会。
1.2GC执行时机
GC触发的条件有两种: (1)程序调用System.gc时可以触发; (2)系统自身来决定GC触发的时机。
了解jvm内存结构
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
- 调用System.gc时,系统建议执行Full GC,但是不必然执行
- 老年代空间不足
- 方法区空间不足
- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
1.3GC内容
自动垃圾回收机制就是寻找Java堆中的对象,并对对象进行分类判别,寻找出正在使用的对象和已经不会使用的对象,然后把那些不会使用的对象从堆上清除。如何区别这两类对象呢?这就有了两种方式:
-
引用计数法
引用计数算法是垃圾收集器中的早期策略。 在这种方法中,堆中的每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个引用变量,该对象实例的引用计数设置为 1。当任何其它变量被赋值为这个对象的引用时,对象实例的引用计数加 1(a = b,则b引用的对象实例的计数器加1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数减 1。
特别地,当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器均减 1。 任何引用计数为0的对象实例可以被当作垃圾收集。
引用计数收集器可以很快的执行,并且交织在程序运行中,对程序需要不被长时间打断的实时环境比较有利,但其很难解决对象之间相互循环引用的问题。 -
可达性分析
可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,通过一系列的名为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain)。 当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的。
在java中可以作为GC roots的有四种
- 虚拟机栈中的引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中的常量引用的对象。
- 本地方法栈中JNI的引用的对象。
1.4GC回收方法
- 标记-清除算法
a.标记,也就是垃圾收集器会找出那些需要回收的对象所在的内存和不需要回收的对象所在的内存,并把它们标记出来。
b.清除,垃圾收集器会清除掉上一步标记出来的那些需要回收的对象区域。
存在的问题就是碎片问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。 - 复制算法
标记清除算法每次执行都需要对堆中全部对象扫面一遍效率不高,为解决效率问题,复制算法将内存按容量划分为大小相等的两块,每次只是用其中的一块。 当这一块使用完了,就将还存活的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。 这样使得每次都对半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。紧接着它的缺点就来了会减少可用内存。 - 标记-整理算法
由于简单的标记清除可能会存在碎片的问题,所以又出现了压缩清除的方法,也就是先清除需要回收的对象,然后再对内存进行压缩操作,将内存分成可用和不可用两大部分。 - 分代收集算法
分代算法是上面几个算法的综合, Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。 而老年代中因为对象存活率较高、没有额外的空间对它进行分配担保,就必须使用“标记-清除”或者“标记-整理”算法来回收。
1.4垃圾回收器
1.Serial(串行GC)收集器
Serial收集器是一个新生代收集器,单线程执行,使用复制算法。它在进行垃圾收集时,必须暂停其他所有的工作线程(用户线程)。是Jvm client 模式下默认的新生代收集器。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率
2.ParNew(并行GC)收集器
ParNew 收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为与Serial收集器一样。
3. ParallelScavenge收集器
是一个新生代收集器,它也是使用复制算法的收集器,又是并行多线程收集器。Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同, CMS等收集器的关注点是尽可能的缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。 吞吐量=程序运行时间/(程序运行时间+垃圾收集时间)、虚拟机总共运行了100分钟。其中垃圾收集花掉1分钟,那吞吐量就是99%
4. SerialOld (串行GC)收集器
Serial Old是Serial收集器的老年代版本,它同样使用一个单线程执行收集、使用“标记-整理”算法。主要使用在Client模式下的虚拟机。
5.ParallelOld(并行GC)收集器
Serial Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
6. CMS(Concurrent Mark Sweep)收集器是一种获取最短回收停顿时间为目标的收集器
初始标记、并发标记、重新标记、并发清除
7.G1收集器
初始标记、并发标记、最终标记、筛选回收
1.5对象引用类型
1.强引用:使用new关键字创建的引用,只要强引用还存在,则垃圾回收永远不会回收掉被引用的对象
2.软引用:描述还有用但是非必须的对象,在系统即将发生内存溢出的时候才将这些对象放到回收范围中进行二次回收,如果回收之后还没有足够内存,则出现内存溢出异常。
3.弱引用:用来描述非必须的对象,但是它比软引用要弱一些,当垃圾回收期工作时,无论内存是否够用都会将引用的对象回收掉
4.虚引用:它是最弱的一种引用关系,对象是否有虚引用不会影响其生命周期,虚引用的唯一目的就是能在这个对象被回收是收到一个系统通知
1.5finalize()方法
- 首先,大致描述一下finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。若已经覆盖,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。若已经覆盖并且已经执行过finalize方法,则进行垃圾回收。
- 然后,简单说一下finalize()是用来做什么的,它和c++中的析构函数不同,c++中不存在垃圾回收,当程序员删除对象时会调用析构函数释放空间和进行一些收尾工作,而java存在垃圾回收,不需要这样,有可能永远也用不到finalize()方法。它最主要的用途是回收特殊渠道申请的内存。Java程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种JNI(Java Native Interface)调用non-Java程序(C或C++),finalize()的工作就是回收这部分的内存。
- 最后,jvm不能保证finalize一定被执行成功。