JVM垃圾回收整理
1. 如何计算对象已死
1.1 引用计数器算法
引用计数器算法是给每个对象设置一个计数器,当有地方引用这个对象的时候,计数器+1,当引用失效的时候,计数器-1,当计数器为0的时候,JVM就认为对象不再被使用,是"垃圾"了。
引用计数器实现简单,效率高;但是不能解决循环引用问问题(A对象引用B对象,B对象又引用A对象,但是A,B对象已不被任何其他对象引用),同时每次计数器的增加和减少都带来了很多额外的开销,所以在JDK1.1之后,这个算法已经不再使用了。
1.2 可达性分析算法
可达性分析算法是通过一些"GC Roots"对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链(Reference Chain),当一个对象没有被GC Roots的引用链连接的时候,说明这个对象是不可用的,如下图所示。
如上图种的对象C,此对象没有被GC Roots节点引用,就是可回收垃圾。
object5/6/7虽然内部引用,但是没有被GC Roots,也是垃圾。
那么,什么的东西才可以作为GC Roots?
GC Roots对象包括:虚拟机栈(栈帧中的本地变量表)中的引用的对象。 方法区域中的类静态属性引用的对象。 方法区域中常量引用的对象。 本地方法栈中JNI(Native方法)的引用的对象。
2.常见的垃圾回收算法
3种:复制;标记清除(Mark-Sweep);标记整理(Mark-Compact);
1.复制算法(年轻代)
复制算法是把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,然后把这块内存整个清理掉。这种方式听上去确实是非常不错的方案,但是总的来说对内存的消耗十分高。
复制之后有交换,谁空谁是To。 详细如下(复制->清空->互换):
a.Eden和From复,制到To,年龄+1
首先,当Eden区满时,会触发第一次gc,把还活着的对象,复制到from区;当Eden区再次发生gc时,会扫描Eden和From这两个区域,对这两个区域进行垃圾回收,把还活着的对象,直接复制到To区(如果有对象年龄达到老年标准,则复制到老年代),同时把对象的年龄+1。
b.清空Eden和From
然后,清空Eden和From种的对象;
c.To和From互换
最后,To和From互换,此时From为空,变为To区;原来的To,变为下一次gc时的From区;部分对象会在From和To之间来回复制,交换15次(由jvm参数MaxTenuringThreshold决定,这个参数默认值是15)之后,如果对象还存活,就存入到老年代。
2.标记清除(Mark-Sweep)(老年代)
标记—清除算法包括两个阶段:"标记"和"清除"。在标记阶段,确定所有要回收的对象,并做标记。清除阶段紧随标记阶段,将标记阶段确定不可用的对象清除。
优点:没有复制,节省空间;
缺点:产生内存碎片;
3.标记压缩整理(Mark-Compact, 或称为标记-整理算法,Java堆中老年代的垃圾回收算法)
优点:在标记清除的基础上,增加滑动,解决了碎片问题;
缺点:滑动(移动)对象需要成本。
4.分代收集算法
分代收集是根据对象的存活时间把内存分为新生代和老年代,根据个代对象的存活特点,每个代采用不同的垃圾回收算法。新生代采用标记—复制算法,老年代采用标记清除或者标记—整理算法。
垃圾算法的实现涉及大量的程序细节,而且不同的虚拟机平台实现的方法也各不相同。上面介绍的只不过是基本思想。 垃圾收集器有哪些?
上面是目前比较常用的垃圾收集器,和他们直接搭配使用的情况,上面是新生代收集器,下面则是老年代收集器,这些收集齐都有自己的特点,根据不同的业务场景进行搭配使用。
年轻代收集器如下
Serial收集器 (串行)
它针对单线程环境设计,且只使用一个线程进行垃圾回收,会暂停所有用户线程,所以不适合服务器环境。
形象解释:大家在餐厅用餐(用户线程),来了一位员工阿姨(垃圾回收线程),说我们要打扫卫生了,请大家先暂停用餐,打扫完再继续吃饭。
ParNew收集器
ParNew收集器其实就是serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为与Serial收集器一样。 使用方式可以使用 -XX:+UseConcMarkSweepGC ,或者是使用 -XX:+UseParNewGC 来强制开启,可以通过 -XX:ParallelGCThreads 来调整或者限制垃圾收集的线程数量。
Parallel Scavenge收集器(并行)
有多个垃圾收集线程并行工作,此时用户线程也是暂停的;适用于科学计算/大数据后台处理等,和前台若交互场景;
形象解释:大家在餐厅用餐(用户线程),来了 多个 员工阿姨(垃圾回收线程),说我们要打扫卫生了,请大家先暂停用餐,打扫完再继续吃饭。
Parallel Scavenge收集器也是一个并行的多线程新生代收集器,它也使用复制算法。Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughput)。
特点:
就是非常关注系统的吞吐量,吞吐量=代码运行时间/(代码运行时间+垃圾收集时间)
老年代垃圾回收器
Serial Old收集器
Serial Old 是 Serial收集器的老年代版本,它同样是一个单线程收集器,使用"标记-整理"(Mark-Compact)算法。
用途一个是在JDK1.5及之前的版本中与Parallel Scavenge收集器搭配使用, 另一个就是作为CMS收集器的后备预案,如果CMS出现Concurrent Mode Failure,则SerialOld将作为后备收集器。
Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和"标记-整理"算法。前面已经提到过,这个收集器是在JDK 1.6中才开始提供的,在此之前,如果新生代选择了Parallel Scavenge收集器。
老年代除了Serial Old以外别无选择,所以在Parallel Old诞生以后,"吞吐量优先"收集器终于有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它非常符合那些集中在互联网站或者B/S系统的服务端上的Java应用,这些应用都非常重视服务的响应速度。从名字上("Mark Sweep")就可以看出它是基于"标记-清除"算法实现的。
CMS收集器工作的整个流程分为以下4个步骤: 初始标记(CMS initial mark):仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要"Stop The World"。 并发标记(CMS concurrent mark):进行GC Roots Tracing的过程,在整个过程中耗时最长。 重新标记(CMS remark):为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。此阶段也需要"Stop The World"。 并发清除(CMS concurrent sweep)
优点
CMS是一款优秀的收集器,它的主要优点在名字上已经体现出来了:并发收集、低停顿,因此CMS收集器也被称为并发低停顿收集器(Concurrent Low Pause Collector)。
缺点 对CPU资源非常敏感 其实,面向并发设计的程序都对CPU资源比较敏感。在并发阶段,它虽然不会导致用户线程停顿,但会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。 CMS默认启动的回收线程数是(CPU数量+3)/4,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。但是当CPU不足4个时(比如2个),CMS对用户程序的影响就可能变得很大,如果本来CPU负载就比较大,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了50%,其实也让人无法接受。 无法处理浮动垃圾(Floating Garbage) 可能出现"Concurrent Mode Failure"失败而导致另一次Full GC的产生。 由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生。这一部分垃圾出现在标记过程之后,CMS无法再当次收集中处理掉它们,只好留待下一次GC时再清理掉。 这一部分垃圾就被称为"浮动垃圾"。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。 标记-清除算法导致的空间碎片 CMS是一款基于"标记-清除"算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。 空间碎片过多时,将会给大对象分配带来很大麻烦,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象。
G1收集器
①. G1(Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器, 以极高概率满足GC停顿时间的同时 ,还 兼具高吞吐量的性能特征
具体细节详见 https://juejin.cn/post/7010034105165299725JDK 8 默认使用的垃圾收集器
查看步骤:
cmd执行命令:
java -XX:+PrintCommandLineFlags -version
输出如下:
引用类型和垃圾回收
参考文章:
https://blog.csdn.net/csdn_20150804/article/details/96368802
https://wangkang007.gitbooks.io/jvm/content/chapter1.html
https://www.jianshu.com/p/a9703d82c901
https://blog.csdn.net/qq9808/article/details/80933396
https://cloud.tencent.com/developer/article/1592943