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

最新文章

  1. spark安装(实战)
  2. js 连接地址分析
  3. 配置JDKAndroid 2D游戏引擎AndEngine
  4. C#加密算法汇总
  5. 软件测试 -- 和用户共同测试(UAT测试)的注意点有哪些
  6. Asp服务器控件(HyperLink、Button) 绑定后台参数 DataBinder.Eval
  7. js获取url传递参数(转的,原作不详)
  8. 翻译题(map使用)
  9. mysql主从数据库
  10. 3步轻松搭建Unity3d安卓开发环境
  11. C#学习笔记-备忘录模式
  12. linux shell 和linux 命令的区别?windows shell 和 windows 命令呢?
  13. 【JVM虚拟机】(6)---深入理解Class中访问标志、类索引、父类索引、接口索引
  14. su与su -的区别
  15. go语言的运算符
  16. SYN flooding引发的网络故障
  17. 20145127 《Java程序设计》第四次实验报告
  18. SQLServer 之 聚合函数
  19. ubuntu下禁用和恢复触摸板
  20. 「BZOJ 2342」「SHOI 2011」双倍回文「Manacher」

热门文章

  1. 五分钟搞懂MySQL索引下推
  2. Robot Framework(7)- DateTime 测试库常用的关键字列表
  3. Intel® QAT 加速卡之IPSec示例
  4. 【MyBatis】几种批量插入效率的比较
  5. Jenkins教程(七)实现 GitLab 提交/合并代码触发构建
  6. nmap使用命令(转载)原文地址https://www.jianshu.com/p/4030c99fcaee
  7. C++快读讲解
  8. 【小程序】微信小程序iOS苹果报错“协议错误”
  9. sql查询字段语句
  10. 防刷功能的实现(thinkphp5)