18-1 jvm调优工具(一)

522 阅读4分钟

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

一、前言

因为我的是mac电脑,所以运行程序都是在mac上,有时一些工具在mac上不是很好用。如果有不好用的情况,可以参考文章:

1. mac安装多版本jdk

2. 彻底解决Jmap在mac版本无法使用的问题

以上是我在mac上运行Jmap时遇到的问题,如果你也遇到了,可以查看。

二、Jmap使用

1. Jmap -histo 进程号

这个命令是用来查看系统内存使用情况的,实例个数,以及占用内存。

命令:

jmap -histo 3241

运行结果:

   
 num     #instances         #bytes  class name
----------------------------------------------
   1:       1101980      372161752  [B
   2:        551394      186807240  [Ljava.lang.Object;
   3:       1235341      181685128  [C
   4:         76692      170306096  [I
   5:        459168       14693376  java.util.concurrent.locks.AbstractQueuedSynchronizer$Node
   6:        543699       13048776  java.lang.String
   7:        497636       11943264  java.util.ArrayList
   8:        124271       10935848  java.lang.reflect.Method
   9:        348582        7057632  [Ljava.lang.Class;
  10:        186244        5959808  java.util.concurrent.ConcurrentHashMap$Node

这里显示的是,byte类型的数组,有多少个实例,占用多大内存。

  • 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

2. Jmap -heap 进程号

注意:Jmap命令在mac不太好用,具体参考前言部分。

windows或者linux上运行的命令是

Jmap -heap 进程号

mac上运行的命令是:(jdk8不能正常运行,jdk9以上可以)

jhsdb jmap --heap --pid 2139

执行结果

Attaching to process ID 2139, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.2+9
​
using thread-local object allocation.
Garbage-First (G1) GC with 8 thread(s)
​
Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 4294967296 (4096.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 2576351232 (2457.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 1048576 (1.0MB)
​
Heap Usage:
G1 Heap:
   regions  = 4096
   capacity = 4294967296 (4096.0MB)
   used     = 21654560 (20.651397705078125MB)
   free     = 4273312736 (4075.348602294922MB)
   0.5041845142841339% used
G1 Young Generation:
Eden Space:
   regions  = 15
   capacity = 52428800 (50.0MB)
   used     = 15728640 (15.0MB)
   free     = 36700160 (35.0MB)
   30.0% used
Survivor Space:
   regions  = 5
   capacity = 5242880 (5.0MB)
   used     = 5242880 (5.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 1
   capacity = 210763776 (201.0MB)
   used     = 0 (0.0MB)
   free     = 210763776 (201.0MB)
   0.0% used

通过上述结果分析,我们查询的内容如下:

  • 进程号:2139
  • JDK版本号:11
  • 使用的垃圾收集器:G1(jdk11默认的)
  • G1垃圾收集器线程数:8
  • 还可以知道堆空间大小,已用大小,元数据空间大小等等。
  • 新生代,老年代region的大小。容量,已用,空闲等。

3. Jmap -dump 导出堆信息

这个命令是导出堆信息,当我们线上有内存溢出的情况的时候,可以使用Jmap -dump导出堆内存信息。然后再导入可视化工具用jvisualvm进行分析。

导出命令

jmap -dump:file=a.dump 进程号

我们还可以设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)

1. -XX:+HeapDumpOnOutOfMemoryError 
2. -XX:HeapDumpPath=./ (路径)

下面有案例说明如何使用。

三、jvisualvm命令工具的使用

1. 基础用法

上面我们有导出dump堆信息到文件中,可以使用jvisualvm工具导入dump堆信息,进行分析。

打开jvisualvm工具命令:

jvisualvm

打开工具界面如下:

点击文件->装入,可以导入文件,查看系统的运行情况了。

2.案例分析 - 堆空间溢出问题定位

下面通过工具来分析内存溢出的原因。

第一步:自定义一段可能会内存溢出的代码,如下:

import com.aaa.jvm.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
​
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
​
@SpringBootApplication
public class JVMApplication {
​
  public static void main(String[] args) {
    List<Object> list = new ArrayList<>();
    int i = 0;
    int j = 0;
    while (true) {
      list.add(new User(i++, UUID.randomUUID().toString()));
      new User(j--, UUID.randomUUID().toString());
    }
  }
}

第二步:配置参数

为了方便看到效果,所以我们会设置两组参数。

第一组:设置堆空间大小,将堆空间设置的小一些,可以更快查看内存溢出的效果

Xms10M ‐Xmx10M ‐XX:+PrintGCDetails

设置的堆内存空间是10M,并且打印GC

第二组:设置内存溢出自动导出dump****文件(内存很大的时候,可能会导不出来)

1. -XX:+HeapDumpOnOutOfMemoryError 
2. -XX:HeapDumpPath=./ (路径)

将这两组参数添加到项目启动配置中。

运行的过程中打印堆空间信息到文件中:

jmap -dump:file=a.dump,format=b 12152

后面我们可以使用工具导入堆文件进行分析(下面有说到)。

我们还可以设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)

完整参数配置如下:

-Xms10M -Xmx10M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/zhangsan/Downloads

这里需要注意的是堆目录要写绝对路径,不能写相对路径。

第三步:启动项目,等待内存溢出

我们看到,运行没有多长时间就内存溢出了。

查看导出到文件的目录:

第四步:导入堆内存文件到jvisualvm工具

文件->装入->选择刚刚导出的文件

第五步:分析

我们主要看【类】这个模块。

通过上图我们可以明确看出,有三个类实例数特别多,分别是:byte[],java.lang.String,com.lxl.jvm.User。前两个我们不容易看出是哪里的问题,但是第三个类com.lxl.jvm.User我们就看出来了,问题出在哪里。接下来就重点排查调用了这个类的地方,有没有出现内存没有释放的情况。

这个程序很简单,那么byte[]和java.lang.String到底是什么呢?我们的User对象结构中字段类型是String。

public class User {
    private int id;
    private String name;
}

既然有很多User,自然String也少不了。

那么byte[]是怎么回事呢?其实String类中有byte[]成员变量。所以也会有很多byte[]对象。