Java应用进程Crash问题排查

当Java应用因MonkeyKing故障产生OutOfMemoryError时,可能触发Linux的OOMKiller机制。该机制在系统内存耗尽时会选择并杀死高内存占用进程。通过dmesg命令可检查内核日志,分析为何进程被kill。默认情况下,堆外内存大小由-Xmx设置决定,可通过-XX:MaxDirectMemorySize进行限制,防止被OOMKiller终结。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

现象

在故障注入“MonkeyKing提供的Java产生OutOfMemoryError故障”场景后,突然发现应用进程被kill,在gc.log、应用自身log中看不到任何异常信息,也未生成hs_err_pid<pid>.log文件。

排查

查看系统日志 /var/log/message 或使用 dmesg 命令。

说明应用进程内存占用过高导致被Kill。

工具使用说明:

  1. dmesg

dmesg命令是用来在Unix-like系统中显示内核的相关信息的。dmesg全称是display message (or display driver),即显示信息。

实际上,dmesg命令是从内核环形缓冲区中获取数据的。当我们在Linux上排除故障时,dmesg命令会十分方便,它能很好地帮我们鉴别硬件相关的error和warning。除此之外,dmesg命令还能打印出守护进程相关的信息,已帮助我们debug。

  1. OOM(Out Of Memory) killer

作为Linux下的程序员,有时不得不面对一个问题,那就是系统内存被用光了,这时当进程再向内核申请内存时,内核会怎么办呢?程序里面调用的malloc函数会返回null吗?

为了处理内存不足时的问题,Linux内核发明了一种机制,叫OOM(Out Of Memory) killer,通过配置它可以控制内存不足时内核的行为。

当物理内存和交换空间都被用完时,如果还有进程来申请内存,内核将触发OOM killer,其行为如下:

1.检查文件/proc/sys/vm/panic_on_oom,如果里面的值为2,那么系统一定会触发panic

2.如果/proc/sys/vm/panic_on_oom的值为1,那么系统有可能触发panic(见后面的介绍)

3.如果/proc/sys/vm/panic_on_oom的值为0,或者上一步没有触发panic,那么内核继续检查文件/proc/sys/vm/oom_kill_allocating_task

3.如果/proc/sys/vm/oom_kill_allocating_task为1,那么内核将kill掉当前申请内存的进程

4.如果/proc/sys/vm/oom_kill_allocating_task为0,内核将检查每个进程的分数,分数最高的进程将被kill掉(见后面介绍)

进程被kill掉之后,如果/proc/sys/vm/oom_dump_tasks为1,且系统的rlimit中设置了core文件大小,将会由/proc/sys/kernel/core_pattern里面指定的程序生成core dump文件,这个文件里将包含

pid, uid, tgid, vm size, rss, nr_ptes, nr_pmds, swapents, oom_score_adj

score, name等内容,拿到这个core文件之后,可以做一些分析,看为什么这个进程被选中kill掉。

  1. 堆外内存默认是多大

如果我们没有通过-XX:MaxDirectMemorySize来指定最大的堆外内存,那么默认的最大堆外内存是多少呢,我们还是通过代码来分析

上面的代码里我们看到调用了sun.misc.VM.maxDirectMemory()

 private static long directMemory = 64 * 1024 * 1024;

    // Returns the maximum amount of allocatable direct buffer memory.
    // The directMemory variable is initialized during system initialization
    // in the saveAndRemoveProperties method.
    //
    public static long maxDirectMemory() {
        return directMemory;
    }

看到上面的代码之后是不是误以为默认的最大值是64M?其实不是的,说到这个值得从java.lang.System这个类的初始化说起

 /**
     * Initialize the system class.  Called after thread initialization.
     */
    private static void initializeSystemClass() {

        // VM might invoke JNU_NewStringPlatform() to set those encoding
        // sensitive properties (user.home, user.name, boot.class.path, etc.)
        // during "props" initialization, in which it may need access, via
        // System.getProperty(), to the related system encoding property that
        // have been initialized (put into "props") at early stage of the
        // initialization. So make sure the "props" is available at the
        // very beginning of the initialization and all system properties to
        // be put into it directly.
        props = new Properties();
        initProperties(props);  // initialized by the VM

        // There are certain system configurations that may be controlled by
        // VM options such as the maximum amount of direct memory and
        // Integer cache size used to support the object identity semantics
        // of autoboxing.  Typically, the library will obtain these values
        // from the properties set by the VM.  If the properties are for
        // internal implementation use only, these properties should be
        // removed from the system properties.
        //
        // See java.lang.Integer.IntegerCache and the
        // sun.misc.VM.saveAndRemoveProperties method for example.
        //
        // Save a private copy of the system properties object that
        // can only be accessed by the internal implementation.  Remove
        // certain system properties that are not intended for public access.
        sun.misc.VM.saveAndRemoveProperties(props);

       ......
       
        sun.misc.VM.booted();
    }

上面这个方法在jvm启动的时候对System这个类做初始化的时候执行的,因此执行时间非常早,我们看到里面调用了sun.misc.VM.saveAndRemoveProperties(props):

     public static void saveAndRemoveProperties(Properties props) {
        if (booted)
            throw new IllegalStateException("System initialization has completed");

        savedProps.putAll(props);

        // Set the maximum amount of direct memory.  This value is controlled
        // by the vm option -XX:MaxDirectMemorySize=<size>.
        // The maximum amount of allocatable direct buffer memory (in bytes)
        // from the system property sun.nio.MaxDirectMemorySize set by the VM.
        // The system property will be removed.
        String s = (String)props.remove("sun.nio.MaxDirectMemorySize");
        if (s != null) {
            if (s.equals("-1")) {
                // -XX:MaxDirectMemorySize not given, take default
                directMemory = Runtime.getRuntime().maxMemory();
            } else {
                long l = Long.parseLong(s);
                if (l > -1)
                    directMemory = l;
            }
        }

        // Check if direct buffers should be page aligned
        s = (String)props.remove("sun.nio.PageAlignDirectMemory");
        if ("true".equals(s))
            pageAlignDirectMemory = true;

        // Set a boolean to determine whether ClassLoader.loadClass accepts
        // array syntax.  This value is controlled by the system property
        // "sun.lang.ClassLoader.allowArraySyntax".
        s = props.getProperty("sun.lang.ClassLoader.allowArraySyntax");
        allowArraySyntax = (s == null
                               ? defaultAllowArraySyntax
                               : Boolean.parseBoolean(s));

        // Remove other private system properties
        // used by java.lang.Integer.IntegerCache
        props.remove("java.lang.Integer.IntegerCache.high");

        // used by java.util.zip.ZipFile
        props.remove("sun.zip.disableMemoryMapping");

        // used by sun.launcher.LauncherHelper
        props.remove("sun.java.launcher.diag");
    }

如果我们通过-Dsun.nio.MaxDirectMemorySize指定了这个属性,只要它不等于-1,那效果和加了-XX:MaxDirectMemorySize一样的,如果两个参数都没指定,那么最大堆外内存的值来自于directMemory = Runtime.getRuntime().maxMemory(),这是一个native方法

JNIEXPORT jlong JNICALL
Java_java_lang_Runtime_maxMemory(JNIEnv *env, jobject this)
{
    return JVM_MaxMemory();
}

JVM_ENTRY_NO_ENV(jlong, JVM_MaxMemory(void))
  JVMWrapper("JVM_MaxMemory");
  size_t n = Universe::heap()->max_capacity();
  return convert_size_t_to_jlong(n);
JVM_END

其中在我们使用CMS GC的情况下的实现如下,其实是新生代的最大值-一个survivor的大小+老生代的最大值,也就是我们设置的-Xmx的值里除去一个survivor的大小就是默认的堆外内存的大小了

原因分析及预防:

MonkeyKing提供了“Java产生OutOfMemoryError异常”的故障场景,制造OOM的方式其中一个方式就是通过占用堆外内存。应用可以通过-XX:MaxDirectMemorySize来设置堆外内存空间。

未限制堆外内存大小的情况下,很有可能进程占用的内存过大而触发Linux系统的OOM Killer机制,最终导致进程被kill。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值