OOM排查和处理 - Java技术债务

1、OOM异常:java.lang.OutOfMemoryError: Java heap space

  • Xms10m代表堆初始化大小为10m;
  • Xmx10m代表堆最大为10M;
  • XX:+PrintGCDetails代表打印日志
  • Xms等于-XX:InitialHeapSize;-Xmx 等同于-XX:MaxHeapSize;而-Xss等同于-XX:ThreadStackSize;

如果不配垃圾回收器,系统默认使用parallel scavenger和parallel old作为老年代垃圾回收器,

堆OOM的原因:

  • 1、分配的对象或者说需要的内存大于配置的内存,内存溢出
  • 2、内存泄漏导致堆OOM

排查方案:

  • 1、在本地环境可以使用jmap和jvm参数配置方式-xx:PrintGCDetails查看内存和GC情况。

  • 2、jmap使用

    首先使用jmap -l查询进程PID 其次根据进程PID使用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比较大,那么就会很耗时,很可能会导致应用的暂停,因此使用这个命令需要慎之又慎。

3、还有另一种工具arthas

4、-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、OOM异常:java.lang.OutOfMemoryError: GC overhead limit exceeded

java进程使用了超过98%的时间来回收垃圾,却只回收了2%的垃圾。同时,官方文档也说了你可以使用 -XX:-UseGCOverheadLimit把这个报错给关闭。通知我们gc线程在不断的FullGC然后却回收了不到2%的垃圾,这个时候,我们可以去dump文件查看哪些对象占大部分空间,然后根据业务去进行分析。

3、OOM异常:java.lang.OutOfMemoryError:unable to create new native thread

造成这个原因是因为线程数太多超过了限制,报了错。 这个线程限制数的大小在不同的服务器可能有不同的设置,比如可以在linux环境下输入命令ulimit -u

原因:

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

解决方案:

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

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

4、OOM异常:OutOfMemoryError: Direct buffer memory

jvm options是-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m。

创建直接内存大于5M会报错

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

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

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

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

5、OOM异常: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用来存什么数据: 在方法区里面主要存放了类型的相关信息:如类名,访问修饰符,常量池,字段描述,方法描述等。

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

解决方法:

  • 直接增加-XX:MaxMetaspaceSize的值
  • 出现metaspace的OOM问题,一个很可能是常量池增加的太大,一个是因为使用了动态代理技术,经过dump文件分析,动态类太多,应该从代码里面去分析,是否需要这么多动态代理。
   登录后才可以发表评论呦...

专注分享Java技术干货,包括
但不仅限于多线程、JVM、Spring Boot
Spring Cloud、 Redis、微服务、
消息队列、Git、面试题 最新动态等。

想交个朋友吗
那就快扫下面吧


微信

Java技术债务

你还可以关注我的公众号

会分享一些干货或者好文章

Java技术债务