十、JVM调优

67 阅读5分钟

调优参数

设置垃圾回收器的参数

  • -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详细日志信息

image.png

这部分是GC正常日志显示

第一行:GC表示是MinorGC,如果是FullGC会用全称

第二行:Allocation Failure是发生GC的原因,表示新生代没有足够的空间了

第三行:4544K是GC前新生代被占用的空间,259K是GC后新生代被占用的空间,6144K是新生代总的空间大小,后面小数是本次GC花费的时间;4544K是GC前整个堆区被占用的空间,4356K是GC后整个堆区被占用的空间,说明有很多新生代对象移动到老年代了。

第四行:user后面的时间是指本次GC在用户态停留的时间,sys后面的时间是指本次GC在内核态停留的时间,real后面的时间是指本次GC花费的总时间

image.png

这部分是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占用过高

  1. 找出哪个进程占用cpu过高(top)
  2. 找出该进程中那个线程占用cpu过高(top -Hp)
  3. 导出该线程的堆栈信息(jstack)
  4. 根据堆栈信息,找出问题代码

内存占用过高

  1. 导出堆内存信息(jmap)
  2. 根据堆内存信息中的对象信息,找出占用大量空间的对象,定位到问题代码

如何监控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,但是对象回收不掉

定位命令

  1. 使用top命令查看哪个进程占用cpu和内存高
  2. [top -Hp 进程号] 这个命令查看进程中哪个线程占用的cpu和内存高
  3. [jstack 进程号> dump.txt] 这个命令将进程中的所有线程的堆栈信息写入dump.txt文件中
  4. [jmap -histo 进程号|head -20] 这个命令查看进程创建出来的数量在前20的对象

其实到这里,有了堆栈信息以及知道进程中那个对象占用的内存最多。

堆栈信息如下

image.png

堆栈信息中会有线程名称和线程号,pool-1-thread-48就是线程名称,nid就是线程号

top -Hp命令可以查出哪个线程占用的cpu和内存高,根据查出的线程号到堆栈信息中搜索,就可以找到对应线程的堆栈信息

根据线程的堆栈信息以及jmap命令显示的堆中占比最多的对象,基本可以定位问题代码

除了使用jstack命令导出堆栈信息外,在代码执行命令中,我们一般会加参数 -XX:+HeapDumpOnOutOfMemoryError,这个参数会让进程在出现内存溢出时,将堆栈信息导出存在文件中,方便后续定位