一、创建会内存溢出的程序
pom:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fan</groupId>
<artifactId>JVMTest</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>{Mainclass}</mainClass>
<!-- 主类的位置,例如上图文件,主类配置应为: -->
<mainClass>com.fan.T15_Fu11GC_Problem01</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<name>JVMTest</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.58</version>
</dependency>
<dependency>
<groupId>backport-util-concurrent</groupId>
<artifactId>backport-util-concurrent</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
</project>
Java:
package com.fan;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class T15_Fu11GC_Problem01 {
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50, new ThreadPoolExecutor.DiscardOldestPolicy());
public static void main(String[] args) throws Exception {
executor.setMaximumPoolSize(50);
for (; ; ) {
modelFit();
Thread.sleep(100);
}
}
private static class CardInfo {
BigDecimal price = new BigDecimal(0.0);
String name = "张三";
int age = 5;
Date birihdate = new Date();
public void m() {
}
}
private static void modelFit() {
List<CardInfo> taskList = getAllCardInfo();
taskList.forEach(info -> {
executor.scheduleWithFixedDelay(() -> {
info.m();
}, 2, 3, TimeUnit.SECONDS);
});
}
private static List<CardInfo> getAllCardInfo() {
List<CardInfo> taskList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
CardInfo ci = new CardInfo();
taskList.add(ci);
}
return taskList;
}
}
打包 jar:
将 JVMTest-1.0-SNAPSHOT.jar 包放到服务器中
二、启动 JVMTest-1.0-SNAPSHOT.jar
java -Xms200M -Xmx200M -XX:+PrintGC -jar JVMTest-1.0-SNAPSHOT.jar
如图,随着时间的推移,GC每隔一段时间回收一次。
jvm排查常用命令:
1. jps:该命令会显示所有的 java进程,这里我们的测试程序为 4869 jar
2. jinfo 进程号: 该命令显示 对应进程号进程的相关信息
[root@centos142 arthas]# jinfo 4869
Attaching to process ID 4869, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.333-b02
Java System Properties:
java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.333-b02
sun.boot.library.path = /usr/local/jdk1.8/jre/lib/amd64
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
path.separator = :
file.encoding.pkg = sun.io
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
sun.os.patch.level = unknown
sun.java.launcher = SUN_STANDARD
user.country = CN
user.dir = /mnt/arthas
java.vm.specification.name = Java Virtual Machine Specification
java.runtime.version = 1.8.0_333-b02
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = /usr/local/jdk1.8/jre/lib/endorsed
java.io.tmpdir = /tmp
line.separator =
java.vm.specification.vendor = Oracle Corporation
os.name = Linux
sun.jnu.encoding = UTF-8
java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
java.specification.name = Java Platform API Specification
java.class.version = 52.0
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
os.version = 3.10.0-1160.el7.x86_64
user.home = /root
user.timezone =
java.awt.printerjob = sun.print.PSPrinterJob
file.encoding = UTF-8
java.specification.version = 1.8
user.name = root
java.class.path = JVMTest-1.0-SNAPSHOT.jar
java.vm.specification.version = 1.8
sun.arch.data.model = 64
sun.java.command = JVMTest-1.0-SNAPSHOT.jar
java.home = /usr/local/jdk1.8/jre
user.language = zh
java.specification.vendor = Oracle Corporation
awt.toolkit = sun.awt.X11.XToolkit
java.vm.info = mixed mode
java.version = 1.8.0_333
java.ext.dirs = /usr/local/jdk1.8/jre/lib/ext:/usr/java/packages/lib/ext
sun.boot.class.path = /usr/local/jdk1.8/jre/lib/resources.jar:/usr/local/jdk1.8/jre/lib/rt.jar:/usr/local/jdk1.8/jre/lib/sunrsasign.jar:/usr/local/jdk1.8/jre/lib/jsse.jar:/usr/local/jdk1.8/jre/lib/jce.jar:/usr/local/jdk1.8/jre/lib/charsets.jar:/usr/local/jdk1.8/jre/lib/jfr.jar:/usr/local/jdk1.8/jre/classes
java.vendor = Oracle Corporation
file.separator = /
java.vendor.url.bug = http://bugreport.sun.com/bugreport/
sun.io.unicode.encoding = UnicodeLittle
sun.cpu.endian = little
sun.cpu.isalist =
VM Flags:
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=209715200 -XX:MaxHeapSize=209715200 -XX:MaxNewSize=69730304 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=69730304 -XX:OldSize=139984896 -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -Xms200M -Xmx200M -XX:+PrintGC
例如,这里我们启动的程序使用的是 -Xms200M -Xmx200M -XX:+PrintGC
3. jstat -[gc] 进程号 [500]:该命令用来统计或跟踪 java 的进程信息。
(1)、动态观察 GC 情况(阅读 GC日志发现频繁GC 、arthas观察、jvisualVM、Jprofiler等)。
[root@centos142 arthas]# jstat -gc 4869
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
8192.0 8192.0 0.0 0.0 50688.0 50040.7 136704.0 136606.0 4864.0 4113.3 512.0 446.4 5 0.304 12 5.061 5.366
(2)、每500毫秒刷新一次,用来跟踪内存的增长过程
[root@centos142 arthas]# jstat -gc 4869 500
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
8192.0 8192.0 0.0 0.0 50688.0 50551.2 136704.0 136606.0 4864.0 4113.3 512.0 446.4 5 0.304 21 8.917 9.221
8192.0 8192.0 0.0 0.0 50688.0 50600.3 136704.0 136606.0 4864.0 4113.3 512.0 446.4 5 0.304 21 8.917 9.221
8192.0 8192.0 0.0 0.0 50688.0 50653.5 136704.0 136606.0 4864.0 4113.3 512.0 446.4 5 0.304 21 8.917 9.221
8192.0 8192.0 0.0 0.0 50688.0 50653.5 136704.0 136606.0 4864.0 4113.3 512.0 446.4 5 0.304 21 8.917 9.221
8192.0 8192.0 0.0 0.0 50688.0 50653.5 136704.0 136606.0 4864.0 4113.3 512.0 446.4 5 0.304 21 8.917 9.221
8192.0 8192.0 0.0 0.0 50688.0 50653.5 136704.0 136606.0 4864.0 4113.3 512.0 446.4 5 0.304 21 8.917 9.221
8192.0 8192.0 0.0 0.0 50688.0 50655.6 136704.0 136606.0 4864.0 4113.3 512.0 446.4 5 0.304 21 8.917 9.221
8192.0 8192.0 0.0 0.0 50688.0 50688.0 136704.0 136606.0 4864.0 4113.3 512.0 446.4 5 0.304 22 8.917 9.221
8192.0 8192.0 0.0 0.0 50688.0 50039.5 136704.0 136606.0 4864.0 4113.3 512.0 446.4 5 0.304 22 9.302 9.606
注意:图形界面用在测试的时候, 压测观察,而不是用在生产环境下定位OOM。
4. jstack 4869 :该命令会显示 所有的线程(线程名、线程编号、线程优先级、操作系统级的优先级、线程的状态、线程的调用堆栈)的信息。如:多个线程的状态一直是 waiting on condition <XXX> ,则说明程序可能产生了死锁。
如何找到哪个线程持有这把锁?
搜索 jstack dump 的信息,找<XXX>,看哪个线程持有这把锁 RUNNABLE。
[root@centos142 arthas]# jstack 4869
"pool-1-thread-45" #52 prio=5 os_prio=0 tid=0x00007fda34290000 nid=0x133d waiting on condition [0x00007fd9e2bea000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f82aef40> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:750)
"pool-1-thread-44" #51 prio=5 os_prio=0 tid=0x00007fda3428e000 nid=0x133c waiting on condition [0x00007fd9e2ceb000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f82c4af8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
5. 系统级命令,top、top - Hp 进程号,top:查看当前进程占内存情况、top - Hp 进程号:查看进程号中所有线程的内存、cup情况。
6. jmap 作用1:
a、jmap -histo 进程号: 对当前 jvm 的所有对象进行分析,查看哪些类有多少个对象,占多大字节。
b、jmap -histo 进程号 | head -20 :查看最占内存的前20个对象是哪些。
jmap 作用2:
jmap -dump:format=b,file=20230121.hprof 进程号 :产生一个java的整个堆内存的转储文件,对它进行分析。
注意:jmap是不可以在 生成环境下执行的,会产生一个很严重的后果:jmap会让 jvm 卡死在某个状态,线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)
1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件(不是很专业,因为多有监控,内存增长就会报警)
2:很多服务器备份(高可用) ,停掉这台服务器对其他服务器不影响
3:在线定位(一般小点儿公司用不到)
4:在测试环境中压测(产生类似内存增长问题,在堆还不是很大的时候进行转储)
JVM常见排查流程:
1. 使用 top 找出哪个进程占用 cpu 比较高。
2. 使用 top -Hp 进程号,找到这个进程中哪个线程占用 cpu 高。
3. 根据线程编号通过 jstack 查看调用链路。一般分为俩种情况:
a、vm GC
b、业务线程,找业务方法占用高的
4. 如果是 vm GC,查看日志,看看原因是什么。如:访问量压力大(扩容机器)、内存回收不掉。
5. 一般启动项目的时候会设置,当进程出现 OOM后,自动生成dump文件。通过工具对dump文件进行分析。
java -Xms200M -Xmx200M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError java文件或 jar包
以上所有命令,都可以被 arthas 替代,arthas更方便、更全面!,学习arthas地址:
Arthas 入门到实战(一)快速入门_明湖起风了的博客-CSDN博客