调优参数
设置垃圾回收器的参数
- -XX:+UseSerialGC:新生代使用Serial,老年代使用Serial Old
- -XX:+UseParNewGC:新生代使用ParNew,老年代使用Serial Old
- -XX:+UseConcMarkSweepGC:新生代使用ParNew,老年代使用CMS
- -XX:+UseParallelGC:新生代使用ParallelScavenge,老年代使用Parallel Old(1.8默认)
- -XX:+UseParallelOldGC:新生代使用ParallelScavenge,老年代使用Parallel Old(1.8默认)
- -XX:+UseG1GC:使用G1
常见调优参数
查看JVM所有参数的命令
java -XX:+PrintFlagsFinal -version
可以查看JVM所有的参数,大概会有几百个参数,最好与grep命令一起使用
设置新生代和老年代大小的参数
- -Xms:初始堆内存大小
- -Xmx:最大堆内存大小
- -Xmn:设置新生代大小
- -Xss:设置线程栈大小
- -XX:MetaSpaceSize:设置元空间大小
- -XX:SurvivorRatio:设置新生代中eden区与survivor区的比例
- -XX:NewRatio:设置新生代与老年代的比例
- -XX:MaxTenuringThreshold:设置进入老年代的阈值
- -XX:+PrintGCDetails:打印GC详细信息
GC详细日志信息
这部分是GC正常日志显示
第一行:GC表示是MinorGC,如果是FullGC会用全称
第二行:Allocation Failure是发生GC的原因,表示新生代没有足够的空间了
第三行:4544K是GC前新生代被占用的空间,259K是GC后新生代被占用的空间,6144K是新生代总的空间大小,后面小数是本次GC花费的时间;4544K是GC前整个堆区被占用的空间,4356K是GC后整个堆区被占用的空间,说明有很多新生代对象移动到老年代了。
第四行:user后面的时间是指本次GC在用户态停留的时间,sys后面的时间是指本次GC在内核态停留的时间,real后面的时间是指本次GC花费的总时间
这部分是GC过程中报错的日志显示
第一行:new generation表示新生代。total 6144K表示新生代中eden+一个survivor占用的空间,used 5504K表示新生代中对象占用的空间。后面三个地址,第一个表示新生代空间起始地址,第二个表示新生代空间被占用的空间终止地址,第三个表示新生代整个空间的末尾地址
第二行:eden space表示eden区。5504K表示eden区整个大小,后面不用解释了
第三行:from space表示survivor区。640K表示survivor区整个大小
第四行与第三行一样,是另一个survivor区信息
第五行:tenured generation表示老年代。total 13696K表示老年代整个空间大小,used 13312K表示被使用的空间大小
第六行:Metaspace表示方法区,后面是方法区的空间大小信息和使用信息
调优基本概念
吞吐量
吞吐量 = 用户代码执行时间/(用户代码执行时间 + 垃圾回收时间)
响应时间
垃圾回收过程中的stop the world时间越短,响应时间越好
对于要求吞吐量的场景,例如数据挖掘,数据分析等,垃圾回收器一般选择PS+PO
对于要求响应时间的场景,例如网站、页面等,垃圾回收器一般选择G1
调优的目的
- 根据需求进行JVM的规化和预调优
- 优化JVM的运行环境
- 解决JVM运行过程中出现的各种问题
JVM预调优
预调优是要根据业务场景,以及业务对吞吐量和响应时间的需求,选择合适的垃圾回收器。
通过业务的数据流量,估算出在高峰期,每秒大概的数据量,选择合适的内存和cpu
JVM运行时常遇到的问题
CPU占用过高
- 找出哪个进程占用cpu过高(top)
- 找出该进程中那个线程占用cpu过高(top -Hp)
- 导出该线程的堆栈信息(jstack)
- 根据堆栈信息,找出问题代码
内存占用过高
- 导出堆内存信息(jmap)
- 根据堆内存信息中的对象信息,找出占用大量空间的对象,定位到问题代码
如何监控JVM
- jstat命令
- jvisualvm
- jprofiler
- arthas
调优举例
问题代码
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 -Xms100M -Xmx100M -jar T15_FullGC_Problem01.jar
经过一段时间,堆内存会被对象占满,频繁触发FullGC,但是对象回收不掉
定位命令
- 使用top命令查看哪个进程占用cpu和内存高
- [top -Hp 进程号] 这个命令查看进程中哪个线程占用的cpu和内存高
- [jstack 进程号> dump.txt] 这个命令将进程中的所有线程的堆栈信息写入dump.txt文件中
- [jmap -histo 进程号|head -20] 这个命令查看进程创建出来的数量在前20的对象
其实到这里,有了堆栈信息以及知道进程中那个对象占用的内存最多。
堆栈信息如下
堆栈信息中会有线程名称和线程号,pool-1-thread-48就是线程名称,nid就是线程号
top -Hp命令可以查出哪个线程占用的cpu和内存高,根据查出的线程号到堆栈信息中搜索,就可以找到对应线程的堆栈信息
根据线程的堆栈信息以及jmap命令显示的堆中占比最多的对象,基本可以定位问题代码
除了使用jstack命令导出堆栈信息外,在代码执行命令中,我们一般会加参数 -XX:+HeapDumpOnOutOfMemoryError,这个参数会让进程在出现内存溢出时,将堆栈信息导出存在文件中,方便后续定位