JVM调优工具详解及调优实战

Jmap

此命令可以用来查看内存信息,实例个数以及占用内存大小

jmap -histo 9104  #查看历史生成的实例
jmap -histo:live 9104  #查看当前存活的实例,执行过程中可能会触发一次full gc
# num:序号
# instances:实例数量
# bytes:占用空间大小
# class name:类名称,[C is a char[],[S is a short[],[I is a int[],[B is a byte[],[[I is a int[][]
# 堆信息
jmap -heap 9104

# 堆内存dump
jmap -dump:format=b,file=eureka.hprof 14660
# 也可以设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)
# -XX:+HeapDumpOnOutOfMemoryError
# -XX:HeapDumpPath=./   (路径)

可以用jvisualvm命令工具导入该dump文件分析

垃圾回收统计

jstat -gc pid 最常用,可以评估程序内存使用及GC压力整体情况

λ jstat -gc 14192
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
10752.0 10752.0 1112.0  0.0   130048.0 55805.7   173568.0     16.0    4864.0 3371.9 512.0  365.8       4    0.004   0      0.000    0.004
  • S0C:第一个幸存区的大小,单位KB
  • S1C:第二个幸存区的大小
  • S0U:第一个幸存区的使用大小
  • S1U:第二个幸存区的使用大小
  • EC:伊甸园区的大小
  • EU:伊甸园区的使用大小
  • OC:老年代大小
  • OU:老年代使用大小
  • MC:方法区大小(元空间)
  • MU:方法区使用大小
  • CCSC:压缩类空间大小
  • CCSU:压缩类空间使用大小
  • YGC:年轻代垃圾回收次数
  • YGCT:年轻代垃圾回收消耗时间,单位s
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间,单位s
  • GCT:垃圾回收消耗总时间,单位s

堆内存统计

jstat -gccapacity 13988

λ jstat -gccapacity 14192
 NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC       MCMN     MCMX      MC     CCSMN    CCSMX     CCSC    YGC    FGC
 86528.0 1384960.0 151552.0 10752.0 10752.0 130048.0   173568.0  2770432.0   173568.0   173568.0      0.0 1056768.0   4864.0      0.0 1048576.0    512.0      4     0
  • NGCMN:新生代最小容量
  • NGCMX:新生代最大容量
  • NGC:当前新生代容量
  • S0C:第一个幸存区大小
  • S1C:第二个幸存区的大小
  • EC:伊甸园区的大小
  • OGCMN:老年代最小容量
  • OGCMX:老年代最大容量
  • OGC:当前老年代大小
  • OC:当前老年代大小
  • MCMN:最小元数据容量
  • MCMX:最大元数据容量
  • MC:当前元数据空间大小
  • CCSMN:最小压缩类空间大小
  • CCSMX:最大压缩类空间大小
  • CCSC:当前压缩类空间大小
  • YGC:年轻代gc次数
  • FGC:老年代GC次数

系统频繁Full GC导致系统卡顿是怎么回事

  • 机器配置:2核4G
  • JVM内存大小:2G
  • 系统运行时间:7天
  • 期间发生的Full GC次数和耗时:500多次,200多秒
  • 期间发生的Young GC次数和耗时:1万多次,500多秒

大致算下来每天会发生70多次Full GC,平均每小时3次,每次Full GC在400毫秒左右;

每天会发生1000多次Young GC,每分钟会发生1次,每次Young GC在50毫秒左右。

JVM参数设置如下:

-Xms1536M -Xmx1536M -Xmn512M -Xss256K -XX:SurvivorRatio=6  -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M 
-XX:+UseParNewGC  -XX:+UseConcMarkSweepGC  -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly 

可以结合对象挪动到老年代那些规则推理下我们这个程序可能存在的一些问题

经过分析感觉可能会由于对象动态年龄判断机制导致full gc较为频繁

对于对象动态年龄判断机制导致的full gc较为频繁可以先试着优化下JVM参数,把年轻代适当调大点:

-Xms1536M -Xmx1536M -Xmn1024M -Xss256K -XX:SurvivorRatio=6  -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M 
-XX:+UseParNewGC  -XX:+UseConcMarkSweepGC  -XX:CMSInitiatingOccupancyFraction=92 -XX:+UseCMSInitiatingOccupancyOnly 

优化完发现没什么变化,full gc的次数比minor gc的次数还多了

我们可以推测下full gc比minor gc还多的原因有哪些?

1、元空间不够导致的多余full gc

2、老年代空间分配担保机制

查到了有大量User对象产生,这个可能是问题所在,但不确定,还必须找到对应的代码确认,如何去找对应的代码了?

1、代码里全文搜索生成User对象的地方(适合只有少数几处地方的情况)

2、如果生成User对象的地方太多,无法定位具体代码,我们可以同时分析下占用cpu较高的线程,一般有大量对象不断产生,对应的方法代码肯定会被频繁调用,占用的cpu必然较高

可以用上面讲过的jstack或jvisualvm来定位cpu使用较高的代码,最终定位到的代码如下:

@RestController
public class IndexController {

    @RequestMapping("/user/process")
    public String processUserData() throws InterruptedException {
        ArrayList<User> users = queryUsers();

        for (User user: users) {
            //TODO 业务处理
            System.out.println("user:" + user.toString());
        }
        return "end";
    }

    /**
     * 模拟批量查询用户场景
     * @return
     */
    private ArrayList<User> queryUsers() {
        ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i < 5000; i++) {
            users.add(new User(i,"zhuge"));
        }
        return users;
    }
}

同时,java的代码也是需要优化的,一次查询出500M的对象出来,明显不合适,要根据之前说的各种原则尽量优化到合适的值,尽量消除这种朝生夕死的对象导致的full gc


hhhhh