目录

  • 1 直接内存溢出
  • 2 内存溢出
    • 2.1 堆溢出
      • 2.2.1 堆溢出案例
    • 2.3 永久代或元空间溢出
      • 2.3.1 永久代或元空间溢出案例
    • 2.4 栈溢出
      • 2.4.1 栈溢出案例
    • 2.5 非常规溢出

1 直接内存溢出

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是 JVM 规范中定义的内存区域。但这部分内存也被频繁的使用,而且也可能导致 OutOfMemoryError 异常出现。

JDK1.4 中新引入了 NIO 机制,它是一种基于通道与缓冲区的新 I/O 方式,可以直接从操作系统中分配直接内存,即直接堆外分配内存,这样能在一些场景中提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。

2 内存溢出

内存溢出(OutOfMemory,简称OOM)是一个令人头疼的问题,它通常出现在某一块内存空间耗尽的时候。在 Java 程序中,导致内存溢出的原因有很多,其中最常见的有:堆溢出、直接内存溢出、方法区溢出等。

2.1 堆溢出

这种场景最为常见,报错信息:

java.lang.OutOfMemoryError: Java heap space

原因

1、代码中可能存在大对象分配
2、可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。

解决方法

1、检查是否存在大对象的分配,最有可能的是大数组分配
2、通过jmap命令,把堆内存dump下来,使用mat工具分析一下,检查是否存在内存泄露的问题
3、如果没有找到明显的内存泄露,使用 -Xmx 加大堆内存
4、还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性

2.2.1 堆溢出案例

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

执行上述代码报错:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat com.msdn.java.hotspot.gc.SimpleHeapOOM.main(SimpleHeapOOM.java:16)

由于堆空间不可能无限增长,所以我们需要借助前文提到的 MAT 或 VIsualVM 工具,分析 dump 文件(使用-XX:+HeapDumpOnOutOfMemoryError命令生成 dump文件),弄清楚到底是出现了内存泄漏(Memory Leak) 还是内存溢出(Memory Overflow)。

如果是内存泄漏, 可进一步通过工具查看泄漏对象到 GC Roots 的引用链, 找到泄漏对象是通过怎样的引用路径、 与哪些 GC Roots 相关联, 才导致垃圾收集器无法回收它们, 根据泄漏对象的类型信息以及它到 GC Roots 引用链的信息, 一般可以比较准确地定位到这些对象创建的位置,进而找出产生内存泄漏的代码的具体位置。

如果不是内存泄漏,换句话说就是内存中的对象确实都是必须存活的,那就应当检查 Java 虚拟机的堆参数(-Xmx与-Xms)设置,与机器的内存对比,看看是否还有向上调整的空间。再从代码上检查是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运行期的内存消耗。

2.3 永久代或元空间溢出

方法区和运行时常量池的溢出

-XX:MetaspaceSize=N //设置Metaspace的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置Metaspace的最大大小,默认为-1,即不限制, 或者说只受限于本地内存大小

与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。

那么如何演示方法区溢出呢?首先我们明确方法区的主要职责是用于存放类型的相关信息,如类名、访问修饰符、常量池、字段描述、 方法描述等。对于这部分区域的测试,基本的思路是运行时产生大量的类去填满方法区,直到溢出为止。本文借助 CGLib 直接操作字节码运行时生成了大量的动态类。

报错信息:

java.lang.OutOfMemoryError: PermGen spacejava.lang.OutOfMemoryError: Metaspace

原因

永久代是 HotSot 虚拟机对方法区的具体实现,存放了被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。

JDK8后,元空间替换了永久代,元空间使用的是本地内存,还有其它细节变化:

  • 字符串常量由永久代转移到堆中
  • 和永久代相关的JVM参数已移除

可能原因有如下几种:

1、在Java7之前,频繁的错误使用String.intern()方法
2、运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载
3、应用长时间运行,没有重启

没有重启 JVM 进程一般发生在调试时,如下面 tomcat 官网的一个 FAQ:

Why does the memory usage increase when I redeploy a web application? That is because your web application has a memory leak. A common issue are “PermGen” memory leaks. They happen because the Classloader (and the Class objects it loaded) cannot be recycled unless some requirements are met (). They are stored in the permanent heap generation by the JVM, and when you redeploy a new class loader is created, which loads another copy of all these classes. This can cause OufOfMemoryErrors eventually. (*) The requirement is that all classes loaded by this classloader should be able to be gc’ed at the same time.

解决方法

因为该OOM原因比较简单,解决方法有如下几种:

1、检查是否永久代空间或者元空间设置的过小
2、检查代码中是否存在大量的反射操作
3、dump之后通过mat检查是否存在大量由于反射生成的代理类
4、放大招,重启JVM

GC overhead limit exceeded

这个异常比较的罕见,报错信息:

java.lang.OutOfMemoryError:GC overhead limit exceeded

原因

这个是JDK6新加的错误类型,一般都是堆太小导致的。Sun 官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。

解决方法

1、检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。

2、添加参数 -XX:-UseGCOverheadLimit 禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap space。

3、dump内存,检查是否存在内存泄露,如果没有,加大内存。

2.3.1 永久代或元空间溢出案例

//-XX:MaxMetaspaceSize=10M
public class RuntimeConstantPoolOOM {static class OOMObject{}public static void main(String[] args)throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {while (true) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)throws Throwable {return methodProxy.invokeSuper(o, args);}});enhancer.create();}}
}

执行结果为:

Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.OutOfMemoryError-->Metaspaceat org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:557)at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)

那么在实际应用中都有哪些场景可能出现方法区溢出呢?

1、关于上述 CGLib 操作字节码生成动态类的代码,在当前很多主流框架,如 Spring、 Hibernate 对类进行增强时, 都会使用到 CGLib 这类字节码技术,当增强的类越多,就需要越大的方法区以保证动态生成的新类型可以载入内存。

2、除此之外,很多运行于Java虚拟机上的动态语言(例如Groovy等)通常都会持续创建新类型来支撑语言的动态性,随着这类动态语言的流行,方法区溢出的情况也会更加频繁。

3、大量 JSP 或动态产生 JSP 文件的应用(JSP第一次运行时需要编译为Java类)、基于 OSGi 的应用(即使是同一个类文件, 被不同的加载器加载也会视为不同的类) 等。

2.4 栈溢出

报错信息:

java.lang.OutOfMemoryError : unable to create new native Thread

原因

出现这种异常,基本上都是创建的了大量的线程导致的,以前碰到过一次,通过jstack出来一共8000多个线程。

解决方法

1、通过 -Xss 降低的每个线程栈大小的容量
2、线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制:

  • /proc/sys/kernel/pid_max
  • /proc/sys/kernel/thread-max
  • maxuserprocess(ulimit -u)
  • /proc/sys/vm/maxmapcount

由于 HotSpot 虚拟机中并不区分虚拟机栈和本地方法栈,栈容量只能由-Xss参数来设定。 关于虚拟机栈和本地方法栈, 在《Java虚拟机规范》 中描述了两种异常:

如果线程请求的栈深度大于虚拟机所允许的最大深度, 将抛出 StackOverflowError 异常。
如果虚拟机的栈内存允许动态扩展, 当扩展栈容量无法申请到足够的内存时, 将抛出 OutOfMemoryError 异常。
《Java虚拟机规范》 明确允许Java虚拟机实现自行选择是否支持栈的动态扩展, 而 HotSpot 虚拟机的选择是不支持扩展, 所以除非在创建线程申请内存时就因无法获得足够内存而出现 OutOfMemoryError 异常, 否则在线程运行时是不会因为扩展而导致内存溢出的, 只会因为栈容量无法容纳新的栈帧而导致 StackOverflowError 异常。

栈溢出抛出 StackOverflowError 错误,出现此种情况是因为方法运行的时候栈的深度超过了虚拟机容许的最大深度所致。一般情况下是程序错误所致的,比如写了一个死递归,就有可能造成此种情况。下面我们通过一段代码来模拟一下此种情况的内存溢出。

2.4.1 栈溢出案例

public class StackOOM {private int len = 1;public void stackOverFlowMethod() {len++;stackOverFlowMethod();}public static void main(String[] args) {StackOOM stackOOM = new StackOOM();try {stackOOM.stackOverFlowMethod();} catch (Throwable e) {System.out.println("stack length: " + stackOOM.len);throw e;}}
}

执行结果为:

stack length: 17850
Exception in thread "main" java.lang.StackOverflowError

那么 HotSpot 虚拟机中是否可以抛出 OutOfMemoryError 呢?我们尝试如下两种方案:

方案一:使用-Xss 参数减少栈内存容量,JDK8 要求栈内存容量至少为 160K,针对上述代码设置如下参数:-Xss160k,执行结果变为:

stack length: 774
Exception in thread "main" java.lang.StackOverflowError

经过测试发现,栈内存容量变小,仍然会抛出 StackOverflowError 异常,异常出现时输出的堆栈深度相应缩小。

方案二:在方法中多定义一些局部变量,增大方法栈中拘捕变量表的长度。

修改代码如下:

  public void stackOverFlowMethod() {long l1, l2, l3, l4, l5, l6, l7, l8, l9;l1 = l2 = l3 = l4 = l5 = l6 = l7 = l8 = l9 = 100L;double d1, d2, d3, d4, d5, d6, d7, d8, d9;d1 = d2 = d3 = d4 = d5 = d6 = d7 = d8 = d9 = 100.0;len++;stackOverFlowMethod();}

执行结果为:

stack length: 4817
Exception in thread "main" java.lang.StackOverflowError

根据结果可知,同样抛出 StackOverflowError 异常, 异常出现时输出的堆栈深度相应缩小。

关于这句话“在 HotSpot 虚拟机上是不会由于虚拟机栈无法扩展而导致 OutOfMemoryError 异常——只要线程申请栈空间成功了就不
会有 OOM, 但是如果申请时就失败, 仍然是会出现 OOM 异常的”是什么意思呢?我们上面尝试的两个方案没有出现 OOM 异常,那是因为程序执行过程中线程数是固定的,所以只要线程申请空间成功,即开始执行,就不会有 OOM。那么我们尝试在代码中不断创建新的线程,看看结果如何。

每个线程的开启都要占用栈内存,如果线程数量过大,栈空间使用完毕,也有可能导致 OOM。如下案例所示:

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

执行结果报错:

Thread4067
Thread4068
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native threadat java.lang.Thread.start0(Native Method)

注意,执行上述代码记得先保存当前的工作,Java 的线程是映射到操作系统的内核线程上,创建线程数过多可能对操作系统带来压力,针对上述 OOM 异常,即使使用 -Xss 参数减少栈内存容量,仍然会报相同的错误,又或者使用 -Xmx 降低堆内存大小,结果也样。说明当前系统仅支持创建 4069 个线程。

2.5 非常规溢出

下面这些OOM异常,可能大部分的同学都没有碰到过,但还是需要了解一下

分配超大数组

报错信息 :

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

这种情况一般是由于不合理的数组分配请求导致的,在为数组分配内存之前,JVM 会执行一项检查。要分配的数组在该平台是否可以寻址(addressable),如果不能寻址(addressable)就会抛出这个错误。

解决方法就是检查你的代码中是否有创建超大数组的地方。

swap溢出

报错信息 :

java.lang.OutOfMemoryError: Out of swap space

这种情况一般是操作系统导致的,可能的原因有:

1、swap 分区大小分配不足;

2、其他进程消耗了所有的内存。

解决方案:

1、其它服务进程可以选择性的拆分出去 2、加大swap分区大小,或者加大机器内存大小

本地方法溢出

报错信息 :

java.lang.OutOfMemoryError: stack_trace_with_native_method

本地方法在运行时出现了内存分配失败,和之前的方法栈溢出不同,方法栈溢出发生在 JVM 代码层面,而本地方法溢出发生在JNI代码或本地方法处。

这个异常出现的概率极低,只能通过操作系统本地工具进行诊断,难度有点大,还是放弃为妙。

JVM各种情况内存溢出分析相关推荐

  1. JVM 调优实战--jmap的使用以及内存溢出分析

    目录 jmap的使用以及内存溢出分析 查看内存使用情况 查看内存中对象数量及大小 将内存使用情况dump到文件中 通过jhat对dump文件进行分析 通过MAT工具对dump文件进行分析 MAT介绍 ...

  2. java内存溢出分析工具:jmap使用实战

    java内存溢出分析工具:jmap使用实战 在一次解决系统tomcat老是内存撑到头,然后崩溃的问题时,使用到了jmap.  1 使用命令  在环境是linux+jdk1.5以上,这个工具是自带的,路 ...

  3. 内存溢出分析之工具篇

    内存溢出分析之工具篇 转载于:https://www.cnblogs.com/lwmp/p/9850446.html

  4. linux环境下内存溢出分析MAT

    文章目录 1 下载及安装mat 1.1 下载地址 1.2 查看服务器版本 1.3 下载安装 2 配置 3 运行 4 分析 1 下载及安装mat 1.1 下载地址 https://www.eclipse ...

  5. JVM内存溢出分析-实战JVM(二)

    为什么80%的码农都做不了架构师?>>>    JVM规范规定,除了程序计数器,虚拟机其他内存区域均会发生内存溢出的可能,OutOfMemoryError(OOM) 原文地址:htt ...

  6. Linux下tomcat内存溢出分析及优化

    为什么80%的码农都做不了架构师?>>>    常见的内存溢出有以下两种: java.lang.OutOfMemoryError: PermGen space java.lang.O ...

  7. Java内存溢出分析

    内存溢出与数据库锁表的问题,可以说是开发人员的噩梦,一般的程序异常,总是可以知道在什么时候或是在什么操作步骤上出现了异常,而且根据堆栈信息也很容易定位到程序中是某处出现了问题.内存溢出与锁表则不然,一 ...

  8. jvm解决堆内存溢出问题

    我们讲一下堆溢出,栈溢出,还有方法区的溢出,这就讲了一个概念,内存溢出和内存泄露的区别是什么,这个概念很混淆,内存泄露怎么又包含内存溢出,内存溢出和内存泄露又有区别,待会我会细讲的,堆溢出我们怎么去解 ...

  9. java 内存溢出分析_用一段时间后java内存溢出问题分析(转)

    几乎每个月都有出现因为内存溢出的问题,除了需要多分配内存外, 是不是要考虑对代码进行一些处理.. 下面是参考网络资源总结的一些在Java编程中尽可能要做到的一些地方. 1.尽量在合适的场合使用单例 使 ...

最新文章

  1. centos7安装Samba服务
  2. HDU - 2586 How far away ?(LCA)
  3. UNIX(多线程):21---线程池实现原理
  4. python中浮点数能用乘法吗_简单讲解Python中的数字类型及基本的数学计算
  5. Linux TTY/PTS概述
  6. 课节3: 图游走类模型1-deepwalk与node2vec
  7. 基于etcd实现大规模服务治理应用实战
  8. python的规模有多大_Python项目可以有多大?最多可以有多少行代码?
  9. 一致代价搜索_58搜索效率优化平台建设实践
  10. 开心消消乐h5版游戏案例分享
  11. 会计计算机,什么是好的计算机会计软件?
  12. 配置Maven环境变量
  13. 论文投稿排版时,Word首页插入连续分节符,后面的内容自动到下一页
  14. python自动化测试-最常用的自动化测试框架
  15. MATLAB 查找互素(质)对
  16. echart结合高德地图的数据可视化大数据展示平台模板
  17. C#敏感词汇过滤(不是正则)
  18. CentOS7图形界面与DOS界面切换
  19. Excel技能之计数求和,让你成为高手之路的机车手
  20. 基于51单片机的酒精浓度检测仪设计

热门文章

  1. 今日单词|长期主义 (Day 1)
  2. 基于Java毕业设计影音娱乐销售管理系统源码+系统+mysql+lw文档+部署软件
  3. 狂奔的蔚来:上市背后的必然与隐忧
  4. IDEA插件实现GitFlow工作流管理
  5. h0103. 末日算法 (10 分)
  6. 末日觉醒服务器端文件,末日觉醒单机版
  7. PHP 微信名片生成
  8. 从投递到拿到offer,这份Android面试秘籍一文全解,kotlin开源项目
  9. SSM+基于SSM的养老管理系统 毕业设计-附源码221609
  10. mac下查看文件路径