一、简介

无论是在开发环境还是在生产环境中,难免都会遇到内存溢出等内存问题,为了尽快使我们找到发生内存问题所在,Java提供了一些内带的命令供我们使用,除了使用JDK自带命令(jps、jstack、jinfo、jstat等),我们也可以借助一些工具,如VirtualVM、jconsole、Eclipse Memory Analyzer(MAT)等,通过这些命令和工具,可以帮助我们方便的在生产监控和打印堆栈的日志信息,以便于更快定位问题所在,本文将对这些命令和工具做一个简单的介绍。

 

二、JDK自带命令

JDK自带的命令,直接使用cmd命令行工具就可以打开,只要配置好环境变量就可以使用这些内置的命令。

Java 内存初始化 java 内存命令_内存分析工具

【a】jps:显示当前系统运行的虚拟机进程信息。可用 jps -help 查看jps命令使用方法,跟我们电脑的进程管理器类似,可以看到对应的进程号等信息。

 

Java 内存初始化 java 内存命令_jconsole_02

首先运行一段程序,并指定运行参数和传递main方法参数:

public class Test {
    public static void main(String[] args) {
        for (String string : args) {
            System.out.println(string);
        }
        //-Xmx20m -Xms20m
        Scanner scanner = new Scanner(System.in);
        scanner.nextInt();
    }
}

Java 内存初始化 java 内存命令_virtualvm_03

(1) jps -l:显示类的全限定名或者jar包路径 。

Java 内存初始化 java 内存命令_virtualvm_04

(2)jps -v:显示运行程序时指定的JVM参数。

Java 内存初始化 java 内存命令_Java 内存初始化_05

(3)jps -m:显示程序运行时传递给main()的参数。

Java 内存初始化 java 内存命令_jconsole_06

当然,也可以直接使用 jps -lmv 显示全部信息,如下图:

Java 内存初始化 java 内存命令_virtualvm_07

 

【b】jstat:监视虚拟机运行时状态信息,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,需要提供进程ID才能具体看到某个进程的运行状态信息等。

使用 jstat -help 查看jstat命令的使用方法

Java 内存初始化 java 内存命令_virtualgc_08

  (1). jstat -gcutil 18248: 查看进程编号为18248的垃圾回收统计信息。 

Java 内存初始化 java 内存命令_virtualgc_09

  (2). jstat -class 18248:  查看进程编号为18248的类加载器加载的一些信息。

Java 内存初始化 java 内存命令_virtualvm_10

 

  (3). jstat [option] : [option]选项取值可以为  class(类加载器行为统计)、compiler(JIT编译器行为统计)、gc(垃圾回收堆的行为统计)、gccapacity(各个垃圾回收代容量(young,old,perm)和他们相应的空间统计)、gcutil(垃圾回收统计)、gccause(垃圾收集统计(同-gcutil ))、gcnew(新生代行为统计)、gcnewcapacity(新生代与其相应的内存空间的统计)、gcold(年老代和永生代行为统计)、gcoldcapacity(年老代行为统计)、gcpermcapacity(永生代行为统计)、printcompilation(HotSpot编译方法统计)。

 

【c】jinfo: 查看虚拟机配置信息

使用jinfo -help查看jinfo命令使用方法:

Java 内存初始化 java 内存命令_jconsole_11

 (1) jinfo -flags 18248: 查看进程编号为18248的JVM实时运行信息,包括JVM版本信息、指定的运行参数。

Java 内存初始化 java 内存命令_virtualvm_12

 

 (2)jinfo -sysprops 18248:显示系统属性

Java 内存初始化 java 内存命令_virtualvm_13

 

【d】jmap:生成heap dump堆内存快照文件。除了使用jmap命令之外,上一篇文章还演示了通过配置运行JVM参数:-XX:+HeapDumpOnOutOfMemoryError来设置程序在发生内存溢出时自动生成堆快照文件信息。jmap命令还可以查询finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。

使用jmap -h查询jmap命令使用方法:

Java 内存初始化 java 内存命令_virtualvm_14

(1)  jmap -dump:live,format=b,file=log.hprof 3756:将进程号为3756的进程生成堆内存快照,文件名称为log.hprof,创建的hprof文件也可以使用Eclipse Memory Analyzer进行分析。

Java 内存初始化 java 内存命令_virtualvm_15

Java 内存初始化 java 内存命令_virtualgc_16

 

(2) jmap -heap 3756:显示dheap的概要信息,GC使用的算法,heap的配置

Java 内存初始化 java 内存命令_virtualgc_17

(3) jmap -finalizerinfo 3756:显示正等待回收对象的信息。

Java 内存初始化 java 内存命令_内存分析工具_18

 

【e】jhat:jhat命令一般与jmap命令结合使用,用于分析jmap生成的堆内存快照文件hprof,生成dump的分析结果后,可以在浏览器中查看。

  • 注意:在生产环境中,一般不会直接在服务器上进行分析,因为jhat比较耗时耗费资源,一般都会把服务器生成的dump文件复制到本地或其他机器上进行分析,然后可以借助一些外部工具如VirtualVM、MAT、VirtualGC等进行分析。

由于不常使用,所以这里对jhat命令不做分析。

【一般情况下,不使用jmap和jhat命令,因为大多数情况下都会使用可视化工具(VirtualVM、MAT、VirtualGC、jconsole等)来实现同样的功能,简单方便。】

 

三、可视化工具

【a】Eclipse Memory Analyzer (MAT) :Java堆转储文件分析工具,可以帮助你发现内存漏洞和减少内存消耗。其实在上篇文章已经介绍过MAT工具的使用方法。软件下载地址:https://www.eclipse.org/mat/downloads.php

MAT工具主要用于分析.hprof后缀的文件,一般通过jmap或者执行运行时JVM参数-XX:HeapDumpOnOutofMemoryError 就可以生成堆内存快照信息。下面我们打开一个hprof文件说明一下大概的用法:

  • 注意:如果生成的堆内存快照比较大,那么MAT在分析内存报告时可能会报错:An internal error occurred during: "Parsing heap dump from XXX”。那么需要适当调大一下MAT内存大小:
  • 方法:一种方法是修改启动参数MemoryAnalyzer.exe-vmargs -Xmx6g,另一种方法是编辑文件 MemoryAnalyzer.ini,在里面添加类似信息 -vmargs– Xmx6g

Java 内存初始化 java 内存命令_virtualgc_19

 

(1)点击柱状图面板Histogram,可以看到占用内存最大的对象:

Java 内存初始化 java 内存命令_Java 内存初始化_20

 

(2)点击Dominator Tree:可以看到占用内存最大的对象以及占比,对象的全限定名等信息。

Java 内存初始化 java 内存命令_jconsole_21

 

(3)点击Leak Suspects泄漏疑点面板:可以观察到内存使用情况以及发生内存溢出问题的可疑点地方。

Java 内存初始化 java 内存命令_jconsole_22

查看详情信息:

Java 内存初始化 java 内存命令_Java 内存初始化_23

 

【b】Jconsole:Jconsole是一个符合JMX标准的监视工具。它使用Java虚拟机的广泛JMX工具来提供有关Java平台上运行的应用程序的性能和资源消耗的信息。

Jconsole.exe应用程序位于JAVA_HOME/bin目录下,如果配置了环境变量,那么可以直接在cmd中输入jconsole命令就可以打开Jconsole可视化工具。如下图:

Java 内存初始化 java 内存命令_virtualgc_24

Jconsole可以监控本地进程和远程进程,通常情况下,使用Jconsole监控本地程序比较有用,因为在生产环境中,Jconsole比较耗费系统的资源,因此比较适合在本地程序进行监控分析。Jconsole会自动读取本地已经启动的JVM进程信息,可以选择其中一个进行监测。

Jconsole命令语法: jconsole 【进程ID】 

进程ID可以通过ps命令(Linux)、jps命令和进程管理器中java或javaw对应的进程ID(Windows).

Java 内存初始化 java 内存命令_Java 内存初始化_25

jconsole自动读取系统中启动的JVM进程,我们选择其中一个进行分析。

 

(1)概览:显示有关JVM和受监视值的摘要信息。包括堆内存使用量、线程、CPU使用率等信息。

Java 内存初始化 java 内存命令_virtualvm_26

 

(2)内存:显示有关内存使用的信息。

Java 内存初始化 java 内存命令_jconsole_27

 

(3)线程:显示有关线程使用的信息。还可以监测到线程死锁的发生。

Java 内存初始化 java 内存命令_jconsole_28

下面是一个监测死锁发生的示例,相关代码如下:

public class Thread01 implements Runnable {

    private Object object1;
    private Object object2;

    public Thread01(Object object1, Object object2) {
        this.object1 = object1;
        this.object2 = object2;
    }

    @Override
    public void run() {
        synchronized (object1) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (object2) {
                System.out.println("Thread01.run");
            }

        }
    }
}
public class Thread02 implements Runnable {

    private Object object1;
    private Object object2;

    public Thread02(Object object1, Object object2) {
        this.object1 = object1;
        this.object2 = object2;
    }

    @Override
    public void run() {
        synchronized (object2) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (object1) {
                System.out.println("Thread02.run");
            }

        }
    }
}

 

public class TestDealLock {
    public static void main(String[] args) {
        Object object1 = new Object();
        Object object2 = new Object();
        new Thread(new Thread01(object1, object2)).start();
        new Thread(new Thread02(object1, object2)).start();
    }
}

Java 内存初始化 java 内存命令_内存分析工具_29

Java 内存初始化 java 内存命令_virtualgc_30

 

(4)类:显示有关类加载的相关信息

Java 内存初始化 java 内存命令_virtualvm_31

 

(5)VM:显示虚拟机相关信息。

Java 内存初始化 java 内存命令_jconsole_32

(6)MBean:显示有关使用平台MBean服务器注册的所有MBean的信息。

Java 内存初始化 java 内存命令_内存分析工具_33

如果我们停止程序运行,那么Jconsole也会立刻断开连接,如图所示:

Java 内存初始化 java 内存命令_内存分析工具_34

 

【c】VisualVM:VisualVM 是一款免费的性能分析工具。它通过 jvmstat、JMX、SA(Serviceability Agent)以及 Attach API 等多种方式从程序运行时获得实时数据,从而进行动态的性能分析。同时,它能自动选择更快更轻量级的技术尽量减少性能分析对应用程序造成的影响,提高性能分析的精度。

软件下载地址:https://visualvm.github.io/download.html

下载完成后进入bin目录,点击visualvm.exe 启动virtualvm,如下图:

Java 内存初始化 java 内存命令_jconsole_35

下面我们选择其中一个进程(11040)进行分析:

 

(1)概述:展示进程信息、虚拟机信息、JVM参数、JDK目录等信息。

Java 内存初始化 java 内存命令_内存分析工具_36

 

(2)monitor监视器:实时展示堆内存、方法区、类加载信息、CPU、线程等使用情况。

Java 内存初始化 java 内存命令_virtualgc_37

 

(3)Threads线程面板:实时展示当前程序中所有线程的运行状态,是否有死锁、热锁等情况的发生。

Java 内存初始化 java 内存命令_Java 内存初始化_38

 

(4)Sampler抽样器:

  • CPU样例 是站在“方法”的角度,可以看到各个方法的自用时间,方便进行代码分析。
  • 线程CPU时间 则是站在“线程”的角度,看到各个线程的自用时间。同时左下角均支持方法名/线程名过滤。

Java 内存初始化 java 内存命令_virtualvm_39

 

(5)virtual gc:这是virtualvm的一个插件,需要安装才能使用,如下图:

Java 内存初始化 java 内存命令_virtualgc_40

记得安装完需要重新打开virtualvm, 这时候virtualvm会多出一个virtualgc的面板,可以观察垃圾回收的详细信息。

Java 内存初始化 java 内存命令_virtualgc_41

 

下面依次分析各个区域展示信息的含义:

  • compile time:编译器编译耗费的时间

Java 内存初始化 java 内存命令_virtualgc_42

 

  • Class Loader Time:表示4049个类被加载,9个类未被加载,耗费1.559秒

Java 内存初始化 java 内存命令_内存分析工具_43

 

  • GC Time:表示进行了72次垃圾回收,耗费221.486毫秒,最后一次垃圾回收的原因:内存分配失败

Java 内存初始化 java 内存命令_内存分析工具_44

 

  • Eden Space

Java 内存初始化 java 内存命令_virtualgc_45

Eden Space(232.000M,30.500M) : 11.889M, 72 collections, 192.629ms

  •   232.000M :表示新生代Eden区域最大分配空间大小;
  •        30.500M :表示当前分配新生代Eden区域空间大小;
  •        11.889M :表示当前占用的新生代Eden区域空间大小;
  •        72 collections:新生代Eden区域进行了72次垃圾回收;
  •        192.629ms:新生代Eden区域垃圾回收耗费的时间;

 

  • S0/S1

Java 内存初始化 java 内存命令_jconsole_46

Survivor 0 (77.500M,1.000M) : 0

  •   77.500M :表示Survivor 0区域最大分配空间;
  •        1.000M :表示当前已经分配Survivor 0区域大小;
  •        0 :表示当前占用的Survivor 0区域大小;

 

Survivor 1 (77.500M,1.000M) : 512.000K

  •   77.500M :表示Survivor 1区域最大分配空间;
  •        1.000M :表示当前已经分配Survivor 1区域大小;
  •        512K :表示当前占用的Survivor 1区域大小;

可以看到,S0 和 S1 肯定有一个是空闲的,这样方便执行 minor GC,但是两者的最大分配空间是相同的。并且在 minor GC 时,会发生 S0 和S1 之间的切换。

 

  • Old Gen老年代:

Java 内存初始化 java 内存命令_virtualvm_47

Old Gen(467.000M,104.000M): 54.920M 1 collections, 31.125ms

  •   467.000M :表示老年代最大分配空间;
  •        104.000M :表示当前已经分配老年代大小;
  •        54.920M :表示当前占用的老年代大小;
  •        1  collections:老年代共进行1次垃圾回收,即Full GC.
  •        31.125ms:Full GC耗费的时间

 

  • Meta Space方法区:一般方法区就相当于永久代,JDK8开始才有metaspace方法区。

Java 内存初始化 java 内存命令_virtualvm_48

Metaspace(1.020G,24.625M): 24.011M

  •   1.020G :表示方法区最大可用大小;
  •        24.625M :表示当前方法区大小;
  •        24.011M :表示当前占用的方法区大小;

 

四、总结

本篇文章主要介绍了一些JDK内置的性能以及内存分析命令,如jps/jmap/jinfo/jstat/jhat等,同时介绍了现在比较流行的三种可视化分析调优工具,VirtualVM(推荐使用)、Eclipse Memory Analyzer 、jconsole。实际工作中,一般用的比较多的是VirtualVM结合Virtualgc插件,非常强大,具体还是根据项目的要求以及每个工具是否耗费系统资源等来选择哪一种性能调优工具。本文仅仅是笔者对JVM性能调优以及内存分析的一些见解和认识,以此作为总结,希望能帮助到大家。