美文网首页
内存异常的那些事(一)

内存异常的那些事(一)

作者: 勤_ | 来源:发表于2021-07-20 16:20 被阅读0次

    概述

    接着上篇性能调优(三),通过一些实例继续分析性能问题的现象和原因,希望能给研发兄弟们一点点参考价值。

    工具及版本

    工具 版本
    Arthas 3.5.2
    perf 3.10.0-1160.31.1.el7.x86_64.debug
    JDK 1.8.0_221

    实例

    根据 JVM8 规范,JVM 运行时内存共分为虚拟机栈、堆、元空间、程序计数器、本地方法栈五个部分。还有一部分内存叫直接内存,属于操作系统的本地内存,也是可以直接操作的。接下来通过代码来演示下内存溢出的一些异常。

    11.png

    堆溢出

    • 编写以下代码验证

      /**
         * -Xmx32M -Xms32M
         * */
        @GetMapping("/heapOom")
        public String heapOomErr() {
            List<byte[]> list = new ArrayList<>();
            while(true){
                list.add(new byte[8*1024*1024]);
            }
        }
      
    • 启动虚拟机的时候配置-Xmx32M -Xms32M,触发溢出的代码,一会后报错如下:

      java.lang.OutOfMemoryError: Java heap space
        at com.lqd.jvm.monitor.MemoryController.heapOomErr(MemoryController.java:24) ~[classes/:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_251]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_251]
        at 
      
    • 解决方案:在保证没有内存泄漏的情况下,一般可以适当调整-Xmx -Xms。

    元空间溢出

    • 编写以下代码

      /**
         * -XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M
         * */
        @GetMapping("/nonheap")
        public String nonheap() {
            while(true) {
                classList.addAll(Metaspace.createClasses());
            }
        }
      
    • 启动虚拟机的时候配置 -XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M,触发溢出的代码,一会后报错如下:

      java.lang.OutOfMemoryError: Metaspace
        at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_251]
        at java.lang.ClassLoader.defineClass(ClassLoader.java:756) ~[na:1.8.0_251]
        at java.lang.ClassLoader.defineClass(ClassLoader.java:635) ~[na:1.8.0_251]
        at com.lqd.jvm.monitor.Metaspace.createClasses(Metaspace.java:39) ~[classes/:na]
        at com.lqd.jvm.monitor.MemoryController.nonheap(MemoryController.java:48) ~[classes/:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_251]
        at 
      
    • 解决方案:在保证没有内存泄漏的情况下,一般可以适当调整-XX:MetaspaceSize -XX:MaxMetaspaceSize。

    垃圾回收超时

    • 代码不变,这时设置-Xmx32M -Xms32M,触发/nonheap消耗元空间的内存空间,一会后报错如下,这是因为经过多次长时间的GC操作都无法回收,导致可用内存越来越少。JVM花费了98%的时间进行垃圾回收,而只得到2%可用的内存,频繁的进行内存回收(最起码已经进行了5次连续的垃圾回收),JVM就会曝出java.lang.OutOfMemoryError: GC overhead limit exceeded错误。

      java.lang.OutOfMemoryError: GC overhead limit exceeded
        at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_251]
        at java.lang.ClassLoader.defineClass(ClassLoader.java:756) ~[na:1.8.0_251]
        at java.lang.ClassLoader.defineClass(ClassLoader.java:635) ~[na:1.8.0_251]
        at com.lqd.jvm.monitor.Metaspace.createClasses(Metaspace.java:39) ~[classes/:na]
        at com.lqd.jvm.monitor.MemoryController.nonheap(MemoryController.java:48) ~[classes/:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_251]
      
    • 解决方案:检测代码干掉内存泄漏的代码。

    栈溢出

    • 编写以下代码

      /**
         * -Xss
         * */
        @GetMapping("/stackOom")
        public void stackOom() {
            int num =1 ;
            testStack();
        }
      
        private void testStack(){
            int i = 2,temp = 0;
            i = temp;
            temp = 2;
            this.testStack();
        }
      
    • 启动虚拟机的时候配置-Xss128k,触发溢出的代码,一会后报错如下:

      java.lang.StackOverflowError: null
        at com.lqd.jvm.monitor.MemoryController.testStack(MemoryController.java:57) ~[classes/:na]
        at com.lqd.jvm.monitor.MemoryController.testStack(MemoryController.java:57) ~[classes/:na]
        at com.lqd.jvm.monitor.MemoryController.testStack(MemoryController.java:57) ~[classes/:na]
        at com.lqd.jvm.monitor.MemoryController.testStack(MemoryController.java:57) ~[classes/:na]
        at com.lqd.jvm.monitor.MemoryController.testStack(MemoryController.java:57) ~[classes/:na]
      
    • 解决方案:在保证没有内存泄漏的情况下,一般可以适当调整-Xss。

    直接内存溢出

    • 编写以下代码

      /**
         * -XX:MaxDirectMemorySize
         * */
        @GetMapping("/directOom")
        public void directOom() {
            try {
                int i = 0 ;
                Field field = Unsafe.class.getDeclaredField("theUnsafe");
                field.setAccessible(true);
                Unsafe unsafe = (Unsafe) field.get(null);
                while(true){
                    i++ ;
                    unsafe.allocateMemory(1024 * 1024) ;
                }
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
      
    • 启动虚拟机,触发溢出的代码,一会后报错如下:

      java.lang.OutOfMemoryError: null
        at sun.misc.Unsafe.allocateMemory(Native Method) ~[na:1.8.0_251]
        at com.lqd.jvm.monitor.MemoryController.directOom(MemoryController.java:66) ~[classes/:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_251]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_251]
        at 
        
        #
      # There is insufficient memory for the Java Runtime Environment to continue.
      # Native memory allocation (malloc) failed to allocate 32744 bytes for ChunkPool::allocate
      # An error report file with more information is saved as:
      
    • 解决方案:在保证没有内存泄漏的情况下,一般可以适当调整-XX:MaxDirectMemorySize。

    参考

    cpu高的怎么回事(一)

    cpu高的怎么回事(二)

    cpu高的怎么回事(三)

    Arthas 安装

    pidstat

    相关文章

      网友评论

          本文标题:内存异常的那些事(一)

          本文链接:https://www.haomeiwen.com/subject/lrkrmltx.html