常见OOM排查和处理

 

总结一些常见OOM

主要有以下几个OOM报错

目录

1.java.lang.OutOfMemoryError: Java heap space

 堆OOM原因:

排查和解决方案:

排查:

解决:

2.java.lang.OutOfMemoryError: GC overhead limit exceeded

3.java.lang.OutOfMemoryError:unable to create new native thread

解决方法:

4.OutOfMemoryError: Direct buffer memory 

5.java.lang.OutOfMemoryError: Metaspace


1.java.lang.OutOfMemoryError: Java heap space

首先在错误提示那已经提示了OOM发生在堆空间里面,因为在本地idea,所以为了模拟这种情况配置了VM Options :-Xms10m -Xmx10m -XX:+PrintGCDetails

为温故而知新,我还是解释一下各参数的含义 -Xms10m代表堆初始化大小为10m;-Xmx10m代表堆最大为10M; -XX:+PrintGCDetails代表打印日志,为什么加这个呢,因为我在本地运行,为了排查oom的前因后果,所以加这个命令查看jvm的垃圾回收情况。

一个小知识点:jvm的参数大部分都是-XX:+/-{KEY}或者是-XX:key=value形式,为什么出现-Xms,-Xmx,-Xss的命令?

其实-Xms等于-XX:InitialHeapSize;-Xmx 等同于-XX:MaxHeapSize;而-Xss等同于-XX:ThreadStackSize;

因此其实-X命令等于-XX命令。

另外多说一句,如果我们自己不设置的话 -Xms默认为系统内存的1/64,-Xmx默认为系统内存的1/4。

public class OutOfMemoryGCLimitExceed {

    public static void addRandomDataToMap() {
        Map<Integer, String> dataMap = new HashMap<>();
        Random r = new Random();
        while (true) {
            dataMap.put(r.nextInt(), String.valueOf(r.nextInt()));
        }
    }

    public static void main(String[] args) throws InterruptedException {
        addRandomDataToMap();
//        Thread.sleep(20000);

    }
}

于是乎,执行该程序时很快就报了堆的OOM。

先来看看报错信息

 

 堆OOM原因:

首先从打印的日志里面可以看到在jdk8里面,如果我们不配置垃圾回收器的话,默认选择的是parallel scavenger和parallel old 作为年轻代老年代的并行垃圾回收器。随着for循环不断new对象,可以看到先进行了一会分配对象失败(Allocation Failure)后,程序不停的进行了Full GC ,然后在报错的前夕,老年代已经接近10M,也就是因为对象的不停创建导致了老年代区不负重荷,最终导致了堆里面的OOM。

另外:虽然在本例子里面没有出现,但是这个也是需要考虑的一点是,有没有可能是因为发生内存泄漏导致的堆的OOM。

排查和解决方案:

排查:

1.在本地环境里面可以使用jmap和jvm参数方式-XX:+PrintGCDetails 查看内存和gc日志情况。

2.jmap使用:

   2.1 先jps -l 查询java程序的进程 获得pid

   2.2 然后可以使用jmap -histo:live pid显示内存存活的实例数和类;也可以使用jmap -dump:format=b,file=heapDump.phrof pid 打印出dump日志

 然而这种方式需要特别注意

1.使用jmap -histo:live pid时,会产生FullGC ,也就是说如果你在生产环境使用了这个命令,特别是对于一些大型公司而言,有可能因为FullGC而造成的问题就很大了,因此这个命令在生产环境的使用要慎之又慎

2.使用jmap -dump:format=b,file=heapDump.phrof pid时会将实时内存信息导出,如果heap比较大,那么就会很耗时,很可能会导致应用的暂停,因此使用这个命令需要慎之又慎。

因此我看到网上有另一种工具arthas(不过我没用过,需要你们自行了解)

3.那么另一种方式就是jvm参数,比如-XX:+HeapDumpOnOutOfMerroryError和配合-XX:HeapDumpPath的使用,会在OOM异常时导出heapDump文件;

或者使用-Xloggc:/opt/xxx/.../oom.log,-XX:+UseGCLogFileRotaion(如果日志记满了就循环写入到日志文件里),-XX:NumbersOfGCLogFiles=n,-XX:GCLogFileSize=20M,-XX:+PrintGCDetails,-XX:+PrintGCDateStamps,-XX:+PrintGCCause

解决:

1.简单粗暴,堆空间不够那就增加堆空间的大小,把-Xms和-Xmx扩大;

2.需要观察稳定运行期,FullGC后会不会有内存增大现象,会不会有内存泄露的情况

3.查看代码是否因为设计原因,导致很多垃圾对象产生。比如某些对象能不能不用每次都新建,而使用单例模式。对于长生命周期对象对象后续不用了,object=null可以辅助GC,一旦方法脱离了作用域,相应的局部变量应用就会被注销。

 

2.java.lang.OutOfMemoryError: GC overhead limit exceeded

代码跟第一个一样,但是我把jvm options改成了这样:-Xms300m -Xmx300m -XX:+PrintGCDetails

public class OutOfMemoryGCLimitExceed {

    public static void addRandomDataToMap() {
        Map<Integer, String> dataMap = new HashMap<>();
        Random r = new Random();
        while (true) {
            dataMap.put(r.nextInt(), String.valueOf(r.nextInt()));
        }
    }

    public static void main(String[] args) throws InterruptedException {
        addRandomDataToMap();
//        Thread.sleep(20000);

    }
}

 报错截图:

先来看看GC overhead limit exceeded发生的原因:

Oracle的官方解释

Exception in thread thread_name: java.lang.OutOfMemoryError: GC Overhead limit exceeded
Cause: The detail message "GC overhead limit exceeded" indicates that the garbage collector is running all the time and Java program is making very slow progress. After a garbage collection, if the Java process is spending more than approximately 98% of its time doing garbage collection and if it is recovering less than 2% of the heap and has been doing so far the last 5 (compile time constant) consecutive garbage collections, then a java.lang.OutOfMemoryError is thrown. This exception is typically thrown because the amount of live data barely fits into the Java heap having little free space for new allocations.
Action: Increase the heap size. The java.lang.OutOfMemoryError exception for GC Overhead limit exceeded can be turned off with the command line flag -XX:-UseGCOverheadLimit.

大概的意思是说,java进程使用了超过98%的时间来回收垃圾,却只回收了2%的垃圾。同时,官方文档也说了你可以使用  -XX:-UseGCOverheadLimit把这个报错给关闭

如果我们启用了这个jvm配置-XX:-UseGCOverheadLimit,再执行我们的程序,发现还是报了java.lang.OutOfMemoryError: Java heap space的错误,也就是说我们也可以用heap oom的解决方案去分析解决这个报错。

因此,这个错误其实是通知我们gc线程在不断的FullGC然后却回收了不到2%的垃圾,这个时候,我们可以去dump文件查看哪些对象占大部分空间,然后根据业务去进行分析。

3.java.lang.OutOfMemoryError:unable to create new native thread

造成这个原因是因为线程数太多超过了限制,报了错。

这个线程限制数的大小在不同的服务器可能有不同的设置,比如可以在linux环境下输入命令ulimit -u

在我的阿里云上这个限制数是7271

出现这个问题原因其实很有可能出现在高并发的情况下,由于一段时间内接口被不断调用,因而线程调用量或者每个接口内需要使用多线程处理数据导致线程数飙升,因此是否可以在某些重大活动面前增加节点,调配好适合的负载均衡策略也是解决这个问题的方法之一。

解决方法:

1.需要尽可能降低:需要分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低。

2.如果真的需要使用这么多的线程,那么就去设置服务器的最大线程数

4.OutOfMemoryError: Direct buffer memory 

这个异常其实我没有见过,因此,这个我是从网上学习总结。

这个是我写的一个小demo,jvm options是-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m。

public class DirectBufferOOMTest {
    public static void main(String[] args) {
        //设置directMerrory小点
        ByteBuffer b = ByteBuffer.allocateDirect(6*1024*1024);
    }
}

因为-XX:MaxDirectMemorySize=5m,而我创建了一个6m的对象分配在直接内存那,很明显就会报错了,来看看报错截图

 通常在nio的情况下时,使用ByteBuffer写入缓存,写出缓存。

而使用ByteBuffer.allocate(capacity),是把缓存写在jvm的内存里面,收到gc管理的,且需要内存拷贝因此速度较慢

而使用ByteBuffer.allocateDirect(capacity)则是把缓存写在操作系统本地内存,因此不用gc的管理,不需要内存拷贝速度很快。

 

5.java.lang.OutOfMemoryError: Metaspace

遇到metaspace的oom,再次温故而知新一下,在jdk1.8后,移除了永久代,换成了metaspace(元空间),首先得先说一句,就是metaspace也是在堆外,因此gc是无法管理的。

这次我在jvm options 加了oom时打印dump文件,接下来看看dump文件

JVM OPTIONS:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:\OwnCode\lamb\lambDump.phrof

 代码也是网上找的,模拟metaspace的错误

public class OutOfMerroryMetaSpace {
    static class innerClass{
        private String sex;
    }

    static class OOMTest {

    }

    public static void main(String[] args) {
        int i = 0;
        try {
            while (true) {
                i++;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMTest.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=" + i);
            e.printStackTrace();
        }


    }
}

 接下来看看dump文件

找到这个代理对象有1638个,总大小为80262。

那么我们就来看看metaspace用来存什么数据:

在方法区里面主要存放了类型的相关信息:如类名,访问修饰符,常量池,字段描述,方法描述等。

而经过cglib直接操作字节码运行时,则会生成大量的动态类,这些类就需要各自保存他们的方法描述,字段描述,类名等,从而占据了metaspace的空间。

 解决方法:

1.直接增加-XX:MaxMetaspaceSize的值

2.出现metaspace的OOM问题,一个很可能是常量池增加的太大,一个是因为使用了动态代理技术,经过dump文件分析,动态类太多,应该从代码里面去分析,是否需要这么多动态代理。

  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
当遇到内存不足(OOM)的问题时,可以采取以下步骤进行排查和解决: 1. 确认OOM错误:查看系统日志或应用程序日志,确认是否发生了OOM错误。通常,OOM错误会在日志中显示为"Out of memory"或"java.lang.OutOfMemoryError"等。 2. 分析内存使用情况:使用监控工具(如top、htop)或分析工具(如jstat、jmap)来观察系统或应用程序的内存使用情况。检查是否存在内存泄漏或者内存使用过高的情况。 3. 调整JVM参数:如果是Java应用程序发生OOM,可以尝试调整JVM参数来增加可用内存。常见的参数包括-Xmx(最大堆内存大小)和-Xms(初始堆内存大小),可以根据应用程序的需求进行调整。 4. 优化代码:检查应用程序的代码,确保没有存在内存泄漏或者不合理的内存使用。可以通过使用合适的数据结构、及时释放资源、避免大对象等方式来优化代码。 5. 增加服务器资源:如果以上方法无法解决OOM问题,可以考虑增加服务器的物理内存或者升级到更高配置的服务器。 6. 使用分布式系统:如果单台服务器无法满足应用程序的内存需求,可以考虑使用分布式系统,将应用程序分散到多台服务器上,从而充分利用集群的内存资源。 7. 调整应用程序逻辑:如果应用程序需要处理大量数据或者复杂计算,可以考虑优化算法或者分批处理数据,以减少内存的使用。 在解决OOM问题时,需要根据具体情况进行分析和调整。如果问题比较复杂,可以借助性能分析工具或者咨询专业的开发人员来进行排查和解决。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值