源码 。
在生产环境不可避免的会出现因为JVM问题导致的故障,这种情况如果处理不当,不及时,很有可能影响毛爷爷,甚至丢掉饭碗,因此掌握处理该类问题的硬技能就显得非常重要了,那都需要哪些硬技能呢,我认为如下的2个:
1:定位分析问题的能力。
2:不要慌!不要慌!!不要慌!!!
对于1
,除了平时积累的技术能力外,外部辅助工具的使用也异常重要,这也正是本文要重点分析的内容,对于2
,大家都知道,不管什么事,一慌张就完蛋,所以千万不要慌(至少要装作不慌张(* ̄︶ ̄))。下面我们就开始吧!
使用如下程序:
public class CommonMistakesApplication {
public static void main(String[] args) throws Exception {
//启动10个线程
IntStream.rangeClosed(1, 10).mapToObj(i -> new Thread(() -> {
while (true) {
//每一个线程都是一个死循环,休眠10秒,打印10M数据
// IntStream.rangeClosed(1, 10000000)生成1~10000000的数字序列
// mapToObj(__ -> "a")生成10000000个a,即10M数据
// collect(Collectors.joining("")) 拼接为字符串
String payload = IntStream.rangeClosed(1, 10000000)
.mapToObj(__ -> "a")
.collect(Collectors.joining("")) + UUID.randomUUID().toString();
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(payload.length());
}
})).forEach(Thread::start);
TimeUnit.HOURS.sleep(1);
}
}
如下pom:
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-shade-pluginartifactId>
<version>1.2.1version>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>shadegoal>
goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>dongshi.daddy.zhengxi.CommonMistakesApplicationmainClass>
transformer>
transformers>
configuration>
execution>
executions>
plugin>
如果觉得麻烦,也可以直接从这里 下载打好的jar包。
插件用于生成可执行的jar包。生成jar包后可以使用命令java -jar .\java8instrument-1.0-SNAPSHOT.jar -Xms1g -Xmx1g
,将堆的最大内存和最小内存都设置为1g。
如下我本地启动的结果:
$ java -jar java8instrument-1.0-SNAPSHOT.jar -Xms1g -Xmx1g
10000036
10000036
10000036
10000036
...
C:\WINDOWS\system32>jps -l | findstr "java8"
23028 java8instrument-1.0-SNAPSHOT.jar
因为jps值专门针对java进程的命令,所以使用起来要比ps方便的多。
C:\WINDOWS\system32>jinfo 23028
Attaching to process ID 23028, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.202-b08
Java System Properties:
java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.202-b08
sun.boot.library.path = D:\program_files\jdk1.8.0_202\jre\bin
java.vendor.url = http://java.oracle.com/
...
sun.cpu.isalist = amd64
VM Flags:
Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=266338304 -XX:MaxHeapSize=4242538496 -XX:MaxNewSize=1414004736 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=88604672 -XX:OldSize=177733632 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line:
注意到-XX:MaxHeapSize=4242538496
注意到堆内存是4g,而不是我们设置的1g,这是因为我们将参数配置写在了后面,被当做主函数的参数了,可以增加如下代码验证:
System.out.println("启动参数:");
System.out.println(Arrays.stream(args).collect(Collectors.joining(System.lineSeparator())));
测试输出如下:
$ java -jar -Dfile.encoding=utf-8 java8instrument-1.0-SNAPSHOT.jar -Xms1g -Xmx1g
启动参数:
-Xms1g
-Xmx1g
也可以添加如下代码确认设置的VM参数:
$ java -jar -Dfile.encoding=utf-8 -Xms1g -Xmx1g java8instrument-1.0-SNAPSHOT.jar test
启动参数:
test
VM options
-Dfile.encoding=utf-8
-Xms1g
-Xmx1g
10000036
10000036
...
因此我们的启动命令修改为java -jar -Dfile.encoding=utf-8 -Xms1g -Xmx1g java8instrument-1.0-SNAPSHOT.jar
。
这是一个GUI工具,可以通过界面方便的查看相关信息。
如图红框的展示信息。
这是一个GUI工具,类似于jvirtualvm,可以通过界面方便的查看相关信息。
使用该命令可以查看堆各个区域的使用情况,GC的情况,GC的耗时等信息,一般使用命令jstat -gcutil PID 刷新毫秒间隔 打印次数
,如下是2秒打印一次,一共打印1000次:
各列含义如下:
S0:survivor 0区域使用百分比。
S1:survivor 1区域使用百分比。
E:Eden区域使用百分比。
O:Old区域使用百分比。
M:Metadata元数据区使用百分比。
YGC:年轻代发生GC的次数。
YGCT:年轻代回收总耗时(秒)。
FGC:老年代发生GC的次数。
FGCT:老年老垃圾回收总耗时(秒)。
GCT:所有区本次垃圾回收总耗时(秒)。
从图中gc次数的增长可以看出大概计算出多长时间发生一次GC,如年老代大概是10秒钟发生一次FGC,这也是生产环境出现问题时我们重点需要关注的点。
我们可以将生成的信息保存到文件中,然后通过网站分析工具fastThread 来生成图标的分析结果,方便我们分析,如下操作:
7.1:保存结果到文件
生成的文件:然后点击Analyze
开始分析,分析主要结果如下:
可用于排查StackOverflowError
,即方法调用栈过深,导致栈内存溢出如下测试代码获取的结果:
public class DeepStackTrace {
public static void main(String[] args) throws Exception {
sayHi();
}
private static void sayHi() throws Exception {
Thread.sleep(200);
sayHi();
}
}
点击红框内容:
$ jcmd -l
2816 org.jetbrains.idea.maven.server.RemoteMavenServer36
15044 java8instrument-1.0-SNAPSHOT.jar
20516 org.jetbrains.idea.maven.server.RemoteMavenServer36
22084 org.jetbrains.idea.maven.server.RemoteMavenServer36
...
$ jcmd 20724 VM.uptime
20724:
765227.730 s
$ jcmd 20724 Thread.print
20724:
2022-04-01 14:26:58
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.202-b08 mixed mode):
"DestroyJavaVM" #51 prio=5 os_prio=0 tid=0x000000002837a800 nid=0x4214 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"http-nio-8765-AsyncTimeout" #49 daemon prio=5 os_prio=0 tid=0x0000000028378000 nid=0x1288 waiting on condition [0x000000002c08e000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.coyote.AbstractProtocol$AsyncTimeout.run(AbstractProtocol.java:1133)
at java.lang.Thread.run(Thread.java:748)
...
获取对象的实例数,占用空间大小,按照占用空间大小排序。如下测试代码和测试的结果:
public class ManyObj {
private static List<People> peopleList = new ArrayList<>();
public static void main(String[] args) throws Exception {
System.out.println("test begin");
IntStream.rangeClosed(1, 99999).forEach(__ -> peopleList.add(new People()));
System.out.println("test end");
System.in.read();
}
}
红框中就是我们在测试代码中创建的对象实例,同样的功能也可以使用jmap 命令,jmap -histo PID
来实现,如下:
$ jmap -histo 26396
num #instances #bytes class name
----------------------------------------------
1: 99999 1599984 dongshi.daddy.zhengxi.People
2: 950 479976 [Ljava.lang.Object;
3: 5138 479224 [C
4: 455 136400 [B
5: 4989 119736 java.lang.String
6: 899 102840 java.lang.Class
7: 791 31640 java.util.TreeMap$Entry
...
$ jcmd 26396 GC.heap_dump d:\\test\\manyobj_jcmd.bin
26396:
Heap dump file created
类似的命令还有
jmap -dump:live,format=b,file=/path/to/filename.hprof PID
。
$ jcmd 26396 VM.flags
26396:
-XX:CICompilerCount=4 -XX:InitialHeapSize=266338304 -XX:MaxHeapSize=4242538496 -XX:MaxNewSize=1414004736 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=88604672 -XX:OldSize=177733632 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
参考文章列表:
6 款 Java 8 自带工具,轻松分析定位 JVM 问题! 。
十二、jdk工具之jcmd介绍(堆转储、堆分析、获取系统信息、查看堆外内存) 。