一. 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使用, 指定输出的目录或文件.

例如: 在发生内存溢出时,会生成如下文件: heap_dump 同时, 会产生如下日志:

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错误。

03.Metaspace

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 脚本中编写遇到的问题

  1. 使用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 常见问题

  1. 问: 发生内存溢出, 服务是否不可用.

    答: 不一定, 一般发生内存溢出,服务依然可用. 主要看发生内存溢出的线程如何处理内存溢出异常.

参考

Understanding out of memory error

Linux kill、kill-15、kill-9区别