内存溢出(OOM)通常出现在某一块内存空间耗尽的时候,导致内存溢出的原因有很多,常见的有堆溢出、直接内存溢出、永久区溢出等。

堆溢出

堆是Java程序中最为重要的内存空间,由于大量的对象都直接分配在堆上,因此它也成为最有可能发生溢出的区间。一般来说,绝大部分Java的内存溢出都属于这种情况。其原因是因为大量对象占据了堆空间,而这些对象都持有强引用,导致无法回收,当对象大小之和大于由Xmx参数指定的堆空间大小时,溢出错误就自然而然地发生了。

【示例】ArrayList对象持有byte数组的强引用,导致数据无法回收。 -Xms5m -Xmx20m

public class SimpleHeapOOM {public static void main(String[] args) {List list = new ArrayList();for (int i = 0; i < 1024; i++) {list.add(new byte[1024 * 1024]);}}
}

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat com.example.demo.jvm.SimpleHeapOOM.main(SimpleHeapOOM.java:16)

为了缓解堆溢出错误,一方面可以使用-Xmx参数指定一个更大的堆空间,另一方面,由于堆空间不可能无限增长,通过下文提到的MAT或者Visual VM等工具,分析找到大量占用堆空间的对象,并在应用程序上做出合理的优化也是十分必要的。

直接内存溢出

在Java的NIO中,支持直接内存的使用,可以使用Java代码获得一块堆外内存空间,这块空间是直接向操作系统申请的。直接内存的申请速度比堆内存慢,但是其访问速度要快于堆内存。因此,对于那些可复用的,并且会被经常访问的空间,使用直接内存是可以提高系统性能的。但由于直接内存没有被Java虚拟机完全托管,若使用不当,也容易触发直接内存溢出,导致宕机。

【示例】下面的代码不断地申请直接内存,并最终可能导致内存溢出

public class DirectBufferOOM {public static void main(String[] args) {for (int i = 0; i < 10240; i++) {ByteBuffer.allocateDirect(1024 * 1024);System.out.println(i);
//            System.gc();}}
}

注意代码第6行,System.gc(暂时被注释掉,也就是不会显式触发GC。接着,在Windows平台上,使用32位Java虚拟机,根据以下参数运行上述代码:-Xmx1G -XX:+PrintGCDetails

不用多久,程序就会因为内存溢出而退出,部分打印信息如下:

Exception in thread "main" java. lang . OutOfMemoryError
at java.nio. DirectByteBuffer. <init> (DirectByteBuffer. java:127)
at java.nio. ByteBuffer . allocateDirect (ByteBuffer.java:306)
at geym. zbase. ch7. oom. Di rectBuf ferOOM. main (DirectBuffer0OM. java:14)

可以看到,在大约733次循环时,发生OutOfMemoryError 错误。从堆栈可以看到,发生OOM时,正在进行DirectByteBuffer的分配。

读者也许还会有一个疑问,就是在这里为什么Java的垃圾回收机制没有发挥作用?程序第12行分配的直接内存并没有被任何对象所引用,为何没有被回收呢?从程序的输入日志中也可以看到,虽然打开了-XX:+PrintGCDetails开关,但是并没有一-次GC日志,这说明在整个执行过程中,GC并没有进行。事实上,直接内存不一- 定能够触发GC (除非直接内存使用量达到了-XX:MaxDirectMemorySize的设置),所以保证直接内存不溢出的方法是合理地进行Full GC的执行,或者设定一- 个系统实际可达的-XX:MaxDirectMemorySize值(默认情况下等于-Xmx的设置)。因此,如果系统的堆内存少有GC发生,而直接内存申请频繁,会比较容易导致直接内存溢出(这个问题在32位虚拟机上尤为明显)。如果将上述代码中第6行的System.gc()的注释去掉,使显式GC生效,那么程序将可以正常结束,这说明GC可以回收直接内存。另一个让该程序正常执行的方法是设置一个较 小的堆,在不指定-XX:MaxDirectMemorySize的情况下,最大可用直接内存等于-Xmx的值。

- Xmx512m -XX: +PrintGCDetails

这里将最大堆限制在512MB,而非1GB,这种情况下,最大可用直接内存也为512MB, 操作系统可以同时为堆和直接内存提供足够的空间,当直接内存使用量达到512MB时,也会进行GC释放无用内存空间。

此外,显式设置-XX:MaxDirectMemorySize也是解决这一问题的方法。 只要设置一个系统实际可达的最大直接内存值,那么像这种实际上不应该触发的内存溢出就不会发生了。

综上所述,为避免直接内存溢出,在确保空间不浪费的基础上,合理得执行显式GC,可以降低直接内存溢出的概率,设置合理的-XX:MaxDrectMemorySize也可以避免意外的内存溢出发生,而设置一个较小的堆在32位虚拟机上可以使得更多的内存用于直接内存。

过多线程导致OOM

由于每一个线程的开启都要占用系统内存,因此当线程数量太多时,也有可能导致OOM。由于线程的栈空间也是在堆外分配的,因此和直接内存非常相似,如果想让系统支持更多的线程,那么应该使用一个较小的堆空间。

【示例】需要在32位操作系统下运行  jdk1.7 -Xmx1g

public class MultiThreadOOM {public static class SleepThread implements Runnable {@Overridepublic void run() {try {Thread.sleep(10000000);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {for (int i = 0; i < 1500000; i++) {new Thread(new SleepThread(), "Thread: " + i).start();System.out.println("Thread:" + i + " created");}}
}

运行结果:

永久区溢出

永久区(Perm) 是存放类元数据的区域。如果一 一个系统定了太多的类型,那么永久区是有可能溢出的。在JDK 1.8中,永久区被一块称为元数据的区域替代, 但是它们的功能是类似的,都是为了保存类的元信息。

【示例】Jdk1.6执行,1.8已经去掉了-XX:MaxPermSize

public class PermOOM {public static void main(String[] args) {for (int i=0;i<100000;i++){Apple apple = Apple.builder().id(i).build();}}
}

运行结果:

解决可以从以下方面考虑:

  • 增加MaxPermSize的值
  • 减少系统需要的类的数量
  • 使用ClassLoader合理地装载各个类,并定期回收

常见内存溢出原因及解决思路相关推荐

  1. jmeter(二十二)内存溢出原因及解决方法

    jmeter(二十二)内存溢出原因及解决方法 参考文章: (1)jmeter(二十二)内存溢出原因及解决方法 (2)https://www.cnblogs.com/imyalost/p/7901064 ...

  2. 内存学习(二)内存溢出介绍以及解决思路

    文章目录 一.内存溢出定义 二.内存溢出原因 三.常见的一些内存溢出风险 3.1 strcpy 函数 3.2 sprintf函数 3.3 malloc函数 一.内存溢出定义 指程序在申请内存时,没有足 ...

  3. java内存溢出原因及解决_java内存溢出的原因和解决方法

    java内存溢出的原因和解决方法 发布时间:2020-06-15 17:57:39 来源:亿速云 阅读:85 作者:元一 内存溢出含义: 内存溢出(out of memory)通俗理解就是内存不够,通 ...

  4. Spark开发-Spark内存溢出原因以及解决方式

    Dpark内存溢出 Spark内存溢出 堆内内存溢出 堆外内存溢出 堆内内存溢出 java.lang.OutOfMemoryError: GC overhead limit execeeded jav ...

  5. Android 系统(87)---常见的内存泄漏原因及解决方法

    常见的内存泄漏原因及解决方法 (Memory Leak,内存泄漏) 为什么会产生内存泄漏? 当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被 ...

  6. 常见的内存泄漏原因及解决方法

    常见的内存泄漏原因及解决方法 参考文章: (1)常见的内存泄漏原因及解决方法 (2)https://www.cnblogs.com/leeego-123/p/12187677.html 备忘一下.

  7. mysql 死锁原因_Mysql并发时经典常见的死锁原因及解决方法

    1.mysql都有什么锁 MySQL有三种锁的级别:页级.表级.行级. 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高,并发度最低. 行级锁:开销大,加锁慢:会出现死锁:锁定粒 ...

  8. 常见的 OOM 原因及其解决方法(OutOfMemoryError)

    当 JVM 内存严重不足时,就会抛出 java.lang.OutOfMemoryError 错误.本文总结了常见的 OOM 原因及其解决方法,如下图所示.如有遗漏或错误,欢迎补充指正. 1.Java ...

  9. springboot上传大文件时内存溢出的可能解决办法

    springboot上传大文件时内存溢出的可能解决办法 在springboot中上传大文件时要考虑内存的情况,一般我们会通过在执行服务时加入-Xms512m -Xmx512m等参数加大堆内存,但这是指 ...

最新文章

  1. linux if 命令判断条件总结
  2. 数据科学 | Python数据科学常用库
  3. python脚本多线程爬虫爬电脑壁纸
  4. 复旦计算机学院徐老师,复旦大学信息科学与技术学院徐跃东副研究员到课题组访问交流...
  5. 的注册表怎么才能删干净_袜子怎么洗才能洗干净,你需要这些技巧
  6. 【重磅】央行发大招!最全面的支付安全风险大检查来了……
  7. BugKuCTF 杂项 telnet
  8. EventTrigger接管所有事件导致其他事件无法触发
  9. 推荐:万能模板,十分钟打造电商首焦Banner
  10. Python来处理数独游戏(含世界最难数独示例)
  11. Linux在终端搜索的两条命令
  12. 29.TCP/IP 详解卷1 --- 网络文件系统
  13. android gradle 离线安装,Android Studio离线配置gradle(附gradle下载地址)
  14. 吴伯凡-认知方法论-认知是一个长期修炼的过程
  15. java 生成2位随机数_java生成随机数保留数点后两位
  16. 【知识图谱】实践篇——基于医疗知识图谱的问答系统实践(Part2):图谱数据准备与导入
  17. 三角测量的一些基础理论
  18. 没有网能使用mysql吗_就用本地数据库;即使在没有联网的电脑上也可以放心使用...
  19. QGIS制图表达-符号大小随比例尺变化
  20. 旅游网后台管理系统(三)权限操作

热门文章

  1. 用Python读写word
  2. c++ Templates读书笔记 9-12章
  3. Android之AndroidStudio输入中文不提示候选字解决办法
  4. Python爬虫(一) 信息系统集成及服务资质网
  5. 带你一步一步深入Handler源码,看这一篇就够了!
  6. java有关会员到期_会员体系中,积分过期的设计方案
  7. Objective-C 适用c数学函数 math.h
  8. rowcount用法
  9. Android Rom修改
  10. Linux系统备份、还原