这是我参与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[]对象。