JVM系列文章
目录
前言
本次给大家介绍下JVM垃圾回收算法。
先上一张图,给大家总体看下。
一、什么是垃圾回收?
GC:垃圾回收,即:Garbage Collection。
【垃圾】特指存在于内存中的、不会再被使用的对象。
【回收】清除内存中的“垃圾”对象。
二、GC相关概念
-
2.1什么是可触及性?
就是GC时,是根据它来确定对象是否可被回收的。也就是说,从根节点开始是否可以访问到某个对象,也说明这个对象是否被使用。
可触及性分为3种状态:
① 可触及:从根节点开始,可以到达某个对象。
② 可复活:对象引用被释放,但是可能在finalize()函数中被初始化复活。
③ 不可触及:由于finalize()只会执行一次,所以,错过这一次复活机会的对
象,则为不可触及状态
-
2.2四种引用级别
-
2.3槽位复用
局部变量表中的槽位是可以复用的,如果局部变量超过了其作用域,则在其作用域之后的局部变量就有可能复用该变量的槽位,这样能够起到节省资源的目的。
-
2.4对象分配总览
2.4.1栈上分配
栈上分配是JVM提供的一项优化技术。基本思想如下所示:
- 对于那些线程私有的对象(即:不可能被其他线程访问的对象),可以将它们打散分配在栈上,而不是分配在堆上。
- 分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统的性能。
- 对于大量的零散小对象,栈上分配提供了一种很好的对象分配优化策略,栈上分配速度快,并且可以有效避免GC带来的负面影响,但是由于和堆空间相比,栈空间较小,因此对于大对象无法也不适合在栈上分配。
栈上分配的技术基础,逃逸分析和标量替换两者必须都开启。
2.4.1.1栈上分配—逃逸分析
逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。
对于线程私有的对象,可以分配在栈上,而不是分配在堆上。好处是方法执行完,
对象自行销毁,不需要gc介入。可以提高性能。而栈上分配的一个技术基础(如
果关闭逃逸分析或关闭标量替换,那么无法将对象分配在栈上)就是逃逸分析。
2.4.1.2栈上分配—标量替换
允许将对象打散分配在栈上。比如:若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。
- 标量。不可被进一步分解的量,JAVA的基本数据类型就是标量(如:int,long等基本数据类型等)。
- 聚合量。标量的反义词就是可以被进一步分解的量,普通的对象类型就是可以被进一步分解的聚合量。
- 替换过程
① 通过逃逸分析确定该对象不会被外部访问。
② 对象可以被进一步分解,即:聚合量。其中,JVM不会创建该对象,而会将该对象成员变量分解若干个被这个方法使用的成员变量所代替。这些代替的成员变量在栈帧或寄存器上分配空间。
2.4.2对象分配—TLAB分配
TLAB的全称是Thread Local Allocation Buffer,即:线程本地分配缓存区,这是一个线程专用的内存分配区域。
- 由于对象一般会分配在堆上,而堆是全局共享的。因此在同一时间,可能会有多个线程在堆上申请空间。因此,每次对象分配都必须要进行同步(虚拟机采用CAS配上失败重试的方式保证更新操作的原子性),而在竞争激烈的场合分配的效率又会进一步下降。JVM使用TLAB来避免多线程冲突,在给对象分配内存时,每个线程使用自己的TLAB,这样可以避免线程同步,提高了对象分配的效率。
- TLAB本身占用Eden区空间,在开启TLAB的情况下,虚拟机会为每个Java线程分配一块TLAB空间。参数-XX:+UseTLAB开启TLAB,默认是开启的。TLAB空间的内存非常小,缺省情况下仅占整个Eden空间的1%,当然可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。由于TLAB空间一般不会很大,因此大对象无法在TLAB上进行分配,总是会直接分配在堆上。TLAB空间由于比较小,因此很容易装满。
三、主要的垃圾回收算法
3.1引用计数
对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1;当引用失效时,引用计数器就减1;只要对象A的引用计数器的值为0,则对象A就不可能再被使用。
但引用计数器有两个严重问题:
① 无法处理循环引用的情况。
② 引用计数器要求在每次因引用产生和消除的时候,需要伴随一个加减法操作,对系统性能会有一定的影响。
因此:JVM并未选择此算法作为垃圾回收算法。
3.2标记清除
标记清除算法是现代垃圾回收算法的思想基础。分为两个阶段:标记阶段和清除阶段。标记清除算法产生最大的问题就是清除之后的空间碎片。
3.3复制算法
将原有内存空间分为两块。每次只使用其中一块内存,例如:A内存,GC时将存活的对象复制到B内存中。然后清除掉A内存所有对象。开始使用B内存。复制算法没有内存碎片,并且如果垃圾对象很多,那么这种算法效率很高。但是它的缺点是系统内存只能使用1/2。
3.4标记压缩
标记压缩算法是一种老年代的回收算法。
垃圾回收步骤:
① 标记存活的对象
② 将所有存活的对象压缩到内存的一端
③ 清理所有存活对象之外的空间。
该算法不会产生内存碎片,并且也不用将内存一分为二。因此,其性价比较高。
3.5分代算法
将堆空间划分为新生代和老年代,根据它们之间的不同特点,执行不同的回收算法,提升回收效率。
当前jvm的垃圾回收,都是采用分代收集算法的,针对新生代和老年代,他们对应的垃圾回收算法如下所示:
① 【新生代】由于大量对象消亡,少数存量对象,只需要复制少量对象,就可以完全清除S0/S1的垃圾对象空间,所以采用“复制算法”更为合适;
② 【老年代】对象存活率高,每次GC只清除少部分对象,所以采用“标记-清除”或“标记-压缩”算法来回收。
3.6分区算法
将堆空间划分成连续的不同小区间,每个区间独立使用、回收。由于当堆空间大时,一次GC的时间会非常耗时,那么可以控制每次回收多少个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。
总结
以上就是本次的分享,希望能给有需要的朋友帮助。