JVM 11-15周 OOM问题总结

641 阅读6分钟

内存溢出(OOM)

常见场景

  1. 元数据空间
  2. 线程私有的Java虚拟机栈
  3. 堆内存空间

Metaspace内存溢出

什么情况下会发生Metaspace内存溢出呢?

  1. 元数据空间参数设置过小 ,512mb一般足够了

    -XX:MetaspaceSize=512m
    -XX:MaxMetaspaceSize=512m
    
  2. 使用cglib之类的技术动态生成一些类,一旦代码中没有控制好,导致你生成的类过于多的时候,就很容易把Metaspace给塞满,进而引发内存溢出

模拟场景

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

 public static void main(String[] args) {

        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Car.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
                    throws Throwable {
                    if (method.getName().equals("run")) {
                        System.out.println("before run,do safety check");
                        return methodProxy.invokeSuper(o, objects);
                    }
                    return methodProxy.invokeSuper(o, objects);
                }
            });

            Car car = (Car)enhancer.create();
            car.run();
        }
    }

    static class Car {
        public void run() {
            System.out.println("car is running");
        }
    }

Caused by: java.lang.OutOfMemoryError: Metaspace

Java虚拟机栈内存溢出

无限制递归调用

模拟场景

-XX:ThreadStackSize=1m

  private static long count = 0;

    public static void main(String[] args) {
		work();
    }

    public static void work() {
        System.out.println("目前是第" + (++count) + "次方法调用");
        work();
    }

Exception in thread "main" java.lang.StackOverflowError

堆内存溢出

发生堆内存溢出的原因:

有限的内存中放了过多的对象,而且大多数都是存活的,此时即使GC过后还是大部分都存活,所以要继续放入更多对象已经不可能了,此时只能引发内存溢出问题。

一般来说发生内存溢出有两种主要的场景:

  1. 系统承载高并发请求,因为请求量过大,导致大量对象都是存活的,所以要继续放入新的对象实在是不行了,此时就会引发OOM系统崩渍

  2. 系统有内存泄漏的问题,就是莫名其妙弄了很多的对象,结果对象都是存活的,没有及时取消对他们的引用,导致触发GC还是无法回收,此时只能引发内存溢出,因为内存实在放不下更多对象了

    因此总结起来,一般引发OOM,要不然是系统负载过高,要不然就是有内存泄漏的问题

实战

在JVM内存溢出的时候自动dump内存快照

JVM本身在发生OOM之前都会尽可能的去进行GC腾出来一些内存空间。

OOM的发生并不是大家想的那样,突然之间内存太多了,JVM自己都没反应过来就直接崩溃了,并非如此。

JVM启动参数设置

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/usr/local/app/oom

JVM参数模板:

-Xms4096M-Xmx4096M-Xmn3072M  -Xss1M  -XX:MetaspaceSize=256M-XX:MaxMetaspaceSize=256M
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC  -XX:CMSInitiatingOccupancyFaction=92
-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSParallellnitialMarkEnabled -XX:+CMSScavengeBeforeRemark
-XX:+DisableExplicitGC  -XX:+PrintGCDetails -Xloggc:gc.log
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oom

Metaspace区域内存溢出的时候,应该如何解决

public static void main(String[] args) {

        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Car.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
                    throws Throwable {
                    if (method.getName().equals("run")) {
                        System.out.println("before run,do safety check");
                        return methodProxy.invokeSuper(o, objects);
                    }
                    return methodProxy.invokeSuper(o, objects);
                }
            });

            Car car = (Car)enhancer.create();
            car.run();
        }
    }

    static class Car {
        public void run() {
            System.out.println("car is running");
        }
    }

JVM启动参数设置:

-XX:+UseParNewGC -XX:+UseConcMarkSweepGC 
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m 
-XX:+PrintGCDetails -Xloggc:gc.log 
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./

./ 表示在当前项目的根目录下。

此时可以找到java_pid21896.hprof文件。使用MAT分析文件可知,我们利用cglib创建了太多类。

gc日志:

OpenJDK 64-Bit Server VM (25.252-b09) for windows-amd64 JRE (1.8.0_252-b09), built on Apr 22 2020 09:32:45 by "jenkins" with MS VC++ 12.0 (VS2013)
Memory: 4k page, physical 8045256k(1306212k free), swap 20823552k(4196040k free)
CommandLine flags: -XX:CompressedClassSpaceSize=2097152 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ -XX:InitialHeapSize=128724096 -XX:MaxHeapSize=2059585536 -XX:MaxMetaspaceSize=10485760 -XX:MaxNewSize=686469120 -XX:MaxTenuringThreshold=6 -XX:MetaspaceSize=10485760 -XX:OldPLABSize=16 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
    
0.551: [GC (Allocation Failure) 0.551: [ParNew: 33856K->2179K(38080K), 0.0025936 secs] 33856K->2179K(122752K), 0.0028536 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.771: [GC (Allocation Failure) 0.771: [ParNew: 36035K->1455K(38080K), 0.0059700 secs] 36035K->2699K(122752K), 0.0060435 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
0.955: [GC (Allocation Failure) 0.955: [ParNew: 35311K->1130K(38080K), 0.0324561 secs] 36555K->2375K(122752K), 0.0325557 secs] [Times: user=0.05 sys=0.00, real=0.03 secs] 
1.167: [GC (Allocation Failure) 1.167: [ParNew: 34983K->1755K(38080K), 0.0010659 secs] 36228K->3000K(122752K), 0.0011454 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

1.295: [Full GC (Metadata GC Threshold) 1.295: [CMS: 1244K->2877K(84672K), 0.0339938 secs] 23038K->2877K(122752K), [Metaspace: 9925K->9925K(1058816K)], 0.0341380 secs] [Times: user=0.05 sys=0.02, real=0.03 secs] 
1.329: [Full GC (Last ditch collection) 1.329: [CMS: 2877K->1887K(84672K), 0.0146346 secs] 2877K->1887K(122816K), [Metaspace: 9925K->9925K(1058816K)], 0.0147663 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 
1.345: [GC (CMS Initial Mark) [1 CMS-initial-mark: 1887K(84672K)] 1887K(122816K), 0.0002645 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
1.345: [CMS-concurrent-mark-start]
1.348: [CMS-concurrent-mark: 0.003/0.003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
1.348: [CMS-concurrent-preclean-start]
1.376: [CMS-concurrent-preclean: 0.001/0.028 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] 
1.378: [Full GC (Metadata GC Threshold) 1.378: [CMS (concurrent mode failure): 1887K->1880K(84672K), 0.0130663 secs] 3226K->1880K(122816K), [Metaspace: 9922K->9922K(1058816K)], 0.0131808 secs] [Times: user=0.02 sys=0.01, real=0.01 secs] 
1.391: [Full GC (Last ditch collection) 1.391: [CMS: 1880K->1880K(84672K), 0.0116271 secs] 1880K->1880K(122816K), [Metaspace: 9922K->9922K(1058816K)], 0.0117646 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
Heap
 par new generation   total 38144K, used 1000K [0x0000000085200000, 0x0000000087b60000, 0x00000000ae0a0000)
  eden space 33920K,   2% used [0x0000000085200000, 0x00000000852fa270, 0x0000000087320000)
  from space 4224K,   0% used [0x0000000087320000, 0x0000000087320000, 0x0000000087740000)
  to   space 4224K,   0% used [0x0000000087740000, 0x0000000087740000, 0x0000000087b60000)
 concurrent mark-sweep generation total 84672K, used 1880K [0x00000000ae0a0000, 0x00000000b3350000, 0x0000000100000000)
 Metaspace       used 9953K, capacity 10186K, committed 10240K, reserved 1058816K
  class space    used 852K, capacity 881K, committed 896K, reserved 1048576K

JVM栈内存溢出的时候,应该如何解决

栈内存溢出而言,我们定位和解决问题非常的简单,你只要把所有的异常都写入本地日志文件,那么当发现系统崩溃了,第一步就去日志里定位一下异常信息就知道了。

具体遇到OOM该怎么处理呢?

  1. 查看系统日志,里面会有详细的信息(定位到OOM发生在哪个内存区域)-> 主体人
  2. MAT分析工具分析dump文件
  3. 根据MAT分析结果去定位框架问题