文章目录
一、了解JVM参数
JVM参数类型:
-
- 标准参,所有JVM都应该支持
- -X 非标,每个JVM实现不同
- -XX 不稳定参数,下个版本可能取消
常见垃圾回收器组合参数设定:(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
GC常用参数
-
-Xmn -Xms -Xmx -Xss
年轻代 最小堆 最大堆 栈空间 -
-XX:+UseTLAB
使用TLAB,默认打开,(一般不需要调整) -
-XX:+PrintTLAB
打印TLAB的使用情况,(一般不需要调整) -
-XX:TLABSize
设置TLAB大小,(一般不需要调整) -
-XX:+DisableExplictGC,要默认打开,禁用手动频繁调用gc
System.gc()不管用 ,FGC -
-XX:+PrintGC
打印出GC信息 -
-XX:+PrintGCDetails
打印出GC详细信息 -
-XX:+PrintGCCauses
打印GC和产生的原因 -
-XX:+PrintHeapAtGC
-
-XX:+PrintGCTimeStamps
打印GC详细时间 -
-XX:+PrintGCApplicationConcurrentTime (低)
打印应用程序时间 -
-XX:+PrintGCApplicationStoppedTime (低)
打印暂停时长 -
-XX:+PrintReferenceGC (重要性低)
记录回收了多少种不同引用类型的引用 -
-verbose:class
类加载详细过程 -
-XX:+PrintVMOptions
打印运行时参数 -
-XX:+PrintFlagsFinal -XX:+PrintFlagsInitial
必须会用,打印参数

-
-Xloggc:opt/log/gc.log
设置gc文件 -
-XX:MaxTenuringThreshold
升代年龄,最大值15,cms默认6,其他默认15 -
锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 …
这些不建议设置
Parallel常用参数
- -XX:SurvivorRatio
- -XX:PreTenureSizeThreshold
大对象到底多大 - -XX:MaxTenuringThreshold
指定对象经过多少次垃圾回收进入老年代,cms默认6,其他默认15。补充:动态年龄指年轻代占用空间超Survivor的一半时会把年龄最大的放入老年代。 - -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的堆空间占用比例 - -XX:+InitiatingHeapOccupacyPercent
默认值45%,当O超过这个值时,启动MixedGC,垃圾回收算法和cms一样
二、调优
1、调优前的基础概念:
- 吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间)
- 响应时间:STW越短,响应时间越好
所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量…
吞吐量优先场景:科学计算、数据挖掘。垃圾回收器一般:(PS + PO)
响应时间优先场景:网站 GUI API 。垃圾回收器一般:(1.8 G1)
2、什么是调优?
- 根据需求进行JVM规划和预调优
- 优化运行JVM运行环境(慢,卡顿)
- 解决JVM运行过程中出现的各种问题(OOM)
3、调优,从规划开始
步骤:
-
熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
-
选择回收器组合
-
计算内存需求(经验值 1.5G 16G)
一般只能算个大概,然后要经过压测 -
选定CPU(越高越好)
-
设定年代大小、升级年龄
-
设定日志参数
eg:
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause解释:-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log:日志文件,一般是系统名+时间;UseGCLogFileRotation:循环使用;XX:NumberOfGCLogFiles有多少个日志文件;XX:GCLogFileSize:每个日志文件大小。可以每天产生一个日志文件。
-
观察日志情况
三、日志详解
每种垃圾回收器的日志格式是不同的!
ps日志

用这个参数运行HelloGC
java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGCDetails HelloGC
查看打印日志


其中heap dump部分:
eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
后面的内存地址指的是,起始地址,使用空间结束地址,整体空间结束地址

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
[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]
四、案例分析
需要调优的情况一般是运维团队首先受到报警信息(CPU Memory)
案例1
试验用程序:
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
(相关参数含义查看上文)

GC默认就是YGC,清理年轻代
(打印信息没截完)
-
java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC


(打印信息没能截完)用UseConcMarkSweepGC 垃圾回收次数会多很多
-
java -XX:+PrintFlagsInitial 默认参数值
-
java -XX:+PrintFlagsFinal 最终参数值
-
java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数
-
java -XX:+PrintFlagsFinal -version |grep GC
案例2
1、代码
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;
}
}
2、运行程序:java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01
3、出现报警信息后,top命令观察到问题:内存不断增长 CPU占用率居高不下

(环境问题,假如Java进程出问题了,且pid是16222)
4、top -Hp 观察进程中的线程,哪个线程CPU和内存占比高

5、jps定位具体java进程
6、jstack 定位线程状况,重点关注:WAITING BLOCKED
这里Java进程有问题,且pid是1591(环境一致的话上面top命令能查出占用资源多的pid是1591)

waiting on <0x0000000088ca3310> (a java.lang.Object)
说明出现锁等待了
这里就能说明为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称,有意义的名称能快速定位问题。
6、jinfo pid 打印进程的相关信息

(没截全)
7、jstat -gc 动态观察gc情况
jstat -gc 4655 500 : 每个500个毫秒打印GC的情况

阅读GC日志发现频繁GC,查看GC dump日志工具:
arthas观察 / jconsole/jvisualVM/ Jprofiler(收费,最好用)
8、jmap - histo 4655 | head -20,查找有多少对象产生,查出前20个(面试时可以说用这个)

9、jmap -dump:format=b,file=xxx pid :(面试时不要说用这个)
线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)
1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件
2:很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
3:在线定位(一般小点儿公司用不到)
除了使用jmap命令,还可以通过以下方式:
- 使用 jconsole 选项通过 HotSpotDiagnosticMXBean 从运行时获得堆转储(生成dump文件)
- 虚拟机启动时如果指定了 -XX:+HeapDumpOnOutOfMemoryError 选项, 则在抛出OutOfMemoryError 时, 会自动执行堆转储。如:
java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01 - 使用 hprof 命令
10、获取到dump文件之后可以使用MAT / jhat /jvisualvm 进行dump文件分析,参考这博客:https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
使用 jhat -J-mx512M xxx.dump 解析后,访问 http://ip:7000,访问分析dump文件,
可以使用OQL查找特定问题对象
11、找到代码的问题
3060




被折叠的 条评论
为什么被折叠?



