JVM的GC算法
Posted IT技术小输出
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM的GC算法相关的知识,希望对你有一定的参考价值。
JVM中的GC针对的是堆空间和方法区,几乎所有的对象都是存放在堆空间里,之所以是几乎,是因为还有逃逸分析栈上分配、标量替换等例外。方法区中主要存放类型信息。
GC总的来说分为两个阶段,分别是标记阶段:先将需要回收的对象找出来;清除阶段:也就是回收需要回收的对象。
目前用于标记回收对象的算法主要是引用计数法和可达性分析算法。其中引用计数算法为每个对象设置一个引用计数器,每当有引用指向它时,引用计数器的数值加1,进行垃圾回收的时候之后回收那些引用计数器值为0的对象,数值不为0的对象不会被回收。这种标记算法非常简单,并且效率很高,想Python语言就使用这种算法,但是Java并没用使用它,因为引用计数算法存在一个弊端:
如上图,对象1.2.3都是可被回收的对象,但是由于他们之间存在相互的循环引用,回收算法不会对它们进行回收。一旦这种情况出现的过多,就可能会造成内存泄漏,使任务崩溃。因此JVM选择了另一种标记算法:可达性分析算法。
可达性分析算法判断对象是否需要被回收的基本思路就是通过找过一系列的根对象组成“GC Roots”作为起始节点集,从根节点集出发,根据引用关系向下检索,走过的引用路径被称为“引用链”,如果有对象和GC Roots之间不存在引用链,那么就需要被清楚。
如上图,蓝色框就是一系列根节点组成的GC Roots,黑色框的对象都和GC Roots之间有引用链连接,不会被清楚算法清楚,红色框的对象虽然之间也有引用存在,但是和GC Roots之间并没有引用链连接,所以会被当做垃圾回收。
在虚拟机栈中引用的对象
在常量池中静态属性引用的对象
在常量池中常量引用的对象
在本地方法栈中JNI引用的对象
JVM内部的引用
所有被同步锁持有的对象
反应JVM内部情况的一些回调、本地缓存等
除了以上提到的可固定作为GC Roots的对象之外,还会有一些临时加入的对象。比如虚拟机一般都是基于分代收集设计的,比如说新生代中的对象有可能被老年代中的对象所引用,那么这些持有新生代中对象引用的老年代对象就需要加入GC Roots中。这样就会有另一个问题需要被考虑,每次找GC Roots都需要遍历一遍老年代找出目标对象吗?这样会非常的耗时效率不高。JVM引入了一个优化,在新生代上维护一个数据结构“记忆集”,这个数据结构将老年代划分为若干个小区域,标识出哪一块小区域存在跨带引用,在确定GC Roots时,只需要遍历那一小块内存区域即可,大大的提高了效率。
在谈及收集算法之前,先来了解分代收理论,现在的虚拟机大多数遵循了这个理论:
(1)绝大多数对象都是朝生夕死的,就像动物幼崽一样,在幼儿时期夭折率总是最高的。
(2)熬过越多次垃圾收集过程的对象就越难以消亡,动物成年后有肚子捕猎觅食的能力之后生存率就大大提高。
基于分代收集理论,大多数虚拟机将堆空间分为新生代和老年代,那么GC行为就区分出三种,分别是minor GC、major GC和full GC三种。第一个是单独对新生代的收集,第二个是单独对老年代的收集,最后一个是对整个堆空间和方法区的收集。默认的只要一个对象经过16次minor GC还没有被回收就会晋升入老年代。
full GC会对方法区进行垃圾回收,我们要尽量避免发生full GC,因为不仅耗时时间长,回收效率还比较低,方法区主要存储类型信息,但是要回收一个类型的条件是比较苛刻的,需要满足三个条件:
回收算法有三种:
清除算法:统一清除掉被标记为可回收对象的对象。此算法比较简单,存在明显的缺陷,其一它的效率是不稳定的,随着需要钱清楚对象的增多而增加耗时;最为严重的缺陷是会倒置堆内存的碎片化,可能会导致之后在分配较大对象时无法找到连续内存而出发full GC,而full GC正是我们需要避免的。
复制算法:复制算法需要留出一半内存空间空闲,当进行垃圾回收时,先将可达的对象复制到空闲的那一半空间中,然后再将原来的那一半内存空间中的数据一次性删除。这种方法不会产生碎片化的问题,但是会浪费掉一半的内存空间。
黄线左边就是使用的内存空间,右边是原本空闲的内存空间,当使用的空间满时,会将绿色存活的对象复制到右边空闲空间中,左边内存空间中的红色可回收对象和绿色对象就会一次性清理掉。现在大多数虚拟机新生代都采用赋值算法进行收集,但是进行了优化,因为新生代的对象大多都是朝生夕死的,所以空闲空间不需要留一半那么多,一般可用空间占新生代空间的90%。
每次Eden区满时,都会触发一次minor GC,存活的对象就会和survivor的from区进入to区,从上图绿色线是第一次minor GC,survivor1为from区,其中的存活对象和Eden区的存活对象一起进入to区;再看蓝色线的第二次GC,survivor1就为from区,其中的存活对想和Eden区的存活对象一起放入to区。从上可以看到,survivor的from和to不是一成不变的,而是不断变化的。
压缩算法:所有的存活对象向内存空间的一端移动,然后直接清除掉边界意外的内存。是一种移动式的回收算法,这种移动操作必须STW。
移动之后内存空间就会如上图,存活的对象都在内存空间的一端,黄色边界线以下的内存数据,都会被一次性清理掉。不会出现内存碎片化,并且不需要浪费一部分内存空间作为空闲,等待复制存放。
以上是关于JVM的GC算法的主要内容,如果未能解决你的问题,请参考以下文章
JVM中的GC垃圾回收
JVM GC算法
JVM之GC算法
GC 算法(实现篇) - GC参考手册
4. GC 算法(实现篇) - GC参考手册
JVM 技术详解:常见的 GC 算法(Parallel/CMS/G1)