JVM:内存溢出OOM

本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记

经典错误

JVM 中常见的两个 OOM 错误

StackoverflowError:栈溢出

OutofMemoryError:java heap space:堆溢出

除此之外,还有以下的错误

  • java.lang.StackOverflowError
  • java.lang.OutOfMemoryError:java heap space
  • java.lang.OutOfMemoryError:GC overhead limit exceeeded
  • java.lang.OutOfMemoryError:Direct buffer memory
  • java.lang.OutOfMemoryError:unable to create new native thread
  • java.lang.OutOfMemoryError:Metaspace

架构

OOM 都属于 Error,而不是 Exception

StackOverflowError

堆栈溢出,我们用最简单的一个递归调用,就会造成堆栈溢出,也就是深度的方法调用

栈一般是512K~1024K,不断的深度调用,直到栈被撑破

-Xss:设计单个线程栈的大小,一般默认为512K~1024K,等价于 -XX:ThreadStackSize

进一步说明:

  • 使用 jinfo -flag ThreadStackSize 会发现 -XX:ThreadStackSize=0
  • 这个值的大小是取决于平台的
    • Linux/x64:1024KB
    • OS X:1024KB
    • Oracle Solaris:1024KB
    • Windows:取决于虚拟内存的大小
public class StackOverflowErrorDemo {

    private static Integer i=0;

    public static void main(String[] args) {
stackOverflowError();
}
/**
* 栈一般是512K,不断的深度调用,直到栈被撑破
* Exception in thread "main" java.lang.StackOverflowError
*/
private static void stackOverflowError() {
System.out.println(i++);
stackOverflowError();
}
}

运行结果

10461
Exception in thread "main" java.lang.StackOverflowError

OutOfMemoryError

java heap space

堆内存溢出:创建了很多对象,导致堆空间不够存储

public class JavaHeapSpaceDemo {
public static void main(String[] args) {
// JVM参数调整堆空间的大小 -Xms10m -Xmx10m
// 创建一个 80M的字节数组
byte [] bytes = new byte[80 * 1024 * 1024];
}
}

我们创建一个80M的数组,会直接出现 Java heap space

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:5)

GC overhead limit exceeded

GC回收时间过长时会抛出 OutOfMemoryError,过长的定义是:超过了98%的时间用来做 GC,并且回收了不到2% 的堆内存。

连续多次GC都只回收了不到2%的极端情况下,才会抛出该异常。假设不抛出 GC overhead limit 错误会造成什么情况呢?那就是GC清理的这点内存很快会再次被填满,迫使GC再次执行,这样就形成了恶性循环,CPU的使用率一直都是100%,而GC却没有任何成果。

代码演示:

为了更快的达到效果,我们首先需要设置JVM启动参数

# 堆内存大小      打印出GC回收情况	    最大直接内存大小
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

这个异常出现的步骤就是,我们不断的像list中插入String对象,直到启动GC回收

/**
* GC 回收超时
* JVM参数配置: -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
*/
public class GCOverheadLimitDemo {
public static void main(String[] args) {
int i = 0;
List<String> list = new ArrayList<>();
try {
while(true) {
list.add(String.valueOf(++i).intern());
}
} catch (Exception e) {
System.out.println("***************i:" + i);
e.printStackTrace();
throw e;
} finally { }
}
}

运行结果:

[GC (Allocation Failure) [PSYoungGen: 2048K->500K(2560K)] 2048K->972K(9728K), 0.0034656 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2548K->505K(2560K)] 3020K->2737K(9728K), 0.0028468 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2553K->485K(2560K)] 4785K->4613K(9728K), 0.0026048 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2533K->496K(2560K)] 6661K->6368K(9728K), 0.0035775 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 496K->0K(2560K)] [ParOldGen: 5872K->6302K(7168K)] 6368K->6302K(9728K), [Metaspace: 3234K->3234K(1056768K)], 0.0562960 secs] [Times: user=0.11 sys=0.02, real=0.06 secs]
[Full GC (Ergonomics) [PSYoungGen: 2048K->898K(2560K)] [ParOldGen: 6302K->7030K(7168K)] 8350K->7928K(9728K), [Metaspace: 3234K->3234K(1056768K)], 0.0414180 secs] [Times: user=0.08 sys=0.00, real=0.04 secs]
[Full GC (Ergonomics) [PSYoungGen: 2048K->2006K(2560K)] [ParOldGen: 7030K->7030K(7168K)] 9078K->9036K(9728K), [Metaspace: 3234K->3234K(1056768K)], 0.0385825 secs] [Times: user=0.08 sys=0.00, real=0.04 secs]
[Full GC (Ergonomics) [PSYoungGen: 2048K->2047K(2560K)] [ParOldGen: 7030K->7030K(7168K)] 9078K->9078K(9728K), [Metaspace: 3234K->3234K(1056768K)], 0.0277052 secs] [Times: user=0.11 sys=0.00, real=0.03 secs]
...很多个Full GC...
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7073K->7073K(7168K)] 9121K->9121K(9728K), [Metaspace: 3234K->3234K(1056768K)], 0.0355338 secs] [Times: user=0.06 sys=0.00, real=0.04 secs]
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7075K->7075K(7168K)] 9123K->9123K(9728K), [Metaspace: 3234K->3234K(1056768K)], 0.0323376 secs] [Times: user=0.05 sys=0.00, real=0.03 secs]
[Full GC (Ergonomics) [PSYoungGen: 2047K->0K(2560K)] [ParOldGen: 7093K->603K(7168K)] 9141K->603K(9728K), [Metaspace: 3259K->3259K(1056768K)], 0.0064373 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2560K, used 76K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 3% used [0x00000000ffd00000,0x00000000ffd13250,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 603K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 8% used [0x00000000ff600000,0x00000000ff696f20,0x00000000ffd00000)
Metaspace used 3267K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 353K, capacity 388K, committed 512K, reserved 1048576K Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

我们能够看到多次Full GC,并没有清理出空间,在多次执行GC操作后,就抛出异常 java.lang.OutOfMemoryError: GC overhead limit exceeded

Direct buffer memory

NIO:主要是由于NIO引起的

写 NIO 程序的时候经常会使用 ByteBuffer 来读取或写入数据,这是一种基于通道 (Channel) 与缓冲区 (Buffer) 的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。

ByteBuffer.allocate(capability):第一种方式是分配 JVM 堆内存,属于 GC 管辖范围,由于需要拷贝所以速度相对较慢;

ByteBuffer.allocteDirect(capability):第二种方式是分配 OS 本地内存,不属于 GC 管辖范围,由于不需要内存的拷贝,所以速度相对较快

但如果不断分配本地内存,堆内存很少使用,那么 JVM 就不需要执行 GC,DirectByteBuffer 对象就不会被回收,这时候会出现堆内存充足,但本地内存可能已经使用光了的情况,当再次尝试分配本地内存就会出现OutOfMemoryError:Direct buffer memory,那么程序就奔溃了。

一句话概括:本地内存不足,但是堆内存充足的时候,就会出现这个问题。

我们使用 -XX:MaxDirectMemorySize=5m 配置能使用的堆外物理内存为5M

-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

然后我们申请一个6M的空间

// 只设置了5M的物理内存使用,但是却分配6M的空间
ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);

这个时候,运行就会出现问题了,如下:

[GC (System.gc()) [PSYoungGen: 1657K->504K(2560K)] 1657K->672K(9728K), 0.0008168 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 168K->623K(7168K)] 672K->623K(9728K), [Metaspace: 3227K->3227K(1056768K)], 0.0033185 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 2560K, used 98K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd18aa0,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 623K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 8% used [0x00000000ff600000,0x00000000ff69bed8,0x00000000ffd00000)
Metaspace used 3260K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 353K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory

unable to create new native thread

不能够创建更多的新的线程了,也就是说创建线程的上限达到了

在高并发场景的时候,会应用到;当高并发请求服务器时,经常会出现如下异常java.lang.OutOfMemoryError:unable to create new native thread,准确说该 native thread 异常与对应的平台有关

导致原因:

  • 应用创建了太多线程,一个应用进程创建的线程数目,超过系统承载极限
  • 服务器并不允许你的应用程序创建这么多线程,linux系统默认:运行单个进程可以创建的线程为1024个,如果应用创建超过这个数量,就会报 java.lang.OutOfMemoryError:unable to create new native thread

解决方法:

  1. 想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低
  2. 对于有的应用,确实需要创建很多线程,远超过linux系统默认1024个线程限制,可以通过修改linux服务器配置,扩大linux默认限制
/**
* 无法创建更多的线程
*/
public class UnableCreateNewThreadDemo {
public static void main(String[] args) {
for (int i = 0; ; i++) { // 死循环
System.out.println("************** i = " + i);
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}

这个时候,就会出现下列的错误,线程数大概在 900 多个

在windows下TM的创建了100060才报错!

Exception in thread "main" java.lang.OutOfMemoryError: unable to cerate new native thread
  • 如何查看线程数:ulimit -u
  • 同一个线程不能进行两次start,会出现:Exception in thread "main" java.lang.IllegalThreadStateException的异常,可进入源码进行分析一波

Metaspace

元空间内存不足,Matespace 元空间应用的是本地内存

-XX:MetaspaceSize 的默认的大小约为20M

元空间是什么

元空间(实现)就是我们的方法区(概念),存放的是类模板,类信息,常量池等

Metaspace 是方法区 HotSpot 中的实现,它与持久代(JDK7)最大的区别在于:Metaspace 并不在虚拟内存中,而是使用本地内存,也即在 java8 中,class metadata(the virtual machines internal presentation of Java class),被存储在叫做 Matespace 的 native memory

永久代(java8 后被元空间 Metaspace 取代了)存放了以下信息:

  • 虚拟机加载的类信息
  • 常量池(不是在堆中吗?
  • 静态变量(类的静态变量还是在堆中的吧
  • 及时编译后的代码

模拟 Metaspace 空间溢出,我们不断生成类往元空间里灌输,类占据的空间总会超过Metaspace指定的空间大小

代码

在模拟异常生成时候,因为初始化的元空间为20M,因此我们使用 JVM 参数调整元空间的大小,为了更好的效果

-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m

代码如下

/**
* 元空间溢出
*/
public class MetaspaceOutOfMemoryDemo { // 静态类
static class OOMTest { }
public static void main(final String[] args) {
// 模拟计数多少次以后发生异常
int i =0;
try {
while (true) {
i++;
// 使用Spring的动态字节码技术
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);
}
});
}
} catch (Exception e) {
System.out.println("发生异常的次数:" + i);
e.printStackTrace();
} finally { }
}
}

会出现以下错误:

发生异常的次数: 201
java.lang.OutOfMemoryError:Metaspace

JVM:内存溢出OOM的更多相关文章

  1. 定位JVM内存溢出问题思路总结

    JVM的内存溢出问题,是个常见而有时候有非常难以定位的问题.定位内存溢出问题常见方法有很多,但是其实很多情况下可供你选择的有效手段非常有限.很多方法在一些实际场景下没有实用价值.这里总结下我的一些定位 ...

  2. jvm内存溢出问题的定位方法

    jvm内存溢出问题的定位方法 今天给大家带来JVM体验之内存溢出问题的定位方法. 废话不多说直接开始: 一.Java堆溢出 测试代码如下: import java.util.*; public cla ...

  3. 5种JVM垃圾收集器特点和8种JVM内存溢出原因

    先来看看5种JVM垃圾收集器特点 一.常见垃圾收集器 现在常见的垃圾收集器有如下几种: 新生代收集器: Serial ParNew Parallel Scavenge 老年代收集器: Serial O ...

  4. 内存溢出(OOM)分析

    当JVM内存不足时,会抛出java.lang.OutOfMemoryError.   主要的OOM类型右: Java heap space:堆空间不足 GC overhead limit exceed ...

  5. jvm内存溢出分析

    概述 jvm中除了程序计数器,其他的区域都有可能会发生内存溢出 内存溢出是什么? 当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出 内存溢出和 ...

  6. Tomcat中JVM内存溢出及合理配置及maxThreads如何配置(转)

    来源:http://www.tot.name/html/20150530/20150530102930.htm Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个Java虚 ...

  7. Tomcat中JVM内存溢出及合理配置

    Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个Java虚拟机.Tomcat的内存溢出本质就是JVM内存溢出,所以在本文开始时,应该先对Java JVM有关内存方面的知识 ...

  8. JVM内存溢出及合理配置

    Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个Java虚拟机.Tomcat的内存溢出本质就是JVM内存溢出,所以在本文开始时,应该先对Java JVM有关内存方面的知识 ...

  9. 巧解Tomcat中JVM内存溢出问题

    你对Tomcat 的JVM内存溢出问题的解决方法是否了解,这里和大家分享一下,相信本文介绍一定会让你有所收获. tomcat 的JVM内存溢出问题的解决 最近在熟悉一个开发了有几年的项目,需要把数据库 ...

随机推荐

  1. string类型数据的操作指令

    1. 2. 3. 4. 5. 6. 7. 8. 9. 从右到左是索引从-1开始 10. 11. 12. 13. 14. 15.

  2. MongoDB(9)- 文档查询操作之 find() 的简单入门

    find() MongoDB 中查询文档使用 find() find() 方法以非结构化的方式来显示所要查询的文档 语法格式 db.collection.find(query, projection) ...

  3. abp element 显示分页

    App.vue添加组件 <template> <div id="app"> <dataTable></dataTable> < ...

  4. Spring5(五)——AOP

    一.AOP 1.介绍 AOP(Aspect Oriented Programming),面向切面编程.它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共 ...

  5. selenium-ide-2.3.0 组件在foxfire45.0无法安装的问题

    楼主在安装selenium-ide组件时,尝试了下面两种方式都无法安装: 1.在forfire浏览器进行拖拽安装,页面无任何跳转.拖拽后回车安装,也没任何效果 2.附件组件-从文件安装添加组件,添加了 ...

  6. html jquey的选择器checkbox,select

    1 判断checkbox是否选中 用到 jquery的 is方法 jquery: <div id="divId" class="divTable"> ...

  7. shell脚本中 /dev/null 的用途

    /dev/null 是一个特殊的设备文件,它丢弃一切写入其中的数据 可以将它 视为一个黑洞, 它等效于只写文件, 写入其中的所有内容都会消失, 尝试从中读取或输出不会有任何结果,同样,/dev/nul ...

  8. Python+Pygame开发太空大战/飞机大战完整游戏项目(附源代码)

    项目名称:太空大战 开发环境:Python3.6.4 第三方库:Pygame1.9.6 代码编辑器:Sublime Text 先来看一下游戏画面吧!  游戏画面动态且丰富哦!   需求分析 利用Pyt ...

  9. 华为云计算IE面试笔记-eBackup有哪几种备份组网方式,各备份组网方式主要的应用场景及备份流程?

    应用场景: LAN-Base一般用于备份数据量小,且对备份窗口没有特殊要求的场景,此类场景下备份服务器和备份代理一般是虚拟机部署. LAN-Free一般用于备份数据量较大,且对备份窗口要求比较严格的场 ...

  10. CF755G-PolandBall and Many Other Balls【倍增FFT】

    正题 题目链接:https://www.luogu.com.cn/problem/CF755G 题目大意 \(n\)个东西排成一排,每个组可以选择一个单独的物品或者两个连续的物品,一个物品不同同时在两 ...