JVM系列-05常用指令与可视化调优工具

1.常用指令

1.1jps

  • jps 是(java process Status Tool), Java版的ps命令,查看java进程及其相关的信息

1.2jinfo【了解】

  1. jinfo是用来查看JVM参数和动态修改部分JVM参数的命令
  2. 格式
jinfo [option] <pid>

options参数解释:

  • no options 输出所有的系统属性和参数
  • -flag 打印指定名称的参数
  • -flag [+|-] 打开或关闭参数
  • -flag = 设置参数
  • -flags 打印所有参数
  • -sysprops 打印系统配置
  1. 例子

package com.lcuyp.jvm.g_tool;

import java.io.IOException;

/**
* jinfo是用来查看JVM参数和动态修改部分JVM参数的命令
*
* jinfo [option]  <pid>
* options参数解释:
* - no options 输出所有的系统属性和参数
* - -flag <name> 打印指定名称的参数
* - -flag [+|-]<name> 打开或关闭参数
* - -flag <name>=<value> 设置参数
* - -flags 打印所有参数
* - -sysprops 打印系统配置
*/
public class Demo2_jinfo {

    public static void main(String[] args) {
        try {
            System.out.println("jinfo 指令");
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

   查看JVM参数和系统配置  :

jinfo 11666
jinfo -flags 11666
jinfo -sysprops 11666

   查看打印GC日志参数

jinfo -flag PrintGC 11666
jinfo -flag PrintGCDetails 11666

   打开GC日志参数

jinfo -flag +PrintGC 11666
jinfo -flag +PrintGCDetails 11666

   关闭GC日志参数

jinfo -flag -PrintGC 11666
jinfo -flag -PrintGCDetails 11666

1.3jstat【重点】

  1. jstat命令是使用频率比较高的命令,主要用来查看JVM运行时的状态信息,包括内存状态、垃圾回收等 。
  2. 命令格式
    其中VMID是进程id,interval是打印间隔时间(毫秒),count是打印次数(默认一直打印)
jstat [option] pid [interval] [count]

option参数解释:

  • -class classloader的行为统计
  • -compiler HotSptJIT编译器行为统计
  • -gc 垃圾回收堆的行为统计
  • -gccapacity 各个垃圾回收代容量(young,old,perm)和他们相应的空间统计
  • -gcutil 垃圾回收统计概述,各个区间的容量使用占比
  • -gccause 垃圾收集统计概述(同-gcutil),附加最近两次垃圾回收事件的原因
  • -gcnew 新生代行为统计
  • -gcnewcapacity 新生代与其相应的内存空间的统计
  • -gcold 年老代和永生代行为统计
  • -gcoldcapacity 年老代行为统计
  • -printcompilation HotSpot编译方法统计

  1. 例子一: 每隔1s查看垃圾回收堆信息    jstat -gcutil 11666 1000 3

package com.lcuyp.jvm.g_tool;
 
import java.io.IOException;
 
/**
* jstat [option] VMID [interval] [count] 查看JVM运行时的状态信息,包括内存状态、垃圾回收等
*
*-class class loader的行为统计
*-gc 垃圾回收堆的行为统计
*-gccapacity 各个垃圾回收代容量(young,old,perm)和他们相应的空间统计
*-gcutil 垃圾回收统计概述
*-gcnew 新生代行为统计
*-gcnewcapacity 新生代与其相应的内存空间的统计
*-gcold 年老代和永生代行为统计
*-gcoldcapacity 年老代行为统计
* 案例:
* jstat -gcutil 11666 1000 3
* jstata -class 11666
* jstata -compiler 11666
*
*/
public class Demo3_jstat {
    public static void main(String[] args) {
        try {
            System.out.println("jstat 指令");
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

S0 survivor0使用百分比

S1 survivor1使用百分比

E Eden区使用百分比

O 老年代使用百分比

M 元数据区使用百分比

CCS 压缩使用百分比

YGC 年轻代垃圾回收次数

YGCT 年轻代垃圾回收消耗时间

FGC Full GC垃圾回收次数

FGCT Full GC垃圾回收消耗时间

GCT 垃圾回收消耗总时间

  • 例子二: 查看GC信息

package com.lcuyp.jvm.g_tool;
 
import java.io.IOException;
 
/**
* jstat [option] VMID [interval] [count] 查看JVM运行时的状态信息,包括内存状态、垃圾回收等
*
*-class class loader的行为统计
*-gc 垃圾回收堆的行为统计
*-gccapacity 各个垃圾回收代容量(young,old,perm)和他们相应的空间统计
*-gcutil 垃圾回收统计概述
*-gcnew 新生代行为统计
*-gcnewcapacity 新生代与其相应的内存空间的统计
*-gcold 年老代和永生代行为统计
*-gcoldcapacity 年老代行为统计
* 案例:
* jstat -gcutil 11666 1000 3
* jstat -gc 11666 1000 3
*
  jvm 参数: -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC  -XX:+PrintGCDetails -verbose:gc
*/
public class Demo4_jstat {
    public static void main(String[] args) throws IOException {
        final int _1M = 1024 * 1024 ;
        byte[] b1 = new byte[2 * _1M];
        System.out.println("创建b1...");
        System.in.read();
 
        byte[] b2 = new byte[2 * _1M];
        System.out.println("创建b2...");
        System.in.read();
 
        byte[] b3 = new byte[2 * _1M];
        System.out.println("创建b3...");
        System.in.read();
 
    }
}

  • S0C:第一个幸存区的大小,单位KB
  • S1C:第二个幸存区的大小
  • S0U:第一个幸存区的使用大小
  • S1U:第二个幸存区的使用大小
  • EC:伊甸园区的大小
  • EU:伊甸园区的使用大小
  • OC:老年代大小
  • OU:老年代使用大小
  • MC:方法区大小(元空间)
  • MU:方法区使用大小
  • CCSC:压缩类空间大小
  • CCSU:压缩类空间使用大小
  • YGC:年轻代垃圾回收次数
  • YGCT:年轻代垃圾回收消耗时间,单位s
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间,单位s
  • GCT:垃圾回收消耗总时间,单位s

-gc和-gcutil的区别:-gcutil表示的是占比情况,-gc表示的是实际的值。

1.4jstack【重点】

  1. jstack是用来查看JVM线程快照的命令,线程快照是当前JVM线程正在执行的方法堆栈集合。使用jstack命令可以定位线程出现长时间卡顿的原因,例如死锁,死循环等。jstack还可以查看程序崩溃时生成的core文件中的stack信息。
  2. 命令格式
jstack [options]  <pid>
  • -F 当使用jstack 无响应时,强制输出线程堆栈。
  • -m 同时输出java堆栈和c/c++堆栈信息(混合模式)
  • -l 除了输出堆栈信息外,还显示关于锁的附加信息
  1. 例子:查看死锁
package com.lcuyp.jvm.g_tool;
 
/**
 * jstack [options]  <pid>
* <p>
* option参数解释:
* - -F 当使用jstack <pid>无响应时,强制输出线程堆栈。
* - -m 同时输出java堆栈和c/c++堆栈信息(混合模式)
* - -l 除了输出堆栈信息外,还显示关于锁的附加信息
*/
public class Demo5_jstack03 {
    private static Object obj1 = new Object();
    private static Object obj2 = new Object();
 
    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        new Thread(new Thread2()).start();
    }
 
    private static class Thread1 implements Runnable {
        public void run() {
            synchronized (obj1) {
                System.out.println("Thread1 拿到了 obj1 的锁!");
                try {
                // 停顿2秒的意义在于,让Thread2线程拿到obj2的锁
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2) {
                    System.out.println("Thread1 拿到了 obj2 的锁!");
                }
            }
        }
    }
 
    private static class Thread2 implements Runnable {
        public void run() {
            synchronized (obj2) {
                System.out.println("Thread2 拿到了 obj2 的锁!");
                try {
// 停顿2秒的意义在于,让Thread1线程拿到obj1的锁
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj1) {
                    System.out.println("Thread2 拿到了 obj1 的锁!");
                }
            }
        }
    }
}

   

1.5jmap【重点】

  1. jmap可以生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及finalizer 队列 。

Dump就是虚拟机运行时的快照,将虚拟机运行时的状态和信息保存到文件中

  1. 格式
jmap [option] 进程pid
  • 如果使用不带选项参数的jmap打印共享对象映射,将会打印目标虚拟机中加载的每个共享对象的起始 地址、映射大小以及共享对象文件的路径全称
  • -heap 打印java heap摘要
  • -histo[:live]  打印堆中的java对象统计信息
  • -clstats 打印类加载器统计信息
  • -finalizerinfo 打印在f-queue中等待执行finalizer方法的对象
  • -dump: 生成java堆的dump文件
  1. 例子一: 生成java堆的dump文件

jmap -dump:[live],format=b,file=<file-path> <pid>

  • live:如果设置,则仅打印具有活动引用的对象,并丢弃准备好进行垃圾回收的对象。此参数是可选的。
  • format = b:指定转储文件将采用二进制格式。如果未设置,结果是相同的
  • file:将写入的文件
  • pid:Java 进程的 ID

package com.lcuyp.jvm.g_tool;

/**
* jmap是用来生成堆dump文件和查看堆相关的各类信息的命令,例如查看finalize执行队列,heap的详细信息和使用情况。
* jmap [option] <pid> (连接正在执行的进程)
*
* 1.打印共享对象
* jmap 11666
*
* 2.打印heap
* jmap -heap 11666
*
* 3.打印存过这对象信息
* jmap -histo:live 9668
*
* 4.查看类加载情况
* jmap -clstats 9668
*
* 5.-finalizerinfo 打印在f-queue中等待执行finalizer方法的对象
* jmap -finalizerinfo 9668
*
* 6.堆转储
* jmap -dump:live,format=b,file=d:/dump.bin 9668
*/
public class Demo6_jmap {
    public static void main(String[] args) {
        System.out.println("jmap 指令");
        try {
            Thread.sleep(2000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

jvisualvm

  1. 打印堆中的java对象统计信息
jmap -histo pid

1.6小结

Q1: 用什么命令查看JVM垃圾回收信息?

jstat

Q2: 死锁怎么检查?

使用jstack命令可以检查出死锁

Q3: 怎么生成 java 程序的 dump 文件?

使用jmap命令或者VisualVM工具

2.VisualVM工具

2.1介绍和插件按照

2.1.1什么是VisualVM

   VisualVM 是一个工具,它提供了一个可视界面,用于查看 Java 虚拟机 (Java Virtual Machine, JVM) 上运行的基于Java 技术的应用程序(Java 应用程序)的详细信息。

2.1.2插件安装

VisualVM基于NetBeans平台开发, 因此它一开始就具备了插件扩展的特性, 通过插件支持, VisualVM可以做许多事情,
例如:

  • 显示虚拟机进程和进程的配置、环境信息(jps、jinfo)
  • 监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)
  • dump及分析堆转储快照(jmap、jhat)
  • 方法级的程序运行性能分析, 找出被调用最多、运行时间最长的方法
  • 离线程序快照: 收集程序的运行时配置、线程dump、内存dump等信息建立一个快照, 可以将快照发送开发者处
  • 进行bug反馈等等

在%JAVA_HOME%\bin目录下, 启动jvisualvm.exe进入主界面, 点击"工具"→"插件"→"可用插件"选项, 选择所需的插件安装。这里安装Visual GC

安装好插件后, 选择一个正在运行的java程序就可以查看程序监控的主界面了

2.1.3小结

Q:什么是VisualVM?

VisualVM 是一个工具,它提供了一个可视界面,用于查看 Java 虚拟机 (Java Virtual Machine, JVM) 上运行的基于Java 技术的应用程序(Java 应用程序)的详细信息。

2.2操作

2.2.1堆转储快照

两种方式生成堆dump文件:

  1. 在"应用程序"窗口中右键单击应用程序节点, 选择"堆 Dump"
  2. 在"监视"页签中选择"堆 Dump"

2.2.2分析程序性能

   在Profiler页签中, 可以对程序运行期间方法级的CPU和内存进行分析, 这个操作会对程序运行性能有很大影响, 所以一般不再生产环境使用。CPU分析将会统计每个方法的执行次数、执行耗时; 内存分析则会统计每个方法关联的对象数及对象所占空间 。

package com.lcuyp.jvm.g_tool;

import java.io.IOException;
import java.util.ArrayList;

public class Demo8_JVisualVM {
    public static void main(String[] args) throws InterruptedException, IOException {
        System.in.read();
        System.out.println("fun()执行开始...");
        fun();
        System.out.println("fun()执行结束...");
        System.in.read();
    }

    private static void fun() throws InterruptedException {
        ArrayList<Capacity> list = new ArrayList<Capacity>();
        for (int i = 0; i < 10000; i++) {
            Thread.sleep(400);
            list.add(new Capacity());
        }
    }
}
class Capacity {
    private  byte[] big = new byte[8 * 1024 * 1024]; //8m
}

2.2.3例子

  1. 分析死锁

package com.lcuyp.jvm.g_tool;
 
 
public class Deom8_JVisualVM2 {
    /**
     * 线程死锁等待演示
     */
    static class SynAddRunalbe implements Runnable {
        int a, b;
        public SynAddRunalbe(int a, int b) {
            this.a = a;
            this.b = b;
        }
 
        public void run() {
            synchronized (Integer.valueOf(a)) {
                synchronized (Integer.valueOf(b)) {
                    System.out.println(a + b);
                }
            }
        }
    }
 
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Demo7_JConsole03.SynAddRunalbe(1, 2)).start();
            new Thread(new Demo7_JConsole03.SynAddRunalbe(2, 1)).start();
        }
    }
}

  1. 分析出死锁

2.2.4远程连接jvisualvm【了解】

  • 启动普通的jar程序JMX端口配置
java ‐Dcom.sun.management.jmxremote.port=8888‐Djava.rmi.server.hostname=192.168.50.60 ‐Dcom.sun.management.jmxremot
e.ssl=false ‐Dcom.sun.management.jmxremote.authenticate=false ‐jar microservice‐eureka‐server.jar

    • -Dcom.sun.management.jmxremote.port 为远程机器的JMX端口
    • -Djava.rmi.server.hostname 为远程机器IP
  • tomcat的JMX配置:在catalina.sh文件里的最后一个JAVA_OPTS的赋值语句下一行增加如下配置行

JAVA_OPTS="$JAVA_OPTS ‐Dcom.sun.management.jmxremote.port=8888 ‐Djava.rmi.server.hostname=192.168.50.60 ‐Dcom.sun.ma
nagement.jmxremote.ssl=false‐Dcom.sun.management.jmxremote.authenticate=false"

3.Arthas

3.1介绍

Arthas 是Alibaba开源的Java诊断工具。采用命令行交互模式,可以方便的定位和诊断 线上程序运行问题。

简介 | arthas

3.2使用

3.2.1下载

  • github下载arthas

wget https://alibaba.github.io/arthas/arthas‐boot.jar

  • Gitee 下载

wget https://arthas.gitee.io/arthas‐boot.jar

📎arthas-boot.jar

📎arthas-packaging-3.6.7-bin.rar

3.2.2启动

  1. 启动
jar -jar arthas‐boot.jar

  1. 如果端口号被占用,也可以通过以下命令换成另一个端口号执行
java -jar arthas-boot.jar --telnet-port 9998 --http-port -1
  1. Arthas目前支持Web Console,用户在attach成功之后,可以直接访问:http://127.0.0.1:3658/。可以填入IP,远程连接其它机器上的arthas。

默认情况下,arthas只listen 127.0.0.1,所以如果想从远程连接,则可以使用 --target-ip参数指定listen的IP。

3.2.3常见的使用

3.2.3.1 dashboard
  1. 作用:显示当前系统的实时数据面板,按q或ctrl+c退出
  2. 效果:

  • ID: Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应
  • NAME: 线程名
  • GROUP: 线程组名
  • PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高
  • STATE: 线程的状态
  • CPU%: 线程消耗的cpu占比,采样100ms,将所有线程在这100ms内的cpu使用量求和,再算出每个线程的cpu使用占比。
  • TIME: 线程运行总时间,数据格式为分:秒
  • INTERRUPTED: 线程当前的中断位状态
  • DAEMON: 是否是daemon线程

3.2.3.2thread
  1. 作用:查看当前 JVM 的线程堆栈信息
  2. 参数说明

参数名称

参数说明

数字

线程id

[n:]

指定最忙的前N个线程并打印堆栈

[b]

找出当前阻塞其他线程的线程

[i <value>]

指定cpu占比统计的采样间隔,单位为毫秒

  1. 例子一:展示当前最忙的前3个线程并打印堆栈
thread -n 3
  1. 例子二: 查看是否有死锁(thread -b命令来查看状态是阻塞状态的线程,就会打印出来阻塞线程的堆栈以及阻塞原因、是否有死锁)
thread -b

  1. 例子三:指定采样时间间隔,每过1000毫秒采样,显示最占时间的3个线程
thread -i 1000 -n 3

3.2.3.3heapdump
  1. 作用:dump java heap, 类似 jmap 命令的 heap dump 功能
  2. 生成dump
heapdump /tmp/dump.hprof
heapdump --live /tmp/dump.hprof  #只dump live对象   

命令列表 | arthas

3.3.4小结

Q1: 什么是arthas?

Arthas 是Alibaba开源的Java诊断工具。采用命令行交互模式,可以方便的定位和诊断 线上程序运行问题。

Q2: 你工作里面JVM调优用什么工具?

  1. 使用命令+导出dump 使用本地的virtualVM分析
  2. 使用Arthas

注意: 不能直接使用virtualVM连接远程服务器, 没有权限。

Q3:微服务项目通过打成docker镜像的方式部署, 怎么使用Arthas?

通过dockerfile把Arthas jar添加到微服务的docker镜像里面。

4.GC日志分析

   GC日志是一个很重要的工具,它准确记录了每一次的GC的执行时间和执行结果,通过分析GC日志可以优化堆设置和GC设置,或者改进应用程序的对象分配模式。

4.1GC日志参数

   不同的垃圾收集器,输出的日志格式各不相同,但也有一些相同的特征。熟悉各个常用垃圾收集器的GC日志,是进行JVM调优的必备一步。 解析GC日志,首先需要收集日志,常用的有以下JVM参数用来打印输出日志信息。

4.1.1GC日志参数

参数

说明

-XX:+PrintGC

打印简单GC日志。  类似:-verbose:gc

-XX:+PrintGCDetails

打印GC详细信息

-XX:+PrintGCTimeStamps

输出GC的时间戳(以基准时间的形式)

-XX:+PrintGCDateStamps

输出GC的时间戳(以日期的形式)

-XX:+PrintHeapAtGC

在进行GC的前后打印出堆的信息

‐XX:+UseGCLogFileRotation

开启滚动生成日志

-Xloggc:../logs/gc.log

指定输出路径收集日志到日志文件

  • EG:
java ‐jar ‐Xloggc:./gc‐%t.log ‐XX:+PrintGCDetails ‐XX:+PrintGCDateStamps ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCCause ‐XX:+UseGCLogFileRotation ‐XX:NumberOfGCLogFiles=10 ‐XX:GCLogFileSize=100M microservice‐eureka‐server.jar

4.1.2常用垃圾收集器参数

参数

描述

-XX:+UseSerialGC

虚拟机在运行在Client   模式下的默认值,打开此开关后,使用Serial+Serial Old 收集器组合进行内存回收

-XX:+UseParNewGC

使用ParNew + Serial Old 收集器组合进行内存回收

-XX:+UseConcMarkSweepGC

使用ParNew + CMS + Serial Old 的收集器组合尽心内存回收,当CMS 出现Concurrent Mode Failure 失败后会使用Serial Old 作为备用收集器

-XX:+UseParallelOldGC

使用Parallel Scavenge + Parallel Old 的收集器组合

-XX:+UseParallelGC

使用Parallel Scavenge + Serial Old (PS MarkSweep)的收集器组合

-XX:+SurvivorRatio

新生代中Eden 和任何一个Survivor 区域的容量比值,默认为 8

-XX:+PretenureSizeThreshold

直接晋升到老年代对象的大小,单位是Byte

-XX:+UseAdaptiveSizePolicy

动态调整Java   堆中各区域的大小以及进入老年代的年龄

-XX:+ParallelGCThreads

设置并行GC  时进行内存回收的线程数

-XX:+GCTimeRatio

GC 时间占总时间的比率,默认值为99,只在Parallel Scavenge 收集器的时候生效

-XX:+MaxGCPauseMillis

设置GC 最大的停顿时间,只在Parallel Scavenge 收集器的时候生效

-XX:+CMSInitiatingOccupancyFraction

设置CMS  收集器在老年代空间被使用多少后触发垃圾收集,默认是68%,仅在CMS  收集器上生效

-XX:+CMSFullGCsBeforeCompaction

设置CMS  收集器在进行多少次垃圾回收之后启动一次内存碎片整理

-XX:+UseG1GC

使用G1 (Garbage First) 垃圾收集器

-XX:+MaxGCPauseMillis

设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soh goal),  JVM 会尽量去达成这个目标.

-XX:+G1HeapRegionSize

使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.

4.2GC日志分析

4.2.1日志的含义

GC 日志理解起来十分简单,因为日志本来就是要给开发人员看的,所以设计的很直观。日志例子:

[GC (Allocation Failure) [PSYoungGen: 6146K->904K(9216K)] 6146K->5008K(19456K), 0.0038730 secs] [Times: user=0.08 sys=0.00, real=0.00 secs]

将上面 GC 日志抽象为各个部分,然后我们再分别说明各个部分的含义

[a(b)[c:d->e(f), g secs] h->i(j), k secs] [Times: user:l sys=m, real=n secs]

  1. a: GC 或者是 Full GC
  2. b: 用来说明发生这次 GC 的原因
  3. c: 表示发生GC的区域,这里表示是新生代发生了GC,上面那个例子是因为在新生代中内存不够给新对象分配了,然后触发了 GC
  4. d: GC 之前该区域已使用的容量
  5. e: GC 之后该区域已使用的容量
  6. f: 该内存区域的总容量
  7. g: 表示该区域这次 GC 使用的时间
  8. h: 表示 GC 前整个堆的已使用容量
  9. i: 表示 GC 后整个堆的已使用容量
  10. j: 表示 Java 堆的总容量
  11. k: 表示 Java堆 这次 GC 使用的时间
  12. l: 代表用户态消耗的 CPU 时间
  13. m: 代表内核态消耗的 CPU 时间
  14. n: 整个 GC 事件从开始到结束的墙钟时间(Wall Clock Time)

4.3.2使用 ParNew + Serial Old

  1. 代码
package com.lcuyp.jvm.h_gc;

public class TestGCLog01 {
    private static final int _1MB = 1024*1024;
    /**
     *  ParNew + Serial Old
     * VM参数:
     *  1.  -Xms20M -Xmx20M -Xmn10M -XX:+UseParNewGC -XX:+PrintGCDetails -XX:SurvivorRatio=8
     */
    public static void testAllocation() {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2*_1MB];
        allocation2 = new byte[2*_1MB];
        allocation3 = new byte[2*_1MB]; 
        allocation4 = new byte[4*_1MB]; //出现一次 Minor GC
    }

    public static void main(String[] args) {
        testAllocation();
    }
}
  1. 日志
[GC (Allocation Failure) [ParNew: 6250K->666K(9216K), 0.0020346 secs] 6250K->4762K(19456K), 0.0020678 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 9216K, used 7132K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  78% used [0x00000000fec00000, 0x00000000ff250618, 0x00000000ff400000)
  from space 1024K,  65% used [0x00000000ff500000, 0x00000000ff5a6b08, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa00020, 0x00000000ffa00200, 0x0000000100000000)
 Metaspace       used 3412K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 374K, capacity 388K, committed 512K, reserved 1048576K
Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release
  1. 结果分析

通过上面的GC日志我们可以看出一开始出现了 MinorGC, 引起GC的原因是 内存分配失败 ,因为分配allocation的时
候,Eden区已经没有足够的区域来分配了,所以发生来本次 MinorGC ,经过 MinorGC 之后新生代的已使用容量从
6146K->753K,然而整个堆的内存总量却几乎没有减少,原因就是,由于发现新生代没有可以回收的对象,所以不
得不使用内存担保将allocation1~3 三个对象提前转移到老年代。此时再在 Eden 区域为 allocation 分配 4MB 的空
间,因此最后我们发现 Eden 区域占用了 4MB,老年代占用了 6MB

4.3.3Parallel Scavenge + Parallel Old

  1. 代码
package com.lcuyp.jvm.h_gc;

public class TestGCLog02 {
    private static final int _1MB = 1024*1024;
    /**
     *  Parallel Scavenge + Parallel Old
     * VM参数:
     *  1.  -Xms20M -Xmx20M -Xmn10M -XX:+UseParallelOldGC -XX:+PrintGCDetails -XX:SurvivorRatio=8
     */
    public static void testAllocation() {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2*_1MB];
        allocation2 = new byte[2*_1MB];
        allocation3 = new byte[2*_1MB];
        allocation4 = new byte[4*_1MB];
    }

    public static void main(String[] args) {
        testAllocation();
    }
}
  1. 日志
[GC (Allocation Failure) [PSYoungGen: 6250K->872K(9216K)] 6250K->4976K(19456K), 0.0021740 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 7337K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 78% used [0x00000000ff600000,0x00000000ffc50618,0x00000000ffe00000)
  from space 1024K, 85% used [0x00000000ffe00000,0x00000000ffeda020,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 4104K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 40% used [0x00000000fec00000,0x00000000ff002020,0x00000000ff600000)
 Metaspace       used 3412K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 374K, capacity 388K, committed 512K, reserved 1048576K

4.3.4大对象回收分析

大对象直接进入老年代 虚拟机提供一个参数 -XX:PretenureSizeThreshold 用来设置直接在老年代分配的对象的大小,如果对象大于这个值就会直接在老年代分配。这样做的目的是避免在 Eden 区及两个Survivor 区之间发生大量的内存复制。

  1. 代码
package com.lcuyp.jvm.h_gc;

public class TestGCLog03 {
    private static final int _1MB = 1024 * 1024;

    /**
     * VM参数:(参数序号对应实验序号)
     *  -Xms20M -Xmx20M -Xmn10M -XX:+UseParNewGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=3145728
     */
    public static void testPreteureSizeThreshold() {
        byte[] allocation;
        allocation = new byte[4 * _1MB];
    }

    public static void main(String[] args) {
        testPreteureSizeThreshold();
    }
}


  1. 日志
Heap
 par new generation   total 9216K, used 2318K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  28% used [0x00000000fec00000, 0x00000000fee43b50, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa00010, 0x00000000ffa00200, 0x0000000100000000)
 Metaspace       used 3426K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 375K, capacity 388K, committed 512K, reserved 1048576K
Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release
  1. 结果分析

通过上面的堆的内存占用情况很容易看出我们分配的4MB大小的对象直接被放到了老年代

4.3.5小结

Q: GC日志的含义?

[GC (Allocation Failure) [PSYoungGen: 6146K->904K(9216K)] 6146K->5008K(19456K), 0.0038730 secs] [Times: user=0.08 sys=0.00, real=0.00 secs]
[a(b)[c:d->e(f), g secs] h->i(j), k secs] [Times: user:l sys=m, real=n secs]
  1. a: GC 或者是 Full GC
  2. b: 用来说明发生这次 GC 的原因
  3. c: 表示发生GC的区域,这里表示是新生代发生了GC,上面那个例子是因为在新生代中内存不够给新对象分配了,然后触发了 GC
  4. d: GC 之前该区域已使用的容量
  5. e: GC 之后该区域已使用的容量
  6. f: 该内存区域的总容量
  7. g: 表示该区域这次 GC 使用的时间
  8. h: 表示 GC 前整个堆的已使用容量
  9. i: 表示 GC 后整个堆的已使用容量
  10. j: 表示 Java 堆的总容量
  11. k: 表示 Java堆 这次 GC 使用的时间
  12. l: 代表用户态消耗的 CPU 时间
  13. m: 代表内核态消耗的 CPU 时间
  14. n: 整个 GC 事件从开始到结束的墙钟时间(Wall Clock Time)

5日志工具【了解】

5.1简介

   GC日志可视化分析工具GCeasy和GCviewer。通过GC日志可视化分析工具,我们可以很方便的看到JVM各个分代的内存使用情况、垃圾回收次数、垃圾回收的原因、垃圾回收占用的时间、吞吐量等,这些指标在我们进行JVM调优的时候是很有用的。

  • GCeasy是一款在线的GC日志分析器,可以通过GC日志分析进行内存泄露检测、GC暂停原因分析、JVM配置建议优化等功能,而且是可以免费使用在线分析工具 https://gceasy.io/index.jsp
  • GCViewer是一款实用的GC日志分析软件,免费开源使用,你需要安装jdk或者java环境才可以使用。软件为GC日志分析人员提供了强大的功能支持,有利于大大提高分析效率

5.2测试准备

package com.lcuyp.jvm.h_gc;

import java.util.ArrayList;

public class TestGCLog04 {
    private static final int _1MB = 1024 * 1024;
    /**
     * -XX:SurvivorRatio=8 -XX:+UseParallelGC -XX:+UseParallelOldGC  -XX:+PrintGCDetails -Xloggc:D://gc.log
     */
    public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<byte[]>();
        for (int i = 0; i < 2000; i++) {
            byte[] arr = new byte[1024 * 1024];
            list.add(arr);
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

5.3GCeasy

  • 这是一个在线分析日志的工具,主要功能是免费的,存在部分收费,地址:https://gceasy.io/

5.4GCViewer

  • GCViewer是一个小工具,可以可视化展示 生成的详细GC输出。支持Sun / Oracle,IBM,HP和BEA的Java虚拟机。它是GNU LGPL下发布的免费软件。
    https://github.com/chewiebug/GCViewer
  • 下载

https://sourceforge.net/projects/gcviewer/

📎gcviewer-1.37-SNAPSHOT.jar

  • 使用

java -jar gcviewer-1.37-SNAPSHOT.jar

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值