GC和GC Tuning
GC和GC Tuning
GC的基础知识
什么是垃圾
C语言申请内存:malloc free
C++: new delete
c/C++ 手动回收内存
Java: new ?
自动内存回收,编程上简单,系统不容易出错,手动释放内存,容易出两种类型的问题:
- 忘记回收
- 多次回收
没有任何引用指向的一个对象或者多个对象(循环引用)
如何定位垃圾
- 引用计数(ReferenceCount)
- 根可达算法(RootSearching)
常见的垃圾回收算法
- 标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)
- 拷贝算法 (copying) - 没有碎片,浪费空间
- 标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)
JVM内存分代模型(用于分代垃圾回收算法)
- 部分垃圾回收器使用的模型
除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型
G1是逻辑分代,物理不分代
除此之外不仅逻辑分代,而且物理分代
- 新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace
- 永久代 元数据 - Class
- 永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存)
- 字符串常量 1.7 - 永久代,1.8 - 堆
- MethodArea逻辑概念 - 永久代、元数据
- 新生代 = Eden + 2个suvivor区
- YGC回收之后,大多数的对象会被回收,活着的进入s0
- 再次YGC,活着的对象eden + s0 -> s1
- 再次YGC,eden + s1 -> s0
- 年龄足够 -> 老年代 (15 CMS 6)
- s区装不下 -> 老年代
- 老年代
- 顽固分子
- 老年代满了FGC Full GC
- GC Tuning (Generation)
- 尽量减少FGC
- MinorGC = YGC
- MajorGC = FGC
- 对象分配过程图
- 动态年龄:(不重要)https://www.jianshu.com/p/989d3b06a49d
- 分配担保:(不重要)
YGC期间 survivor区空间不够了 空间担保直接进入老年代
参考:https://cloud.tencent.com/developer/article/1082730
5.常见的垃圾回收器
- 垃圾回收器的发展路线,是随着内存越来越大的过程而演进
从分代算法演化到不分代算法
Serial算法 几十兆
Parallel算法 几个G
CMS 几十个G - 承上启下,开始并发回收 -
.- 三色标记 - - JDK诞生 Serial追随 提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认是CMS
并发垃圾回收是因为无法忍受STW - Serial 年轻代 串行回收
- PS 年轻代 并行回收
- ParNew 年轻代 配合CMS的并行回收
- SerialOld
- ParallelOld
- ConcurrentMarkSweep 老年代 并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms)
CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定
CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收
想象一下:
PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW)
几十个G的内存,单线程回收 -> G1 + FGC 几十个G -> 上T内存的服务器 ZGC
算法:三色标记 + Incremental Update - G1(200ms - 10ms)
算法:三色标记 + SATB - ZGC (10ms - 1ms) PK C++
算法:ColoredPointers + LoadBarrier - Shenandoah
算法:ColoredPointers + WriteBarrier - Eplison
- PS 和 PN区别的延伸阅读:
▪https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73 - 垃圾收集器跟内存大小的关系
- Serial 几十兆
- PS 上百兆 - 几个G
- CMS - 20G
- G1 - 上百G
- ZGC - 4T - 16T(JDK13)
1.8默认的垃圾回收:PS + ParallelOld
常见垃圾回收器组合参数设定:(1.8)
- -XX:+UseSerialGC = Serial New (DefNew) + Serial Old
- 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
- -XX:+UseParNewGC = ParNew + SerialOld
- -XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
- -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
- -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
- -XX:+UseG1GC = G1
- Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC
- java +XX:+PrintCommandLineFlags -version
- 通过GC的日志来分辨
- Linux下1.8版本默认的垃圾回收器到底是什么?
- 1.8.0_181 默认(看不出来)Copy MarkCompact
- 1.8.0_222 默认 PS + PO
JVM调优第一步,了解JVM常用命令行参数
-
JVM的命令行参数参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
-
HotSpot参数分类
标准: - 开头,所有的HotSpot都支持
非标准:-X 开头,特定版本HotSpot支持特定命令
不稳定:-XX 开头,下个版本可能取消
java -version
java -X
java -XX:+PrintFlagsWithComments //只有debug版本能用
试验用程序:import java.util.List; import java.util.LinkedList; public class HelloGC { public static void main(String[] args) { System.out.println("HelloGC!"); List list = new LinkedList(); for(;;) { byte[] b = new byte[1024*1024]; list.add(b); } } }
- 区分概念:内存泄漏memory leak,内存溢出out of memory
- java -XX:+PrintCommandLineFlags HelloGC
- java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC
PrintGCDetails PrintGCTimeStamps PrintGCCauses - java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
- java -XX:+PrintFlagsInitial 默认参数值
- java -XX:+PrintFlagsFinal 最终参数值
- java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数
- java -XX:+PrintFlagsFinal -version |grep GC
- java -XX:+PrintFlagsFinal -version | wc -l
共728个参数
PS GC日志详解
每种垃圾回收器的日志格式是不同的!
PS日志格式
heap dump部分:
eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
后面的内存地址指的是,起始地址,使用空间结束地址,整体空间结束地址
total = eden + 1个survivor
调优前的基础概念:
- 吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间)
- 响应时间:STW越短,响应时间越好
所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量...
问题:
科学计算,吞吐量。数据挖掘,thrput。吞吐量优先的一般:(PS + PO)
响应时间:网站 GUI API (1.8 G1)
什么是调优?
- 根据需求进行JVM规划和预调优
- 优化运行JVM运行环境(慢,卡顿)
- 解决JVM运行过程中出现的各种问题(OOM)
调优,从规划开始
- 调优,从业务场景开始,没有业务场景的调优都是耍流氓
- 无监控(压力测试,能看到结果),不调优
- 步骤:
- 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
- 响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)
- 吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]
- 选择回收器组合
- 计算内存需求(经验值 1.5G 16G)
- 选定CPU(越高越好)
- 设定年代大小、升级年龄
- 设定日志参数
- -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
- 或者每天产生一个日志文件
- 观察日志情况
- 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
- 案例1:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?
这个问题比较业余,因为很多不同的服务器配置都能支撑(1.5G 16G)
1小时360000集中时间段, 100个订单/秒,(找一小时内的高峰期,1000订单/秒)
经验值,
非要计算:一个订单产生需要多少内存?512K * 1000 500M内存
专业一点儿问法:要求响应时间100ms
压测!
- 案例2:12306遭遇春节大规模抢票应该如何支撑?
12306应该是中国并发量最大的秒杀网站:
号称并发量100W最高
CDN -> LVS -> NGINX -> 业务系统 -> 每台机器1W并发(10K问题) 100台机器
普通电商订单 -> 下单 ->订单系统(IO)减库存 ->等待用户付款
12306的一种可能的模型: 下单 -> 减库存 和 订单(redis kafka) 同时异步进行 ->等付款
减库存最后还会把压力压到一台服务器
可以做分布式本地库存 + 单独服务器做库存均衡
大流量的处理方法:分而治之
- 怎么得到一个事务会消耗多少内存?
- 弄台机器,看能承受多少TPS?是不是达到目标?扩容或调优,让它达到
- 用压测来确定
优化环境
- 有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G
的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G
的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了- 为什么原网站慢?
很多用户浏览数据,很多数据load到内存,内存不足,频繁GC,STW长,响应时间变慢 - 为什么会更卡顿?
内存越大,FGC时间越长 - 咋办?
PS -> PN + CMS 或者 G1
- 为什么原网站慢?
- 系统CPU经常100%,如何调优?(面试高频)
CPU100%那么一定有线程在占用系统资源,- 找出哪个进程cpu高(top)
- 该进程中的哪个线程cpu高(top -Hp)
- 导出该线程的堆栈 (jstack)
- 查找哪个方法(栈帧)消耗时间 (jstack)
- 工作线程占比高 | 垃圾回收线程占比高
- 系统内存飙高,如何查找问题?(面试高频)
- 导出堆内存 (jmap)
- 分析 (jhat jvisualvm mat jprofiler ... )
- 如何监控JVM
- jstat jvisualvm jprofiler arthas top...
解决JVM运行中的问题
一个案例理解常用工具
- 测试代码:
package com.mashibing.jvm.gc; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 从数据库中读取信用数据,套用模型,并把结果进行记录和传输 */ public class T15_FullGC_Problem01 { private static class CardInfo { BigDecimal price = new BigDecimal(0.0); String name = "张三"; int age = 5; Date birthdate = new Date(); public void m() {} } private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50, new ThreadPoolExecutor.DiscardOldestPolicy()); public static void main(String[] args) throws Exception { executor.setMaximumPoolSize(50); for (;;){ modelFit(); Thread.sleep(100); } } private static void modelFit(){ List<CardInfo> taskList = getAllCardInfo(); taskList.forEach(info -> { // do something executor.scheduleWithFixedDelay(() -> { //do sth with info info.m(); }, 2, 3, TimeUnit.SECONDS); }); } private static List<CardInfo> getAllCardInfo(){ List<CardInfo> taskList = new ArrayList<>(); for (int i = 0; i < 100; i++) { CardInfo ci = new CardInfo(); taskList.add(ci); } return taskList; } }
- java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01
- 一般是运维团队首先受到报警信息(CPU Memory)
- top命令观察到问题:内存不断增长 CPU占用率居高不下
- top -Hp 观察进程中的线程,哪个线程CPU和内存占比高
- jps定位具体java进程
jstack 定位线程状况,重点关注:WAITING BLOCKED
eg.
waiting on <0x0000000088ca3310> (a java.lang.Object)
假如有一个进程中100个线程,很多线程都在waiting on<xx>
,一定要找到是哪个线程持有这把锁
怎么找?搜索jstack dump的信息,找<xx>
,看哪个线程持有这把锁RUNNABLE
作业:1:写一个死锁程序,用jstack观察 2 :写一个程序,一个线程持有锁不释放,其他线程等待 - 为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称
怎么样自定义线程池里的线程名称?(自定义ThreadFactory) - jinfo pid
- jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用)
jstat -gc 4655 500 : 每个500个毫秒打印GC的情况
如果面试官问你是怎么定位OOM问题的?如果你回答用图形界面(错误)
1:已经上线的系统不用图形界面用什么?(cmdline arthas)
2:图形界面到底用在什么地方?测试!测试的时候进行监控!(压测观察) - jmap - histo 4655 | head -20,查找有多少对象产生
- jmap -dump:format=b,file=xxx pid :
线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)
1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件(不是很专业,因为多有监控,内存增长就会报警)
2:很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
3:在线定位(一般小点儿公司用不到)
4:在测试环境中压测(产生类似内存增长问题,在堆还不是很大的时候进行转储) - java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01
- 使用MAT / jhat /jvisualvm 进行dump文件分析https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
jhat -J-mx512M xxx.dumphttp://192.168.17.11:7000
拉到最后:找到对应链接
可以使用OQL查找特定问题对象 - 找到代码的问题
jconsole远程连接
- 程序启动加入参数:
java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX
- 如果遭遇 Local host name unknown:XXX的错误,修改/etc/hosts文件,把XXX加入进去
192.168.17.11 basic localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
- 关闭linux防火墙(实战中应该打开对应端口)
service iptables stop chkconfig iptables off #永久关闭
- windows上打开 jconsole远程连接 192.168.17.11:11111
jvisualvm远程连接
https://www.cnblogs.com/liugh/p/7620336.html (简单做法)
jprofiler (收费)
arthas在线排查工具
- 为什么需要在线排查?
在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。 - jvm观察jvm信息
- thread定位线程问题
- dashboard 观察系统情况
- heapdump + jhat分析
- jad反编译
动态代理生成类的问题定位
第三方的类(观察代码)
版本问题(确定自己最新提交的版本是不是被使用) - redefine 热替换
目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性
m() -> mm() - sc - search class
- watch - watch method
- 没有包含的功能:jmap
jcmd
跟踪堆外内存的问题
jprofiler
https://www.cnblogs.com/duanxz/p/5500494.html
GC算法的基础概念
- Card Table
由于做YGC时,需要扫描整个OLD区,效率非常低,所以JVM设计了CardTable, 如果一个OLD区CardTable中有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card
在结构上,Card Table用BitMap来实现
CMS
CMS的问题
- Memory Fragmentation
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩 - Floating Garbage
Concurrent Mode Failure
产生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped解决方案:降低触发CMS的阈值
PromotionFailed
解决方案类似,保持老年代有足够的空间
–XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让CMS保持老年代足够的空间
CMS日志分析
执行命令:java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC com.mashibing.jvm.gc.T15_FullGC_Problem01
[GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
ParNew:年轻代收集器
6144->640:收集前后的对比
(6144):整个年轻代容量
6585 -> 2770:整个堆的情况
(19840):整个堆大小
[GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
//8511 (13696) : 老年代使用(最大)
//9866 (19840) : 整个堆使用(最大)
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
//这里的时间意义不大,因为是并发执行
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//标记Card为Dirty,也称为Card Marking
[GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//STW阶段,YG occupancy:年轻代占用及容量
//[Rescan (parallel):STW下的存活对象标记
//weak refs processing: 弱引用处理
//class unloading: 卸载用不到的class
//scrub symbol(string) table:
//cleaning up symbol and string tables which hold class-level metadata and
//internalized string respectively
//CMS-remark: 8511K(13696K): 阶段过后的老年代占用及容量
//10108K(19840K): 阶段过后的堆占用及容量
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
//标记已经完成,进行并发清理
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//重置内部结构,为下次GC做准备
G1
G1日志详解
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
//young -> 年轻代 Evacuation-> 复制存活对象
//initial-mark 混合回收的阶段,这里是YGC混合老年代回收
[Parallel Time: 1.5 ms, GC Workers: 1] //一个GC线程
[GC Worker Start (ms): 92635.7]
[Ext Root Scanning (ms): 1.1]
[Update RS (ms): 0.0]
[Processed Buffers: 1]
[Scan RS (ms): 0.0]
[Code Root Scanning (ms): 0.0]
[Object Copy (ms): 0.1]
[Termination (ms): 0.0]
[Termination Attempts: 1]
[GC Worker Other (ms): 0.0]
[GC Worker Total (ms): 1.2]
[GC Worker End (ms): 92636.9]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.0 ms]
[Other: 0.1 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.0 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.0 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
//以下是混合回收其他阶段
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0000078 secs]
[GC concurrent-mark-start]
//无法evacuation,进行FGC
[Full GC (Allocation Failure) 18M->18M(20M), 0.0719656 secs]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38
76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]
g1的三色标记解决方案SATB源码跟踪
putfileld 汇编码解析
bytecodeintepreter.cpp -> CASE(_putfield) -> 2157行 -> obj_field_put(...)
oop.inline.hpp obj_field_put -> oop_stroe
oop.inline.hpp -- oop_stroe -- update_barrier_set_pre -- update -- update_barrier_set(...)
//G1SATBCardTableModRefBS.cpp
void G1SATBCardTableModRefBS::enqueue(oop pre_val) {
// Nulls should have been already filtered.
assert(pre_val->is_oop(true), "Error");
if (!JavaThread::satb_mark_queue_set().is_active()) return;
Thread* thr = Thread::current();
if (thr->is_Java_thread()) {
JavaThread* jt = (JavaThread*)thr;
jt->satb_mark_queue().enqueue(pre_val);
} else {
MutexLockerEx x(Shared_SATB_Q_lock, Mutex::_no_safepoint_check_flag);
JavaThread::satb_mark_queue_set().shared_satb_queue()->enqueue(pre_val); //引用入队
}
}
ZGC
zgc支持的系统:
启用zgc
-XX:+UseZGC 启用ZGC
-XX:+UseZGC -Xmx -Xlog:gc
-XX:+UseZGC -Xmx -Xlog:gc* 可以打印更详细的GC日志
zgc通用参数
-XX:MinHeapSize, -Xms 最小堆大小 (default = 8388608 = 8M)
-XX:InitialHeapSize, -Xms 初始化堆大小 (default = 134217728 = 128M )
-XX:MaxHeapSize, -Xmx 最大堆大小 (default = 2134900736 = 2036M)
-XX:SoftMaxHeapSize JVM堆的最大软限制 (default = 2134900736 = 2036M)
-XX:ConcGCThreads 并发GC的线程数量(default -XX:+ConcGCThreads=1 )
-XX:ParallelGCThreads 设置垃圾回收时的并行GC线程数量 (default = 4 )
-XX:UseLargePages 使用大页面内存 (dafault false)
-XX:UseTransparentHugePages 使用Transparent大页面内存
-XX:UseNUMA 使用UNMA内存分配,可以获得更好的性能
-XX:SoftRefLRUPolicyMSPerMB 每MB的空闲内存空间允许软引用对象存活时间(default = 1000)
-XX:AllocateHeapAt = 堆分配参数,可以使用非DRAM 内存,这个参数将指向文件系统的文件并使用内存映射来达到在备用存储设备上进行堆分配。
zgc特有参数
-XX:ZAllocationSpikeTolerance 修正系数,数值越大,越早触发GC (default = 2.000000)
-XX:ZCollectionInterval ZGC发生的最小时间间隔 ,秒 (default = 0.000000)
-XX:ZFragmentationLimit relocation时,当前region碎片化大于此值,则回收region (default = 25.000000)
-XX:ZMarkStackSpaceLimit 指定为标记堆栈分配的最大字节数 (default = 8589934592 = 8096M)
-XX:ZProactive 是否启用主动回收 (default true)
-XX:ZUncommit 是否归还不使用的内存给OS(default true)
-XX:ZUncommitDelay 不再使用的内存最多延迟多久会归还给OS (default = 300 s)
zgc诊断参数
-XX:+UnlockDiagnosticVMOptions 使用诊断模式,下面的参数才会起作用
-XX:ZStatisticsInterval 指定统计数据输出之间的时间间隔(秒)。
-XX:ZVerifyForwarding 检验转发表
-XX:ZVerifyMarking 检验标记集
-XX:ZVerifyObjects 检验对象
-XX:ZVerifyRoots 检验根节点
-XX:ZVerifyViews 检验堆视图访问
-------------------------------------------------
可优化参数详解
2.4.1 堆参数的设置
ZGC比较重要的调优参数是设置最大堆内存(-Xmx<size>
)., ZGC设置一个最大堆内存有两个考量:1.堆可以容纳程序的存活集 2.堆中要有足够的空间允许GC运行时分配.
然而多大内存合适这就要根据 内存使用情况以及GC频率来确定。
2.4.2 并发GC线程数设置
另一个调优参数就是并发GC线程数量的设置 (-XX:ConcGCThreads=<number>
) 。
一般情况下ZGC可以自动选择一个合适的值,但是也要根据程序的特点来修改。这个参数对GC使用CPU的时间影响比较大,如果数量太大的话,GC线程会占用过多的CPU时间,数量过少的话垃圾回收有会不及时。通常情况下如果程序的低延迟很重要,那么CPU使用率最好永远不要超过70%。
2.4.3 将不用的内存返回给OS
默认情况下,ZGC会将不使用的内存还给OS。可以使用XX:-ZUncommit 取消这一功能。然而如果实际堆内存比最小堆内存都小的话,肯定不会将不使用的内存返还给OS,另外最大堆内存(-Xmx)和最小堆内存 (-Xms) 的设置相等的话,也不会起作用。
另外可以设置延迟归还对内存的时间 -XX:ZUncommitDelay=
2.4.4 在Linux中开启大页支持
在Linux操作系统中,采用内存映射来管理内存,逻辑页面映射到对应的物理内存,
使用大页面这个参数在吞吐量,低延迟,启动时间方面都有很好的性能提升,可以说除了设置起来有点复杂之外,没有任何的缺点。 设置这个参数需要Root权限,所以默认没有开启。
2.4.5 Linux中使用大透明页
Linux下的大页分为两种标准,大页(Huge Pages)和透明大页(Transparent Huge Pages)。大页是预分配的方式管理,透明大页是动态分配的方式。我们可以使用这个参数-XX:+UseTransparentHugePages 来使用 transparent huge pages .对延迟要求很高的程序而言,这个参数是不推荐使用的,它可能会造成不必要的延迟峰值。
2.4.6 配置NUMA的支持
NUMA(非一致性内存访问)是Intel为了解决SMP多CPU结构中中线访问方式中资源竞争和一致性问题引起的。ZGC支持NUMA,此特性是默认开启的,它可以自主判断系统是否支持NUMA。如果在支持NUMA的机器上运行,那么将有显著的性能提升。
3 ZGC垃圾回收流程简述
3.1 ZGC同G1类似也将内存分为Regions, 叫做ZPages。 ZPages可以动态的创建和销毁,而且还可以动态的变化大小,无论怎么变化均为2MB 的倍数。
ZGC的堆区可以分为Small(2MB) Medium(32MB) Large(N*2MB)等多个Heap Regions. Medium和Large是连续分配的内存。
ZGC的垃圾回收一般可以分为已下几个步骤流程。
Pause Mark Start
这个阶段会有STW,主要对Root集进行扫描,标记Root集中存活对象,即标记根可达对象。
Concurrent Mark/Remap
并发标记阶段,根据上一步标记的根对象去遍历对象图,并标记存活对象,主要根据上一步的Root存活对象标记其对象树上的对象。
Pause Mark End
标记结束,此处有STW,算再标记阶段,由于之前标记的时候程序也在运行,此时会有对象引用的变更,此处会对那些变更引用的对象重新标记一下。此时也会清理一些Root对象以及无效空间。
Concurrent Prepare for Relocate
并发标记Region及处理弱根对象 ,标记需要整理的Region集合,并选择下一步Relocation要用到的Region集合,也会处理不是强根的对象。
Pause Relocate Start
开始Relocate,此处有STW,处理Root对象的指针,此时会将Roots的对象移动到选好的Region集合中。
Concurrent Relocate
并发Relocate,此时并发执行,将存活对象移动到上一步选好的新的Region集合中。然后就是清理被释放的分区,等等工作。
可以看出来ZGC整体使用了标记复制加整理的思想,不过ZGC可以根据当前的内存使用情况,选择将存活对象整理到一个全新的空Region集合中或者某个存有对象的Region中。由于整个GC的流程中只有三处需要暂停,然而这三处的STW时间都是亚毫秒级别。整个GC的流程延迟总体会小于1ms。
————————————————
版权声明:本文为CSDN博主「JavaTechNorth」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/JavaTechNorth/article/details/119789093
-------------------------------------------------
案例汇总
OOM产生的原因多种多样,有些程序未必产生OOM,不断FGC(CPU飙高,但内存回收特别少) (上面案例)
硬件升级系统反而卡顿的问题(见上)
线程池不当运用产生OOM问题(见上)
不断的往List里加对象(实在太LOW)
smile jira问题
实际系统不断重启
解决问题 加内存 + 更换垃圾回收器 G1
真正问题在哪儿?不知道
tomcat http-header-size过大问题(Hector)
lambda表达式导致方法区溢出问题(MethodArea / Perm Metaspace)
LambdaGC.java -XX:MaxMetaspaceSize=9M -XX:+PrintGCDetails
"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -XX:MaxMetaspaceSize=9M -XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.1\lib\idea_rt.jar=49316:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\work\ijprojects\JVM\out\production\JVM;C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectSize.jar" com.mashibing.jvm.gc.LambdaGC
[GC (Metadata GC Threshold) [PSYoungGen: 11341K->1880K(38400K)] 11341K->1888K(125952K), 0.0022190 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Metadata GC Threshold) [PSYoungGen: 1880K->0K(38400K)] [ParOldGen: 8K->1777K(35328K)] 1888K->1777K(73728K), [Metaspace: 8164K->8164K(1056768K)], 0.0100681 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] 1777K->1777K(73728K), 0.0005698 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] [ParOldGen: 1777K->1629K(67584K)] 1777K->1629K