《深入理解JVM虚拟机》读书笔记-内存溢出异常

jvm中唯一一个没有OOM的地方:

程序计数器

Java堆溢出

创造heap溢出的条件

  • Java堆用于储存对象实例

  • 不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象

  • 随着对象数量的增加,总容量触及最大堆的容量限制后就会产生内存溢出异常。

Java堆内存溢出异常测试


/**

 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

 * @author zzm

 */

public class HeapOOM {

    static class OOMObject {

    }

    public static void main(String[] args) {

        List<OOMObject> list = new ArrayList<OOMObject>();

        while (true) {

            list.add(new OOMObject());

        }

    }

}

运行结果:


java.lang.OutOfMemoryError: Java heap space

Dumping heap to java_pid3404.hprof ...

Heap dump file created [22045981 bytes in 0.663 secs]

Java堆内存的OutOfMemoryError异常是实际应用中最常见的内存溢出异常情况。出现Java堆内存溢出时,异常堆栈信息“java.lang.OutOfMemoryError”会跟随进一步提示“Java heap space”。

排查手段

要解决这个内存区域的异常,常规的处理方法是首先通过内存映像分析工具(如Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析。

  • 第一步首先应确认内存中导致OOM的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。

  • 如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,找到泄漏对象是通过怎样的引用路径、与哪些GC Roots相关联,才导致垃圾收集器无法回收它们,根据泄漏对象的类型信息以及它到GC Roots引用链的信息,一般可以比较准确地定位到这些对象创建的位置,进而找出产生内存泄漏的代码的具体位置。

  • 如果不是内存泄漏,换句话说就是内存中的对象确实都是必须存活的,那就应当检查Java虚拟机的堆参数(-Xmx与-Xms)设置,与机器的内存对比,看看是否还有向上调整的空间。

    • 再从代码上检查是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运行期的内存消耗。

虚拟机栈和本地方法栈溢出

由于HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说,-Xoss参数(设置本地方法栈大小)虽然存在,但实际上是没有任何效果的,栈容量只能由-Xss参数来设定。


关于虚拟机栈和本地方法栈,在《Java虚拟机规范》中描述了两种异常:

  • 如果<font color=red>线程请求的栈深度大于虚拟机所允许的最大深度</font>,将抛出StackOverflowError异常。

  • 如果虚拟机的<font color=red>栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时</font>,将抛出OutOfMemoryError异常。


HotSpot虚拟机的异常

《Java虚拟机规范》明确允许Java虚拟机实现自行选择是否支持栈的动态扩展,而HotSpot虚拟机的选择是不支持扩展

  • 所以除非在创建线程申请内存时就因无法获得足够内存而出现OutOfMemoryError异常,否则在线程运行时是<font color=red>不会因为扩展而导致内存溢出的</font>。

  • 只会因为栈容量无法容纳新的栈帧而导致StackOverflowError异常。

验证

用例1

使用-Xss参数减少栈内存容量。

结果:抛出StackOverflowError异常,异常出现时输出的堆栈深度相应缩小。


/**

 * VM Args:-Xss128k

 * @author zzm

 */

public class JavaVMStackSOF {

    private int stackLength = 1;

    public void stackLeak() {

        stackLength++;

        stackLeak();

    }

     public static void main(String[] args) throws Throwable {

        JavaVMStackSOF oom = new JavaVMStackSOF();

        try {

            oom.stackLeak();

        } catch (Throwable e) {

            System.out.println("stack length:" + oom.stackLength);

            throw e;

        }

    }

}

运行结果:


stack length:2402

Exception in thread "main" java.lang.StackOverflowError

    at org.fenixsoft.oom. JavaVMStackSOF.leak(JavaVMStackSOF.java:20)

    at org.fenixsoft.oom. JavaVMStackSOF.leak(JavaVMStackSOF.java:21)

    at org.fenixsoft.oom. JavaVMStackSOF.leak(JavaVMStackSOF.java:21)

……后续异常堆栈信息省略

  对于不同版本的Java虚拟机和不同的操作系统,栈容量最小值可能会有所限制,这主要取决于操作系统内存分页大小。譬如上述方法中的参数-Xss128k可以正常用于32位Windows系统下的JDK 6,但是如果用于64位Windows系统下的JDK 11,则会提示栈容量最小不能低于180K,而在Linux下这个值则可能是228K,如果低于这个最小限制,HotSpot虚拟器启动时会给出如下提示:


The Java thread stack size specified is too small. Specify at least 228k

用例2

定义了大量的本地变量,增大此方法帧中本地变量表的长度。

结果:抛出StackOverflowError异常,异常出现时输出的堆栈深度相应缩小。

首先,对第一种情况进行测试,具体如代码清单2-4所示。


/**

 * @author zzm

 */

public class JavaVMStackSOF {

    private static int stackLength = 0

    private static int stackLength = 0;

    public static void test() {

        long unused1, unused2, unused3, unused4, unused5,

             unused6, unused7, unused8, unused9, unused10,

             unused11, unused12, unused13, unused14, unused15,

             unused16, unused17, unused18, unused19, unused20,

             unused21, unused22, unused23, unused24, unused25,

             unused26, unused27, unused28, unused29, unused30,

             unused31, unused32, unused33, unused34, unused35,

             unused36, unused37, unused38, unused39, unused40,

             unused41, unused42, unused43, unused44, unused45,

             unused46, unused47, unused48, unused49, unused50,

             unused51, unused52, unused53, unused54, unused55,

             unused56, unused57, unused58, unused59, unused60,

             unused61, unused62, unused63, unused64, unused65,

             unused66, unused67, unused68, unused69, unused70,

             unused71, unused72, unused73, unused74, unused75,

             unused76, unused77, unused78, unused79, unused80,

             unused81, unused82, unused83, unused84, unused85,

             unused86, unused87, unused88, unused89, unused90,

             unused91, unused92, unused93, unused94, unused95,

             unused96, unused97, unused98, unused99, unused100;

        stackLength ++;

        test();

        unused1 = unused2 = unused3 = unused4 = unused5 =

        unused6 = unused7 = unused8 = unused9 = unused10 =

        unused11 = unused12 = unused13 = unused14 = unused15 =

        unused16 = unused17 = unused18 = unused19 = unused20 =

        unused21 = unused22 = unused23 = unused24 = unused25 =

        unused26 = unused27 = unused28 = unused29 = unused30 =

        unused31 = unused32 = unused33 = unused34 = unused35 =

        unused36 = unused37 = unused38 = unused39 = unused40 =

        unused41 = unused42 = unused43 = unused44 = unused45 =

        unused46 = unused47 = unused48 = unused49 = unused50 =

        unused51 = unused52 = unused53 = unused54 = unused55 =

        unused56 = unused57 = unused58 = unused59 = unused60 =

        unused61 = unused62 = unused63 = unused64 = unused65 =

        unused66 = unused67 = unused68 = unused69 = unused70 =

        unused71 = unused72 = unused73 = unused74 = unused75 =

        unused71 = unused72 = unused73 = unused74 = unused75 =

        unused76 = unused77 = unused78 = unused79 = unused80 =

        unused81 = unused82 = unused83 = unused84 = unused85 =

        unused86 = unused87 = unused88 = unused89 = unused90 =

        unused91 = unused92 = unused93 = unused94 = unused95 =

        unused96 = unused97 = unused98 = unused99 = unused100 = 0;

    }

    public static void main(String[] args) {

        try {

            test();

        }catch (Error e){

            System.out.println("stack length:" + stackLength);

            throw e;

        }

    }

}

运行结果


stack length:5675

Exception in thread "main" java.lang.StackOverflowError

    at org.fenixsoft.oom. JavaVMStackSOF.leak(JavaVMStackSOF.java:27)

    at org.fenixsoft.oom. JavaVMStackSOF.leak(JavaVMStackSOF.java:28)

    at org.fenixsoft.oom. JavaVMStackSOF.leak(JavaVMStackSOF.java:28)

……后续异常堆栈信息省略

实验结果表明:

<font color=red>无论是由于栈帧太大还是虚拟机栈容量太小,当新的栈帧内存无法分配的时候,HotSpot虚拟机抛出的都是StackOverflowError异常</font>。

元空间溢出

Java 8及之后的版本使用Metaspace来替代永久代。

Metaspace是方法区在Hotspot 中的实现,它与持久代最大的区别在于:Metaspace并不在虚拟机内存中而是使用本地内存也即在Java8中, classe metadata(the virtual machines internal presentation of Java class),被存储在叫做Metaspace native memory。


Metaspace存放了以下信息:

  • 虚拟机加载的类信息

  • 常量池

  • 静态变量

  • 即时编译后的代码


模拟Metaspace空间溢出

模拟Metaspace空间溢出,我们借助CGLib直接操作字节码运行时不断生成类往元空间灌,类占据的空间总是会超过Metaspace指定的空间大小的。

测试类


package com.pl.jvm.metaspace;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;

import org.springframework.cglib.proxy.MethodInterceptor;

import org.springframework.cglib.proxy.MethodProxy;

/**

 * <p>

 *

 * @Description: TODO

 * </p>

 * @ClassName MetaSpaceDemo

 * @Author pl

 * @Date 2021/7/7

 * @Version V1.0.0

 */

public class OOMEMetaspaceDemo {

    // 静态类

    static class OOMObject {}

    /**

     * -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m

     *

     * @param args

     */

    public static void main(final String[] args) {

        // 模拟计数多少次以后发生异常

        int i =0;

        try {

            while (true) {

                i++;

                // 使用Spring的动态字节码技术

                Enhancer enhancer = new Enhancer();

                enhancer.setSuperclass(OOMObject.class);

                enhancer.setUseCache(false);

                enhancer.setCallback(new MethodInterceptor() {

                    @Override

                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

                        return methodProxy.invokeSuper(o, args);

                    }

                });

                enhancer.create();

            }

        } catch (Throwable e) {

            System.out.println("第几次发生异常::" + i);

            e.printStackTrace();

        } finally {

        }

    }

}

输出


第542次发生异常:

java.lang.OutOfMemoryError: Metaspace

    at java.lang.Class.forName0(Native Method)

    at java.lang.Class.forName(Class.java:348)

    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:563)

    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)

    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:582)

    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:131)

    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)

    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:569)

    at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:384)

    at com.pl.jvm.metaspace.OOMEMetaspaceDemo.main(OOMEMetaspaceDemo.java:45)

HotSpot提供了一些参数作为元空间的防御措施,主要包括:

  • -XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。

  • -XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该值。

  • -XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。

  • -XX:Max-MetaspaceFreeRatio,用于控制最大的元空间剩余容量的百分比。

本机直接内存溢出

  直接内存(Direct Memory)的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不去指定,则<font color=red>默认与Java堆最大值(由-Xmx指定)一致</font>。

一般直接内存溢出的原因

  写NIO程序经常使用ByteBuffer来读取或者写入数据,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避兔了在Java堆和Native堆中来回复制数据。

  • ByteBuffer.allocate(capability) 第一种方式是分配VM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢。

  • ByteBuffer.allocateDirect(capability) 第二种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快。

  如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序就直接崩溃了。

Direct buffer memory


import java.nio.ByteBuffer;

import java.util.concurrent.TimeUnit;

public class OOMEDirectBufferMemoryDemo {

    /**

     * -Xms5m -Xmx5m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

     *

     * @param args

     * @throws InterruptedException

     */

    public static void main(String[] args) throws InterruptedException {

        System.out.println(String.format("配置的maxDirectMemory: %.2f MB",//

                sun.misc.VM.maxDirectMemory() / 1024.0 / 1024));

        TimeUnit.SECONDS.sleep(3);

        ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);

    }   

}

输出


[GC (Allocation Failure) [PSYoungGen: 1024K->504K(1536K)] 1024K->772K(5632K), 0.0014568 secs] [Times: user=0.09 sys=0.00, real=0.00 secs]

配置的maxDirectMemory: 5.00 MB

[GC (System.gc()) [PSYoungGen: 622K->504K(1536K)] 890K->820K(5632K), 0.0009753 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[Full GC (System.gc()) [PSYoungGen: 504K->0K(1536K)] [ParOldGen: 316K->725K(4096K)] 820K->725K(5632K), [Metaspace: 3477K->3477K(1056768K)], 0.0072268 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

Exception in thread "main" Heap

 PSYoungGen      total 1536K, used 40K [0x00000000ffe00000, 0x0000000100000000, 0x0000000100000000)

  eden space 1024K, 4% used [0x00000000ffe00000,0x00000000ffe0a3e0,0x00000000fff00000)

  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)

  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)

 ParOldGen       total 4096K, used 725K [0x00000000ffa00000, 0x00000000ffe00000, 0x00000000ffe00000)

  object space 4096K, 17% used [0x00000000ffa00000,0x00000000ffab5660,0x00000000ffe00000)

 Metaspace       used 3508K, capacity 4566K, committed 4864K, reserved 1056768K

  class space    used 391K, capacity 394K, committed 512K, reserved 1048576K

java.lang.OutOfMemoryError: Direct buffer memory

    at java.nio.Bits.reserveMemory(Bits.java:694)

    at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)

    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)

    at com.lun.jvm.OOMEDirectBufferMemoryDemo.main(OOMEDirectBufferMemoryDemo.java:20)

生成 ByteBuffer 的时候调用了 allication 方法,按下面一步接一步的调用,可以看到 System.gc() :

image.png

image.png

image.png

而上述参数:-XX:+DisableExplicitGC 会使 System.gc() 失效,因此当某些 nio 的引用到了 old 区又没发生 full gc 的时候,就会出现标题的 memory 异常了。

解决办法

1)检查是否直接或间接使用了 nio ,例如手动调用生成 buffer 的方法或者使用了 nio 容器如 netty, jetty, tomcat 等等;

2)-XX:MaxDirectMemorySize 加大,该参数默认是 64M ,可以根据需求调大试试;

3)检查 JVM 参数里面有无: -XX:+DisableExplicitGC ,如果有就去掉.

OutOfMemoryError


/**

 * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M

 * @author zzm

 */

public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws Exception {

        Field unsafeField = Unsafe.class.getDeclaredFields()[0];

        unsafeField.setAccessible(true);

        Unsafe unsafe = (Unsafe) unsafeField.get(null);

        while (true) {

            unsafe.allocateMemory(_1MB);

        }

    }

}

运行结果


Exception in thread "main" java.lang.OutOfMemoryError

    at sun.misc.Unsafe.allocateMemory(Native Method)

    at org.fenixsoft.oom.DMOOM.main(DMOOM.java:20)

  例子中的代码越过了DirectByteBuffer类直接通过反射获取Unsafe实例进行内存分配,(Unsafe类的getUnsafe()方法指定只有引导类加载器才会返回实例,体现了设计者希望只有虚拟机标准类库里面的类才能使用Unsafe的功能,在JDK 10时才将Unsafe的部分功能通过VarHandle开放给外部使用),因为虽然使用DirectByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配就会在代码里手动抛出溢出异常,真正申请分配内存的方法是Unsafe::allocateMemory()。

  如果直接内存导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见有什么明显的异常情况,如果发现内存溢出之后产生的Dump文件很小,而程序中又直接或间接使用了DirectMemory(典型的间接使用就是NIO),那就可以考虑重点检查一下直接内存方面的原因了。

补充

StackOverflowError和OutofMemoryError

在《java虚拟机规范》规范中,对本地方法和虚拟机栈的使用中规定了两个异常:StackOverflowError和OutofMemoryError

StackoverFlowError

  • java.lang.StackOverflowError

OutofMemoryError

  • java.lang.OutOfMemoryError:java heap space
  • java.lang.OutOfMemoryError:GC overhead limit exceeeded
  • java.lang.OutOfMemoryError:Direct buffer memory
  • java.lang.OutOfMemoryError:unable to create new native thread
  • java.lang.OutOfMemoryError:Metaspace

对于上面中没有提到的异常进行补充

GC overhead limit exceeeded

GC overhead limit exceeded

超出GC开销限制

GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC 都只回收了不到2%的极端情况下才会抛出。

假如不抛出GC overhead limit错误会发生什么情况呢?那就是GC清理的这么点内存很快会再次填满,迫使cc再次执行。这样就形成恶性循环,CPU使用率一直是100%,而Gc却没有任何成果

image.png

import java.util.ArrayList;

import java.util.List;

public class OOMEGCOverheadLimitExceededDemo {

    /**

     * 

     * -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m

     * 

     * @param args

     */

    public static void main(String[] args) {

        int i = 0;

        List<String> list = new ArrayList<>();

        try {

            while(true) {

                list.add(String.valueOf(++i).intern());

            }

        } catch (Exception e) {

            System.out.println("***************i:" + i);

            e.printStackTrace();

            throw e;

        }

    }

}

输出


[GC (Allocation Failure) [PSYoungGen: 2048K->498K(2560K)] 2048K->1658K(9728K), 0.0033090 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

[GC (Allocation Failure) [PSYoungGen: 2323K->489K(2560K)] 3483K->3305K(9728K), 0.0020911 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

[GC (Allocation Failure) [PSYoungGen: 2537K->496K(2560K)] 5353K->4864K(9728K), 0.0025591 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

[GC (Allocation Failure) [PSYoungGen: 2410K->512K(2560K)] 6779K->6872K(9728K), 0.0058689 secs] [Times: user=0.09 sys=0.00, real=0.01 secs] 

[Full GC (Ergonomics) [PSYoungGen: 512K->0K(2560K)] [ParOldGen: 6360K->6694K(7168K)] 6872K->6694K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0894928 secs] [Times: user=0.42 sys=0.00, real=0.09 secs] 

[Full GC (Ergonomics) [PSYoungGen: 2048K->1421K(2560K)] [ParOldGen: 6694K->6902K(7168K)] 8742K->8324K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0514932 secs] [Times: user=0.34 sys=0.00, real=0.05 secs] 

[Full GC (Ergonomics) [PSYoungGen: 2048K->2047K(2560K)] [ParOldGen: 6902K->6902K(7168K)] 8950K->8950K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0381615 secs] [Times: user=0.13 sys=0.00, real=0.04 secs] 

...省略89行...

[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7044K->7044K(7168K)] 9092K->9092K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0360935 secs] [Times: user=0.25 sys=0.00, real=0.04 secs] 

[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7046K->7046K(7168K)] 9094K->9094K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0360458 secs] [Times: user=0.38 sys=0.00, real=0.04 secs] 

[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7048K->7048K(7168K)] 9096K->9096K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0353033 secs] [Times: user=0.11 sys=0.00, real=0.04 secs] 

    进行了几次gc,  gc回收之前 2047  回收之后还是2047       老年代   7050K->7048K(7168K)              Metaspace: 2670K->2670K(1056768K)

***************i:147041

[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7050K->7048K(7168K)] 9098K->9096K(9728K), [Metaspace: 2670K->2670K(1056768K)], 0.0371397 secs] [Times: user=0.22 sys=0.00, real=0.04 secs] 

java.lang.OutOfMemoryError: GC overhead limit exceeded

[Full GC (Ergonomics)     at java.lang.Integer.toString(Integer.java:401)

[PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7051K->7050K(7168K)] 9099K->9097K(9728K), [Metaspace: 2676K->2676K(1056768K)], 0.0434184 secs] [Times: user=0.38 sys=0.00, real=0.04 secs] 

    at java.lang.String.valueOf(String.java:3099)

    at com.lun.jvm.OOMEGCOverheadLimitExceededDemo.main(OOMEGCOverheadLimitExceededDemo.java:19)

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

[Full GC (Ergonomics) [PSYoungGen: 2047K->0K(2560K)] [ParOldGen: 7054K->513K(7168K)] 9102K->513K(9728K), [Metaspace: 2677K->2677K(1056768K)], 0.0056578 secs] [Times: user=0.11 sys=0.00, real=0.01 secs] 

    at java.lang.Integer.toString(Integer.java:401)

    at java.lang.String.valueOf(String.java:3099)

    at com.lun.jvm.OOMEGCOverheadLimitExceededDemo.main(OOMEGCOverheadLimitExceededDemo.java:19)

Heap

 PSYoungGen      total 2560K, used 46K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)

  eden space 2048K, 2% used [0x00000000ffd00000,0x00000000ffd0bb90,0x00000000fff00000)

  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)

  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)

 ParOldGen       total 7168K, used 513K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)

  object space 7168K, 7% used [0x00000000ff600000,0x00000000ff6807f0,0x00000000ffd00000)

 Metaspace       used 2683K, capacity 4486K, committed 4864K, reserved 1056768K

  class space    used 285K, capacity 386K, committed 512K, reserved 1048576K

### unable to create new native thread

不能够创建更多的新的线程了,也就是说创建线程的上限达到了

高并发请求服务器时,经常会出现异常java.lang.OutOfMemoryError:unable to create new native thread,准确说该native thread异常与对应的平台有关

导致原因:

  • 应用创建了太多线程,一个应用进程创建多个线程,超过系统承载极限
  • 服务器并不允许你的应用程序创建这么多线程,linux系统默认运行单个进程可以创建的线程为1024个,如果应用创建超过这个数量,就会报 java.lang.OutOfMemoryError:unable to create new native thread

解决方法:

  1. 想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低
  2. 对于有的应用,确实需要创建很多线程,远超过linux系统默认1024个线程限制(一般到900多就差不多了挂了),可以通过修改Linux服务器配置,扩大linux默认限制

public class OOMEUnableCreateNewThreadDemo {

    public static void main(String[] args) {

        for (int i = 0; ; i++) {

            System.out.println("************** i = " + i);

            new Thread(() -> {

                try {

                    TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }, String.valueOf(i)).start();

        }

    }

}

#### unable to create new native thread上限调整

非root用户登录Linux系统(CentOS)测试

服务器级别调参调优

查看系统线程限制数目


ulimit -u

修改系统线程限制数目


vim /etc/security/limits.d/90-nproc.conf

打开后发现除了root,其他账户都限制在1024个

image.png

假如我们想要张三这个用卢运行,希望他生成的线程多一些,我们可以如下配置


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容