一. JVM Out Of Memory 相关参数#
1
2
3
4
5
| -XX:+HeapDumpOnOutOfMemoryError
-XX:+HeapDumpPath
-XX:OnOutOfMemoryError
-XX:+ExitOnOutOfMemoryError
-XX:+CrashOnOutOfMemoryError
|
-XX:+HeapDumpOnOutOfMemoryError
和-XX:+HeapDumpPath
#
-XX:+HeapDumpOnOutOfMemoryError
代表在发生内存溢出时,生成堆转储文件. 一般配合 -XX:+HeapDumpPath
使用, 指定输出的目录或文件.
例如:
在发生内存溢出时,会生成如下文件:
同时, 会产生如下日志:
1
2
3
4
5
6
7
| ava.lang.OutOfMemoryError: Java heap space
Dumping heap to /Users/chen/dump-file/java_pid86050.hprof ...
Heap dump file created [179875659 bytes in 0.587 secs]
[2022-07-12 23:23:12.463] [http-nio-64222-exec-1] [TxId : , SpanId : ] [ERROR] c.r.common.config.webmvc.RestExceptionHandler - GlobalExceptionHandler handleException error
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1055)
....
|
-XX:OnOutOfMemoryError#
当发生内存溢出的时候,还可以让JVM调用任一个shell脚本,或者执行一段命令.
例如:
输出一段话到某文件
-XX:OnOutOfMemoryError="echo error>/Users/chen/dump-file/error.log"
输出日志如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| java.lang.OutOfMemoryError: Java heap space
Dumping heap to /Users/chen/dump-file/java_pid87778.hprof ...
Heap dump file created [179878014 bytes in 0.422 secs]
#
# java.lang.OutOfMemoryError: Java heap space
# -XX:OnOutOfMemoryError="echo out>/Users/chen/dump-file/error.log"
# Executing "echo out>/Users/chen/dump-file/error.log"...
[2022-07-12 23:29:56.487] [http-nio-64222-exec-1] [TxId : , SpanId : ] [ERROR] c.r.common.config.webmvc.RestExceptionHandler - GlobalExceptionHandler handleException error
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1055)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
...
|
-XX:+ExitOnOutOfMemoryError#
此参数在 jdk 1.8_92版本新增
当发生内存溢出的时候,JVM就会立马退出。如果你想在发生内存溢出的时候关闭应用那么可以使用这个参数。
-XX:+CrashOnOutOfMemoryError#
此参数在 jdk 1.8_92版本新增
当发生内存溢出的时候,JVM就会退出,同时,JVM会产生文本和二进制格式的崩溃日志
例如:
发生内存溢出后,产生如下日志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| Aborting due to java.lang.OutOfMemoryError: Java heap space
#
# A fatal error has been detected by the Java Runtime Environment:
#
# Internal Error (debug.cpp:308), pid=88993, tid=0x0000000000009d03
# fatal error: OutOfMemory encountered: Java heap space
#
# JRE version: OpenJDK Runtime Environment (8.0_292-b10) (build 1.8.0_292-b10)
# Java VM: OpenJDK 64-Bit Server VM (25.292-b10 mixed mode bsd-amd64 compressed oops)
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /Users/chen/hs_err_pid88993.log
#
# If you would like to submit a bug report, please visit:
# https://github.com/AdoptOpenJDK/openjdk-support/issues
#
Process finished with exit code 134 (interrupted by signal 6: SIGABRT)
|
同时产生文件 hs_err_pid88993.log
hs_err_pid88993.log
二. Out Of Memory 类型#
01.Java heap space:#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import java.util.ArrayList;
import java.util.List;
/**
* @author chen
*/
public class OutOfMem {
static List<String> list = new ArrayList<String>();
public static void main(String[] args) {
Integer[] array = new Integer[10000 * 10000];
}
}
|
添加JVM 参数: -Xmx50M
输出:
1
2
| Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.redstarclouds.sample.consumer.test.OutOfMem
|
02. GC Overhead limit exceeded#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* @author chen
*/
public class OutOfMem {
public static void main(String args[]) throws Exception {
Map m = new HashMap();
m = System.getProperties();
Random r = new Random();
while (true) {
m.put(r.nextInt(), "randomValue");
}
}
}
|
添加JVM 参数: -Xmx50M -XX:+UseParallelGC
一段时间后, 输出:
1
2
3
| Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.valueOf(Integer.java:832)
at com.redstarclouds.sample.consumer.test.OutOfMem.main(OutOfMem.java:18)
|
JVM花费了98%的时间进行垃圾回收,而只得到2%可用的内存,频繁的进行内存回收(最起码已经进行了5次连续的垃圾回收),JVM就会曝出java.lang.OutOfMemoryError: GC overhead limit exceeded错误。
添加JVM 参数: -XX:-UseGCOverheadLimit
后,可避免此异常. 这个方法只会把java.lang.OutOfMemoryError: GC overhead limit exceeded变成更常见的java.lang.OutOfMemoryError: Java heap space错误。
maven 引入:
1
2
3
4
5
6
7
8
9
10
11
12
| <dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
<scope>compile</scope>
</dependency>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| /**
* @author chen
*/
public class OutOfMem {
static javassist.ClassPool cp
= javassist.ClassPool.getDefault();
public static void main(String args[]) throws Exception
{
for (int i = 0; i < 100000; i++) {
Class c = cp.makeClass(
"com.saket.demo.Metaspace" + i)
.toClass();
}
}
}
|
添加JVM 参数: -Xmx500M -XX:MaxMetaspaceSize=32m
1
2
3
4
5
6
7
8
9
10
11
| Exception in thread "main" javassist.CannotCompileException: by java.lang.ClassFormatError: Metaspace
at javassist.util.proxy.DefineClassHelper.toClass(DefineClassHelper.java:271)
at javassist.ClassPool.toClass(ClassPool.java:1232)
at javassist.ClassPool.toClass(ClassPool.java:1090)
at javassist.ClassPool.toClass(ClassPool.java:1048)
at javassist.CtClass.toClass(CtClass.java:1290)
at com.redstarclouds.sample.consumer.test.OutOfMem.main(OutOfMem.java:18)
Caused by: java.lang.ClassFormatError: Metaspace
at javassist.util.proxy.DefineClassHelper$Java7.defineClass(DefineClassHelper.java:182)
at javassist.util.proxy.DefineClassHelper.toClass(DefineClassHelper.java:260)
... 5 more
|
04.Native thread#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| import java.util.ArrayList;
import java.util.List;
/**
* @author chen
*/
public class OutOfMem {
public static void main(String args[]) throws Exception
{
while (true) {
new Thread(new Runnable() {
public void run()
{
try {
Thread.sleep(1000000000);
}
catch (InterruptedException e) {
}
}
}).start();
}
}
}
|
JVM 参数: -Xmx512M
1
2
3
4
| Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:717)
at com.redstarclouds.sample.consumer.test.OutOfMem.main(OutOfMem.java:23)
|
三. 生产环境中的配置#
生产环境中,java部署使用的是JSW打包, 当服务宕机, 可自动拉起对应的进程服务, 所以shell脚本中, 只要kill对应的进程,并发送告警信息即可.
为了实现内存溢出后告警, 编写以下shell 脚本:
1
2
3
4
5
6
7
8
9
10
11
| kill -9 $1
# 钉钉机器人access token
access_token='XXX'
host_ip=`ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d "addr:"`
alert_message="警告! \n\n服务: $2 \nIP: $host_ip \n发生内存溢出,已kill进程并自动重启应用. 请进行人工排查和确认. "
curl "https://oapi.dingtalk.com/robot/send?access_token=${access_token}" \
-H "Content-Type: application/json" \
-d "{\"msgtype\": \"text\",\"text\": {\"content\":\"${alert_message}\"}}"
|
在服务中配置以下参数:
1
2
3
| -XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/logs/[对应日志目录]
-XX:OnOutOfMemoryError=/apps/kill_alarm.sh %p [java服务名称:例如 consumer-tank]
|
当发生内存溢出时,会kill, 并发送钉钉机器人消息:

GC & Out of Memory 配置#
1
2
3
4
5
6
7
8
9
10
11
12
| ## 需要添加的jvm参数
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintHeapAtGC
-XX:GCLogFileSize=100M
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-Xloggc:/logs/[对应日志目录]/gc_%t.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/logs/[对应日志目录]
-XX:OnOutOfMemoryError=/apps/kill_alarm.sh %p [java服务名称:例如pay-e-rest]
|
shell 脚本中编写遇到的问题#
使用kill -9 还是使用 kill -15?
kill -9 发出SIGKILL信号.
kill -15 发出SIGTERM信号.
单独kill或者kill -15是相同,单独使用kill时,默认就是kill -15,这种被公认为是优雅的退出。
在使用kill时,系统会发送一个对应的信号给对应的程序,当程序接到信号后,一般会做三个选择:1.立即停止。2.释放资源后停止。3.忽略该信号。
因为kill只是通知对应的程序,要进行安全干净的退出,如果退出的过程中遇到阻碍,程序就会忽略该命令。
举个可以更好帮助理解的例子,但是例子不与kill是同一回事,只是辅助理解,类似我们在windows系统下,有一个文件打开状态,我们对文件进行重命名,是无法修改成功的,可以理解为忽略了修改,只有关掉这个文件,可以理解为释放资源后修改。可以说它会有一个“准备的时间”。
而kill -9相对来说就没有这个“准备时间”,带有强制性,可能会造成一些数据丢失等现象。
因为kill -15 会导致jsw 全部退出, 无法自动重启, 所以使用了SIGKILL
Out Of Memory Error 常见问题#
问: 发生内存溢出, 服务是否不可用.
答: 不一定, 一般发生内存溢出,服务依然可用. 主要看发生内存溢出的线程如何处理内存溢出异常.
Understanding out of memory error
Linux kill、kill-15、kill-9区别