GC类型
对象在young区的eden创建,当eden空间满后触发Minor GC,将还存活的对象复制到一个survivor0中,另一个survivor1也会将对象复制过去,然后对eden和survivor1进行全部清理,survivor0和survivor1就这样不断交替,总有一个是空着的,当对象放不下或者是对象年龄足够老(默认15)会将其放入Old区,由此可见Minor GC时不但会清理对象,还会将对象放入Old区。
可以通过设置-XX:MaxTenuringThreshold=n来指定对象经过多少次Minor GC后就进入Old 区,默认15
可以通过设置-XX:SurvivorRatio=n设置Survivor区和eden区的比例,如-XX:SurvivorRatio=8 那么比例就是8:1:1 ,没有设置的话就以-XX:InitialSurvivorRatio=n为默认设置,这个值默认是8
Full gc:
full gc是对新生代,老年代、永久久代的统一回收,由于是对整个空间的回收,并且会触发系统的停顿(stop-the-world),因此应当尽量的减少系统full gc的次数。
触发的full gc的几个条件
在jdk8中叫做元空间,使用的是计算机的本地内存通过-XX:MaxMetaspaceSize=n设置大小,如果不设置,默认最大内存大小是计算机的本地内存,由于运行时常量池在方法区中,而永久代又是方法区的实现,所以运行时常量池随着jdk8也移动到了本地内存,但是无论是jdk7还是jdk8,字符串常量池还是在堆中,字符串常量的创建是需要消耗堆内存的
1、promotion failed意思是晋升失败是由于新生代把一些对象往老年代扔,然后老年代空间不足则抛出“promotion failed”,触发full gc,可能的原因是:Survivor空间过小或者老年代空间小或者碎片多,或者两者同时发生
2、concurrent mode failure是CMS设置启动的老年代内存占比阈值过高,所以导致系统无法预留足够的空间满足程序需求,就会出现concurrent mode failure,启动担保机制,老年代增长过快触发full gc进行清理,解决方法是降低触发CMS的阀值,使用-XX:CMSInitiatingOccupancyFraction调低阈值,默认值是68,可以调到50
原因有几个:1、代码问题大量大对象直接进入老年代 2、老年代空间不足,通过-XX:NewRatio=n可以调整老年代和年轻的堆比例
GC日志
通过设置系统参数:-XX:+PrintGCDetails可以GC日志打印到控制台,以下几个命令可以设置GC日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式) -XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2020-10-23T21:53:59.234+0800) -XX:+PrintGCDetails 打印出GC的详细信息 -verbose:gc 开启gc日志 开启这个按钮后就算是使用了+PrintGCDetails也无法在控制台看到,因为打印的内容会到日志文件中 -Xloggc:d:/gc.log gc日志的存放位置
实战:
image.png
运行这段代码,由于堆内存被这些常量占满,马上就会触发gc了
public class Tzb { public static void main(String[] args) { while (true){ String str=System.currentTimeMillis()+ UUID.randomUUID().toString(); str.intern(); } }}
打印了下列的内容,我把它们一部分复制出来
image.png
-XX:InitialHeapSize=1048576 -XX:MaxHeapSize=1048576 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC ---------------------------------------------------------------------------------------------------------------------------------[GC (Allocation Failure) [PSYoungGen: 505K->488K(1024K)] 505K->488K(1536K), 0.0006483 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] ---------------------------------------------------------------------------------------------------------------------------------[Full GC (Ergonomics) [PSYoungGen: 488K->441K(1024K)] [ParOldGen: 400K->318K(512K)] 888K->759K(1536K), [Metaspace: 4010K->4010K(1056768K)], 0.0033631 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
上面的有三部分我用虚线标出来了行中
1、PSYoungGen:新生代GC区域的信息,这个新生代区域的名字会跟着收集器的不同而改变,ParNew收集器叫做ParNew,Serial叫做DefNew,Parallel Scavenge叫做PSYoungGen,由于我使用的是Parallel Scavenge收集器所以新生代显示为PSYoungGen
2、[PSYoungGen:488K->441K(1024K)]:“GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域可用总容量,这里Eden+Survivor0,另外一个由于是进行复制用的,所以不计入容量)”
3、505K->488K(1536K), 0.0006483 secs:“GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量),“0.0006483 secs”表示该内存区域GC所占用的时间,单位是秒”
4、[Times: user=0.00 sys=0.00, real=0.00 secs]:这里面的user、sys和real与Linux的time命令所输出的时间含义一致,分别代表用户态消耗的CPU时间、内核态消耗的CPU事件和操作从开始到结束所经过的墙钟时间(Wall Clock Time)。CPU时间与墙钟时间的区别是,墙钟时间包括各种非运算的等待耗时,例如等待磁盘I/O、等待线程阻塞,而CPU时间不包括这些耗时,但当系统有多CPU或者多核的话,多线程操作会叠加这些CPU时间,所以读者看到user或sys时间超过real时间是完全正常的
触发full gc会严重影响程序的运行性能,因为发生了牛批的stop-the-world
1、PSYoungGen:新生代GC区域发生了full gc
2、ParOldGen:发生老年代的区域,由于收集器不同老年代的区域名称也会不同,Serial old叫做Tenured,Parallel Old叫做ParOldGen,由于我使用的是Parallel Scavenge收集器会自动激活Parallel Old收集器所以老年代叫做ParOldGen
3、Metaspace:元空间,jdk7叫做Perm也就是方法区也成为永久代,jdk8后把方法区移除了,变成了元空间也就是这个Metaspace
下面列出gc日志中各个新生代收集器默认配合的老年代收集器及各自对应的新生代区域和老年代区域的名字:
这是目前官方推荐的配合主流
image.png
控制台这玩意在线上能看?试一下生产日志文件?
image.png
image.png
打开后长这样:
image.png
GC收集器
Serial
Serial是串行新生代收集器,client的默认收集器,Serial Old是老年代的Serial,这两个特点除了工作区域与算法不一样其他没啥区别,一般适用于小型应用和单处理器,单核GC效率较高,但是会发生Stop-The-World,可以与CMS,Serial Old使用,由于没有线程开销,所以在单核情况下性能无敌(现在没什么服务器是单核了吧!!)
Serial会发生Stop-The-World
ParNew(jdk9已经不支持)
是Serial的多线程版,本并行收集器,除了多线程与Serial没什么区别,这两款是共用了绝大部分的代码,所以特点几乎没什么区别,一样会发生Stop-The-World。许多Server选它只是为了和CMS配合而已。(如何不是为了配合CMS最好不要用这个收集器)
依然会发生Stop-The-World
Parallel Scavenge
并行收集器,这个收集器比较重要,是jdk7和jdk8 Server端默认的新生代收集器,Parallel Scavenge的特点是多线程,追求高吞吐量(吞吐量就是假如系统运行100分钟,收集器工作1分钟,吞吐量为99%),它有自适应调节的功能,通过收集系统的信息动态调节自身的参数,达到高吞吐的目的,吞吐高代表CUP使用率高,优点很明显但是缺点也很明显,它无法与老年代的CMS适配,同样是高性能的CMS追求的是最短的停顿时间,鱼和熊掌不可兼得,jvm还是选择了Parallel Scavenge作为默认Server的收集器,为了配合它的高吞吐,jvm还专门弄了一个ParNew Old,它是ParNew的老年版本。(这么牛批但是依然会发生Stop-The-World)
CMS(jdk9已经不建议使用,被G1取代)
这个比较牛批的老年代收集器,使用的是标记清除法,由于追求的是最短停顿时间,所以可以带给用户良好的体验,非常适合互联 和B/S的服务器。
一般老年代和永久代的回收是需要触发full gc的,但是CMS可以在不触发full gc的情况下单独对老年代和永久代进行gc,但是它的gc是需要检查永久代和老年代空间使用率默认是达到92%才启动,还要通过-XX:+
CMSInitiatingOccupancyFraction=n设置,gc有7个步骤
1、初始标记(STW initial mark),会Stop-The-World
2、并发标记(Concurrent marking),并发操作,不会Stop-The-World
3、并发预清理(Concurrent precleaning),并发操作,不会Stop-The-World
5、重新标记(Final remark),会Stop-The-World
6、并发清理(Concurrent sweeping),并发操作,不会Stop-The-World
7、并发重置(Concurrent reset),并发操作,不会Stop-The-World
我们看到只是标记的部分都是会发生Stop-The-World的,但是标记的操作是非常短的,可以忽略不计的,所以CMS能做到最短的停顿时间,效率极高,但是缺点就是无法与Parallel Scavenge适配,CMS启动时会同时默认启动ParNew、Serial Old,为了防止SMC的回收失败,使用了CMS+ParNew+Serial Old的担保机制
担保机制:
由于CMS为了降低停顿,有一定的并发,启动时需要预留足够的内存给用户线程,所以CMS需要在内存空间满之前就得启动,jdk5默认是当老年代65%时启动,而jdk6以后是默认92%,还要通过-XX:+
CMSInitiatingOccupancyFraction设置更高的启百分比,如果垃圾生产的太快了或者阈值太高,在CMS运行期间发现无法预留足够的内存,就会出现concurrent mode failure而运行异常,这时JVM就会启动备用的Serial Old去处理老年代垃圾,所以CMS+ParNew+Serial Old的担保机制是为了防止CMS的concurrent mode failure的异常
初始标记和重新标记会Stop-The-World
CMS示例, 如图:我没有选择任何的新生代收集器,只是开启了CMS
image.png
我已经圈出来了,可以只要开启CMS就会自动选择ParNew
image.png
我将CMS的一部分复制下来
[GC (CMS Initial Mark) [1 CMS-initial-mark: 723K(768K)] 742K(1920K), 0.0002026 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] ---------------------------------------------------------------------------------------------------------------------------------[CMS-concurrent-mark-start][CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] ---------------------------------------------------------------------------------------------------------------------------------[CMS-concurrent-preclean-start][CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] ---------------------------------------------------------------------------------------------------------------------------------[GC (CMS Final Remark) [YG occupancy: 75 K (1152 K)][Rescan (parallel) , 0.0001481 secs][weak refs processing, 0.0000063 secs][class unloading, 0.0001892 secs][scrub symbol table, 0.0003164 secs][scrub string table, 0.0000845 secs]---------------------------------------------------------------------------------------------------------------------------------[1 CMS-remark: 723K(768K)] 799K(1920K), 0.0007957 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] ---------------------------------------------------------------------------------------------------------------------------------[CMS-concurrent-sweep-start][CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] ---------------------------------------------------------------------------------------------------------------------------------[CMS-concurrent-reset-start][CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] ---------------------------------------------------------------------------------------------------------------------------------
一共有7部分,对应着上面列出的CMS的7个步骤
G1
G1收集器是JDK9默认的收集器,jdk7和jdk8中都可以用它,到了jdk9才正式成为默认,G1完全取代了CMS,针对CMS的碎片化进行了改进,使用G1不再需要与其他的收集器配合,原因是在于G1对之前的内存结构做了非常大的改动,不再把新生代和老年代分别分配成一整块一整块的大区域,而是把之前的老年代和新生代的Eden,survivior分割成一小块一小块的Region放在同一片堆区域,每块Region最大是32M,堆空间最多存放2048个Region,就是最多是60G到70G,G1基于复制算法,高效的整理剩余内存,而不需要管理内存碎片,
G1有以下的特点:
G1的收集算法也比较独特,采用了分代并且分区的算法,分代就是根据不同的块的特点,例如新生代的块就会使用复制算法,老年代的块使用标记压缩,并且每个块都分配一个remember set集合记录所属块和其他块的对象引用关系,通过检查这个集合找出垃圾对象,避免了扫描整个确定可达关系。
一般来说,空间越大扫描的范围就越大,停顿时间就长,但是进行分块可以减少每次扫描整个堆空间的时间
内存结构如图,其中Humongous是大对象,大对象直接进入Humongous的Region
同时还有一个并发标记过程记录的G区,G区是垃圾对象比率较高的区域,混合回收会优先清理的区域
image.png
G1的GC模式两种:
G1的工作流程主要分两个阶段:并发标记和混合GC
1、并发标记:
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0013482 secs]
[GC concurrent-root-region-scan-start][GC concurrent-root-region-scan-end, 0.0000189 secs][Ext Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.7]
[GC concurrent-mark-start]
[Update RS (ms): Min: 0.0, Avg: 0.3, Max: 0.4, Diff: 0.4, Sum: 1.7] [Processed Buffers: Min: 0, Avg: 0.8, Max: 1, Diff: 1, Sum: 5] [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
2、混合清理:
并发标记完成后,G1会根据设置的停顿时间,优先选择清理性价比高的区域,也就是G区
G1的流程
full gc
和CMS一样,G1也是个并发的回收器,当不足以提供足够内存给用户程序时同样会触发full gc
[GC concurrent-mark-start][Full GC (Allocation Failure) 1712K->690K(2048K), 0.0023713 secs] [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 1712.1K(2048.0K)->690.3K(2048.0K)], [Metaspace: 3469K->3469K(1056768K)] [Times: user=0.02 sys=0.00, real=0.00 secs] [GC concurrent-mark-abort]
G1收集器演示:
image.png
G1真的比较复杂,算法和机制都比较难,日志多了很多
image.png
GC篇总结:
声明:本站部分文章内容及图片转载于互联 、内容不代表本站观点,如有内容涉及侵权,请您立即联系本站处理,非常感谢!