GC 和 GC Tuning
GC 的基础知识
1.什么是垃圾
C 语言申请内存:malloc free
C++: new delete
c/C++ 手动回收内存
Java: new ?
自动内存回收,编程上简单,系统不容易出错,手动释放内存,容易出两种类型的问题:
- 忘记回收
- 多次回收
没有任何引用指向的一个对象或者多个对象(循环引用)
2.如何定位垃圾
- 引用计数(ReferenceCount)
- 根可达算法(RootSearching)
3.常见的垃圾回收算法
- 标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)
- 拷贝算法 (copying) - 没有碎片,浪费空间
- 标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)
4.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.常见的垃圾回收器
- 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(10ms)
算法:三色标记 + SATB - ZGC (1ms) PK C++
算法:ColoredPointers + LoadBarrier - Shenandoah
算法:ColoredPointers + WriteBarrier - Eplison
- PS 和 PN 区别的延伸阅读:点击
- 垃圾收集器跟内存大小的关系
- 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:+UseConcMarkSweepGC
= 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 的命令行参数参考
-
HotSpot 参数分类
标准: - 开头,所有的 HotSpot 都支持
非标准:-X 开头,特定版本 HotSpot 支持特定命令
不稳定:-XX 开头,下个版本可能取消
java -version
java -X
试验用程序:
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
PS GC 日志详解
每种垃圾回收器的日志格式是不同的!
PS 日志格式
heap dump 部分:
eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
后面的内存地址指的是,起始地址,使用空间结束地址,整体空间结束地址
total = eden + 1 个 survivor(原因是 survivor 是复制算法,实际可用空间就只能是 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.test.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 TestFullGCProblem01 {
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.test.jvm.gc.TestFullGCProblem01
- 一般是运维团队首先收到报警信息(CPU Memory)
top
命令观察到问题:内存不断增长 CPU 占用率居高不下top -Hp
观察进程中的线程,哪个线程 CPU 和内存占比高jps
定位具体 java 进程
jstack
定位线程状况,重点关注:WAITING BLOCKED
eg.
waiting on <0x0000000088ca3310> (a java.lang.Object)
假如有一个进程中 100 个线程,很多线程都在 waiting on ,一定要找到是哪个线程持有这把锁
怎么找?搜索 jstack dump 的信息,找 ,看哪个线程持有这把锁 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:在线定位(一般小点儿公司用不到)java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.test.jvm.gc.TestFullGCProblem01
- 使用 MAT / jhat /jvisualvm 进行 dump 文件分析:https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
jhat -J-mx512M xxx.dump
http://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 classwatch
- watch method- 没有包含的功能:jmap
GC 算法的基础概念
Card Table
由于做 YGC 时,需要扫描整个 OLD 区(原因是 old 区可能由指向 young 区的东西),效率非常低,所以 JVM 设计了 CardTable,如果一个 OLD 区 CardTable 中有对象指向 Y 区,就将它设为 Dirty,下次扫描时,只需要扫描 Dirty Card。在结构上,Card Table 用 BitMap 来实现。
并发标记算法
难点:在标记对象过程中,对象引用关系正在发生改变
三色标记法
- 白色:未被标记的对象
- 灰色:自身被标记,成员变量未被标记
- 黑色:自身和成员变量均已标记完成
漏标(2 种情况):
漏标是指,本来时 live object,但是由于没有遍历到,被当成垃圾回收了。
- 在 remakr 过程中,黑色指向了白色,如果不对黑色重新扫描,则会漏标,会把白色对象当做没有新引用指向从而回收掉。
- 并发标记过程中,删除了所有从灰色到白色的引用,会产生漏标,此时白色对象应该被回收。
怎么解决(2 个方式):
- incremental update:增量更新,关注引用的增加,把黑色重新标记为灰色,下次重新扫描属性。(CMS 使用了这种)
- SATB(snapshot at the beginning):关注引用的删除,当指向消失时,要把这个引用推到 GC 的堆栈,保证白色还能被 GC 扫描到。(G1 使用了这种方式)
CMS
简介
- Concurrent Mark Sweep
- a mostly concurrent, low-pause collector
- 4 phases
- initial mark
- concurrent mark
- remark
- concurrent sweep
初始标记
- 这个过程会发生 STW,但是由于找的对象会比较少,因此 STW 时间会很短
并发标记
- 这个过程不会发送 STW
重新标记
- 为了解决并发标记过程中,某些对象的引用不存在了,所以需要重新标记
- 这个过程会发生 STW
并发清理
- 并发清理的过程还是可能会产生垃圾,这部分称为浮动垃圾,会在下次 GC 的时候清理
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.test.jvm.gc.TestFullGCProblem01
[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 暂停时间,同时还能保持较高的吞吐量。
- 与 PS 相比较,G1 大约比其低 10%~15% 的吞吐量,但是维持响应时间在 200ms。
- G1 使用分治的思想来管理内存,把内存分割成一个一个的 Region。每个 Region 可能是某一个代,例如 old、survivor、tenured、Humongous。每个区域不是固定的。
- 特点
- 并发收集
- 压缩空闲空间不会延长 GC 的暂停时间
- 更易预测的 GC 暂停时间
- 适用不需要实现很高吞吐量的场景
- 基本概念
- CSet = CollectionSet
一组可被回收的分区的集合。在 CSet 中存活的数据会在 GC 过程中被移动到另一个可用分区,CSet 中的分区可以来自 Eden 空间、Survivor 空间或者老年代。CSet 会占用不到整个堆空间的 1% 大小。 - RSet = RememberedSet
** 记录了其他 region 中的对象到本 region 的引用。其价值在于:垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描 RSet 即可。RSet 是维护在每个 Region 中的。
** 由于 RSet 的存在,那么每次给对象赋引用的时候,就得做一些额外的操作,指的时在 RSet 中做一些额外的记录(在 GC 中被称为写屏障)。
- CSet = CollectionSet
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]
案例汇总
OOM 产生的原因多种多样,有些程序未必产生 OOM,不断 FGC(CPU 飙高,但内存回收特别少) (上面案例)
-
硬件升级系统反而卡顿的问题(见上)
-
线程池不当运用产生 OOM 问题(见上)
不断的往 List 里加对象(实在太 LOW) -
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(105984K), [Metaspace: 8164K->8156K(1056768K)], 0.0124299 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:388) at sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(InstrumentationImpl.java:411) Caused by: java.lang.OutOfMemoryError: Compressed class space at sun.misc.Unsafe.defineClass(Native Method) at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:63) at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:399) at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:394) at java.security.AccessController.doPrivileged(Native Method) at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:393) at sun.reflect.MethodAccessorGenerator.generateSerializationConstructor(MethodAccessorGenerator.java:112) at sun.reflect.ReflectionFactory.generateConstructor(ReflectionFactory.java:398) at sun.reflect.ReflectionFactory.newConstructorForSerialization(ReflectionFactory.java:360) at java.io.ObjectStreamClass.getSerializableConstructor(ObjectStreamClass.java:1574) at java.io.ObjectStreamClass.access$1500(ObjectStreamClass.java:79) at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:519) at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:494) at java.security.AccessController.doPrivileged(Native Method) at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:494) at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:391) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1134) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at javax.management.remote.rmi.RMIConnectorServer.encodeJRMPStub(RMIConnectorServer.java:727) at javax.management.remote.rmi.RMIConnectorServer.encodeStub(RMIConnectorServer.java:719) at javax.management.remote.rmi.RMIConnectorServer.encodeStubInAddress(RMIConnectorServer.java:690) at javax.management.remote.rmi.RMIConnectorServer.start(RMIConnectorServer.java:439) at sun.management.jmxremote.ConnectorBootstrap.startLocalConnectorServer(ConnectorBootstrap.java:550) at sun.management.Agent.startLocalManagementAgent(Agent.java:137)
-
直接内存溢出问题(少见)
《深入理解 Java 虚拟机》P59,使用 Unsafe 分配直接内存,或者使用 NIO 的问题 -
栈溢出问题
-Xss 设定太小 -
比较一下这两段程序的异同,分析哪一个是更优的写法:
Object o = null; for(int i=0; i<100; i++) { o = new Object(); //业务处理 }
for(int i=0; i<100; i++) { Object o = new Object(); }
-
重写 finalize 引发频繁 GC
小米云,HBase 同步系统,系统通过 nginx 访问超时报警,最后排查,C++ 程序员重写 finalize 引发频繁 GC 问题
为什么 C++ 程序员会重写 finalize?(new delete)
finalize 耗时比较长(200ms) -
如果有一个系统,内存一直消耗不超过 10%,但是观察 GC 日志,发现 FGC 总是频繁产生,会是什么引起的?
System.gc() (这个比较 Low) -
Distuptor 有个可以设置链的长度,如果过大,然后对象大,消费完不主动释放,会溢出 (来自 死物风情)
-
用 jvm 都会溢出,mycat 用崩过,1.6.5 某个临时版本解析 sql 子查询算法有问题,9 个 exists 的联合 sql 就导致生成几百万的对象(来自 死物风情)
-
new 大量线程,会产生 native thread OOM,(low)应该用线程池,
解决方案:减少堆空间(太 TMlow 了),预留更多内存产生 native thread
JVM 内存占物理内存比例 50% - 80%
GC 参数
GC 常用参数
-Xmn -Xms -Xmx -Xss
年轻代 最小堆 最大堆 栈空间-XX:+UseTLAB
使用 TLAB,默认打开-XX:+PrintTLAB
打印 TLAB 的使用情况-XX:TLABSize
设置 TLAB 大小-XX:+DisableExplictGC
System.gc()不管用 ,FGC-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintHeapAtGC
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationConcurrentTime
(低)
打印应用程序时间-XX:+PrintGCApplicationStoppedTime
(低)
打印暂停时长-XX:+PrintReferenceGC
(重要性低)
记录回收了多少种不同引用类型的引用-verbose:class
类加载详细过程-XX:+PrintVMOptions
-XX:+PrintFlagsFinal
和-XX:+PrintFlagsInitial
必须会用-Xloggc:opt/log/gc.log
-XX:MaxTenuringThreshold
升代年龄,最大值 15- 锁自旋次数
-XX:PreBlockSpin
热点代码检测参数-XX:CompileThreshold
逃逸分析 标量替换 ...
这些不建议设置
Parallel 常用参数
-XX:SurvivorRatio
-XX:PreTenureSizeThreshold
大对象到底多大-XX:MaxTenuringThreshold
-XX:+ParallelGCThreads
并行收集器的线程数,同样适用于 CMS,一般设为和 CPU 核数相同-XX:+UseAdaptiveSizePolicy
自动选择各区大小比例
CMS 常用参数
-XX:+UseConcMarkSweepGC
-XX:ParallelCMSThreads
CMS 线程数量-XX:CMSInitiatingOccupancyFraction
使用多少比例的老年代后开始 CMS 收集,默认是 68%(近似值),如果频繁发生 SerialOld 卡顿,应该调小,(频繁 CMS 回收)-XX:+UseCMSCompactAtFullCollection
在 FGC 时进行压缩-XX:CMSFullGCsBeforeCompaction
多少次 FGC 之后进行压缩-XX:+CMSClassUnloadingEnabled
-XX:CMSInitiatingPermOccupancyFraction
达到什么比例时进行 Perm 回收GCTimeRatio
设置 GC 时间占用程序运行时间的百分比-XX:MaxGCPauseMillis
停顿时间,是一个建议时间,GC 会尝试用各种手段达到这个时间,比如减小年轻代
G1 常用参数
-XX:+UseG1GC
-XX:MaxGCPauseMillis
建议值,G1 会尝试调整 Young 区的块数来达到这个值-XX:GCPauseIntervalMillis
?GC 的间隔时间-XX:+G1HeapRegionSize
分区大小,建议逐渐增大该值,1 2 4 8 16 32。
随着 size 增加,垃圾的存活时间更长,GC 间隔更长,但每次 GC 的时间也会更长
ZGC 做了改进(动态区块大小)G1NewSizePercent
新生代最小比例,默认为 5%G1MaxNewSizePercent
新生代最大比例,默认为 60%GCTimeRatio
GC 时间建议比例,G1 会根据这个值调整堆空间ConcGCThreads
线程数量InitiatingHeapOccupancyPercent
启动 G1 的堆空间占用比例
参考资料
- https://blogs.oracle.com/jonthecollector/our-collectors
- https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
- http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp
- JVM 调优参考文档:https://docs.oracle.com/en/java/javase/13/gctuning/introduction-garbage-collection-tuning.html#GUID-8A443184-7E07-4B71-9777-4F12947C8184
- https://www.cnblogs.com/nxlhero/p/11660854.html 在线排查工具
- https://www.jianshu.com/p/507f7e0cc3a3 arthas 常用命令
- Arthas 手册:
- 启动 arthas java -jar arthas-boot.jar
- 绑定 java 进程
- dashboard 命令观察系统整体情况
- help 查看帮助
- help xx 查看具体命令帮助
- jmap 命令参考: https://www.jianshu.com/p/507f7e0cc3a3
- jmap -heap pid
- jmap -histo pid
- jmap -clstats pid
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于