目录
❝
四种垃圾回收算法,七种垃圾收集器
❞
上一篇讲了垃圾回收,回收哪,回收谁,如何断定对象已死的两种办法(引用计数法和可达性分析算法),以及讲明了 Java 四种引用类型根据 JVM 内存不同情况的回收JVM垃圾回收(上):面试必问,白话文讲解,小白请进,这篇就来重点讲一下怎么回收的问题!
垃圾回收算法周志明老师的《深入理解Java虚拟机》是很棒的入门资料,我在写 JVM 的时候也参考了这本书,但是在写这一篇的时候我发现了有个错误,并且查阅了大量谷歌和维基百科资料,证实确实说法有问题,而且国内很多博客都是沿用了这种错误的说法,估计都是沿用这本书。
❝
周志明老师在《深入理解Java虚拟机》第2版书中,69页写的是: 首先标记出所有要回收的对象,在标记完成后统一回收所有被标记的对象。
❞
对于垃圾回收器老说,对象就分为要回收的和不需要回收的,这里周志明老师说的是标记所有要回收的对象, 但其实真正标记的是不需要回收的对象。
标记清除算法(Mark Sweep)标记-清除算法,第一个GC算法,可以说是最古老的一种GC算法了,于1960年由 Lisp之父 John McCarthy 在其论文中发布。顾名思义,标记-清除算法分为两个阶段:
可达性分析算法:此算法主要用来判断对象是否存活,一个堆就是一块连续的内存空间,一个堆有一个根root,从这个根出发有很多个引用链,所有能到达root的 Object 对象都是可达对象,即还在被引用的存活的,不需要被垃圾回收;不能到达root 的对象如 Object5,7,8 是不可达对象,是需要被垃圾回收器回收的死亡对象,如下图:
Root Set
Mark
Sweep
标记清除算法标记清除算法
每隔一段时间或者在堆空间不足的时候才进行一次垃圾回收,每次垃圾回收先将所有堆上分配的内存单元标记为“不可到达”,然后从一组根引用开始扫描,把所有从根引用出发可以达到的单元标记为“可以到达”,其他的不可达的就是垃圾,这样就区分出来了。
缺点:
对于清除来说,只要知道哪些要回收哪些不要回收就可以,所以貌似标记哪个都可以,但是这都是建立在可以分开的前提下。拿 JVM来说,有了可达性分析算法,需要和不需要回收的对象是可以分开的,但是最初 标记清除算法 出来的时候都还没有 JVM,连JAVA语言都不存在。
因此剥离开 JVM 分析这个算法的时候,疑惑有三:
上面的算法是鼻祖,后面的垃圾回收算法都是为了对上面的进行优化
复制算法
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
优点:
缺点:
标记整理算法
标记-整理(Mark-Compact)算法,标记过程 仍然与“标记-清除”算法一样,但后续步骤不 是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,Compact就是压缩的意思,然后直接清理掉端边界以外的内存。
分代收集算法分代思想来源目前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法是根据对象存活周期的不同将内存划分为几块,然后每一块根据此块内对象的存亡时间特点不同,选择上面的三种算法的某一种来发挥算法的优点。
下面图关系是一个博士做的测试,得到的结论:即大部分的 Java 对象只存活一小段时间,而存活下来的小部分 Java 对象则会存活很长一段时间。
Java 对象生命周期的直方图
正式因为这个特点,才造就了 Java 虚拟机的分代回收思想。简单来说,就是将堆空间划分为两代,分别叫做新生代和老年代。新生代用来存储新建的对象。当对象存活时间够长时,则将其移动到老年代。
分代收集年轻代红色的代表存活的对象,From和To都为survivor区。
绝大多数刚创建的对象会被分配在Eden区,其中的绝大多数对象很快就会消亡。Eden区是连续的内存空间,因此在其上分配内存极快;当Eden区满的时候,执行Minor GC。
如图,将年轻代内存分为一块较大的 eden(西方伊甸园代表生命降生) 空间和两块较小的 survivor(生存) 空间,默认比例是 8:1:1,即每次新生代中可用内存空间为整个新生代容量的 90%,每次使用 eden 和其中一个 survivour。当回收时,将 eden 和 survivor 中还存活的对象一次性复制到另外一块 survivor 上,最后清理掉 eden 和刚才用过的 survivor,若另外一块 survivor 空间没有足够内存空间存放上次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。
年老代年轻代可看出用的是“复制算法”,老年代的对象存活的比较多,死亡的比较少,使用标记整理算法。
永久代很多人都把方法区称为永久代,本质上两者不等价。
Java虚拟机规范中不要求对方法区进行垃圾收集,因为这个区域的收集性价比很低。但是在很多大量使用反射,动态代理,CGLIB等 ByteCode框架,以及动态生成JSP,以及OSGI这类频繁定义ClassLoder的地方,方法区的运行时数据区在运行时疯狂的增加内容,没有垃圾回收器很容易发生OOM。
因此HotPot设计团队设计了永久代的垃圾回收,这个说法是建立在HotPot虚拟机的,其设计团队把GC分代收集扩展到了方法区,也即是用永久代来实现方法区以至于垃圾收集器可以像管理堆一样管理方法区内存,对其他的虚拟机来说是不存在永久代的概念的。
永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点:
image
没有直接设置老年代的参数,但是可以设置堆空间大小和新生代空间大小两个参数来间接控制。
老年代空间大小=堆空间大小-年轻代大空间大小
内存分配与回收策略图中上面的是年轻代的垃圾收集器,下面的是老年代的垃圾收集器,两个收集器之间有连线,说明它们可以配合使用。
没有最好的垃圾收集器,只有最合适的,存在即合理。
Serial 收集器(复制算法)单线程(单线程的意义不仅仅说明它会使用一个 cpu或一条垃圾收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集的时候,必须暂停其他所有工作线程即“stop the world”,直到他收集结束)。
“stop the world”是垃圾收集器在后台自己发起和结束的,在用户不可见偷偷的把用户的线程停掉,如果运行一小时就要停下五分钟进行收集,这是很痛苦的,因此缩短这个停顿时间是一代一代垃圾收集器努力的方向。
image
开启参数: -XX: UseSerialGC
适用场景:用户的桌面应用场景(这种收集器简单,桌面应用分配给虚拟机管理的内存一般几十兆到几百兆,收集停顿时间也就是几十毫秒上百毫秒,所以用这个停顿时间是完全可以接受的)
ParNew 收集器(复制算法)Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、 收集算法、 Stop The World、 对象分配规则、 回收策略等都与Serial收集器完全一样。
开启参数:-XX: UseParNewGC
适用场景: 在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与老年代的CMS收集器配合工作;但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
Parallel Scvenge收集器(复制算法)其他与ParNew类似,特别之处在于:CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间;而其目标是达到一个可控制的吞吐量,适合在后台运算,没有太多的交互。
Parallel Scavenge关注点:可控的吞吐量
开启参数:-XX: UseParallelGC
适用场景: 后台计算不需要太多交互,例如,那些执行批量处理、订单处理、工资支付、科学计算的应用程序。
Parallel Scavenge提供了两个参数用于精确控制吞吐量:
从这里开始就是老年代垃圾收集器。
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。这个收集器的主要意义也是在于给 Client 模式下的虚拟机使用,即用户的桌面应用场景。
Parallel Old 收集器(标记-整理算法)Parallel Old 收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器从 JDK 1.6中才开始提供的,用来代替老年代的Serial Old收集器。
适用场景:特别是在Server模式,多CPU的情况下;这样在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge加Parallel Old收集器的"给力"应用组合。
Concurrent Mark Sweep 收集器(CMS)CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于 “标记—清除”算法实现的。整个过程分为4个步骤,
优点:并发收集,低停顿;
缺点:
开启参数:-XX: UseConcMarkSweepGC
适用场景:互联网站或者WEB服务端
G1 收集器周志明-深入了解Java虚拟机
看上面两段介绍。
开启参数:-XX: UseG1GC
适用场景:服务端应用
G1 收集器
我司JVM参数#jvm参数设置
RUNJVM_ARGS="-server-Dfile.encoding=UTF-8-Dsun.jnu.encoding=UTF-8-Djava.io.tmpdir=/tmp-Djava.net.preferIPv6Addresses=false"&&\
JVM_GC="-Xloggc:/tmp/gc.log-XX: DisableExplicitGC-XX: PrintGCDetails-XX: PrintHeapAtGC-XX: PrintTenuringDistribution-XX: UseConcMarkSweepGC-XX: PrintGCTimeStamps-XX: PrintGCDateStamps"&&\
JVM_GC=$JVM_GC"-XX:CMSFullGCsBeforeCompaction=0-XX: UseCMSCompactAtFullCollection-XX:CMSInitiatingOccupancyFraction=80"&&\
JVM_HEAP="-XX:SurvivorRatio=8-XX: HeapDumpOnOutOfMemoryError-XX:ReservedCodeCacheSize=128m-XX:InitialCodeCacheSize=128m"
ENTRYPOINTjava-Djetty.http.port=8080\
${JVM_ARGS}\
${JVM_GC}\
${JVM_HEAP}\
-Dspring.profiles.active=${PROFILE}\
-Xmx${JVM_XMX}\
-Xms${JVM_XMS}\
-jar/root/${MODULE_NAME}.jar
我公司用的是CMS收集器。
-XX:SurvivorRatio=8表示 两个Survivor : Eden = 2: 8 ,每个Survivor占 1/10;
有收获的朋友点个“在看”否?欢迎关注我,一起成长~
关注我,一起成长~
参考资料: 周志明《深入了解Java虚拟机》, 谷歌,维基百科
往期推荐: JVM垃圾回收(上):面试必问,白话文讲解,小白请进
Java跨平台根本原因,面试必问JVM内存模型白话文详解来了
不好意思,你可能连new对象实际在做什么都不知道!
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved