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. openswan发送状态机分析

    openswan发送状态机分析 1. 函数调用关系 2. 函数说明 如果按用户空间.内核空间划分的话,此部分代码更多是运行在内核空间的. 2.1 ipsec_tunnel_init_devices() ...

  2. java线程day-01

    综述:下面写的是我学习java线程时做的一些笔记和查阅的一些资料总结而成.大多以问答的形式出现. 一.什么是线程? 答:线程是一个轻量级的进程,现在操作系统中一个基本的调度单位,而且线程是彼此独立执行 ...

  3. 彻底搞明白PHP中的include和require

    在PHP中,有两种包含外部文件的方式,分别是include和require.他们之间有什么不同呢? 如果文件不存在或发生了错误,require产生E_COMPILE_ERROR级别的错误,程序停止运行 ...

  4. JDK1.8源码(三)——java.lang.String类

    一.概述 1.介绍 String是一个final类,不可被继承,代表不可变的字符序列,是一个类类型的变量.Java程序中的所有字符串字面量(如"abc")都作为此类的实例实现,&q ...

  5. Charles安装https证书

    Charles抓取https的包,出现unknow,需要安装https证书.

  6. javascript wchar_t 宽字符 转化为 ascii字符码数组

    String.prototype.charCodeAt String.fromCharCode() String.prototype.toUtfArray = function() { return ...

  7. Python Type Hints(类型提示)

    在做自动化测试的时候,改进测试框架,类型提示会让你写代码时更加流程,当你在一个模块定义了类型,而其他模块没有提示的时候,是相当不方便.

  8. cas的基础配置

    去除HTTPS的j基础认证方式 cas的:deployerConfigContext.xml <!-- Required for proxy ticket mechanism. -->&l ...

  9. iNeuOS工业互联网操作系统部署在华为欧拉(openEuler)国产系统,vmware、openEuler、postgresql、netcore、nginx、ineuos一站式部署

    目       录 1.      概述... 3 2.      创建虚拟机&安装华为欧拉(openEuler)系统... 4 2.1           创建新的虚拟机... 4 2.2  ...

  10. 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 百篇博客分析OpenHarmony源码 | v10.04

    百篇博客系列篇.本篇为: v10.xx 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 51.c.h .o 前因后果相关篇为: v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 ...