2023-08-10  阅读(3)
原文作者:Ressmix 原文地址:https://www.tpvlog.com/article/97

一、简介

从本章开始,我们将介绍JVM中的内存溢出异常——Out of Memory。我们运行Java程序时,本质是创建了一个JVM进程,然后在里面执行Java字节码。既然是进程,就一定有内存限制,当Java程序使用的内存空间超过限制时,就可能发生内存溢出异常。

在JVM中,一共有三种可能出现OOM的地方:方法区(元数据区)、Java栈内存、Java堆内存。本章,我们就来一一看一下各个区域内存溢出的情况。

二、方法区溢出

JVM内存模型中,我们介绍过JVM内存模型,JVM中有一块区域叫“方法区”,里面主要保存着从”.class“文件里加载进来的类,包括 类的名称方法信息字段信息静态变量常量 以及 编译器编译后的代码 等。

JDK1.8及以后这块区域叫做“元数据区",元数据区直接使用本地内存。默认情况下,元数据区会根据使用情况动态调整,避免了在JDK1.8以前由于加载类过多从而出现 java.lang.OutOfMemoryError: PermGen。但也不能无限扩展,因此可以使用-XX:MaxMetaspaceSize来控制最大内存。

既然元数据区有大小,那这里就可能发生内存溢出。

2.1 溢出原因

元数据区中对象一般是不会被回收的,JVM进行Full GC时,会尝试对元数据区中的垃圾对象进行回收。但是元数据区中的对象回收的条件是相当苛刻的:比如加载这个类的类加载器先要被回收、这个类的所有对象实例都要被回收等等。所以,即使Full GC针对元数据区进行垃圾回收,也未必能够回收掉很多垃圾对象。

JVM启动时,元数据区默认情况只会分配几十MB空间,所以生产环境一定要显式指定该区域的大小,一般512MB就足够了:

--XX:MetaspaceSize=512m --XX:MaxMetaspaceSize=512m

针对元数据区,避免内存溢出的最好办法就是预估系统运行模型,然后合理分配Metaspace区域的内存大小,同时避免无限制的通过动态代理生成类。

三、栈溢出

JVM中,每个线程都有自己的虚拟机栈,就是所谓的 栈内存 。线程只要执行某个方法,就会为该方法创建一个栈帧,然后将栈帧入栈到虚拟机栈中。这个栈帧中存放着方法的各种局部变量。

我们通过参数JVM启动的-Xss参数设置栈内存的大小,比如我们之前的示例中,一般栈内存大小都指定为1MB——-Xss1M。所以,既然栈内存有大小,那这里也可能发生内存溢出,我们通过示例来看下。

3.1 溢出原因

Java虚拟机栈的内存大小是有限的,如果一个线程不停的层层调用方法,每次调用就会创建栈帧入栈,因为栈帧是有大小的,所以当虚拟机栈满了以后,就会出现栈内存溢出。

一般来说,除非是一些递归调用,否则线程不会一直只入栈不出栈,而且1MB的栈大小也足够容纳递归调用所需的栈内存。所以,引发栈内存溢出的往往都是程序bug,比如递归调用时没有终结条件等。

四、堆溢出

Java堆内存,应该是我们进行JVM调优接触最多的一部分区域了。这里存放着我们程序代码里创建的各种各样的对象。一般来说,我们给Java堆内存分配空间时,是固定的大小,所以这里也是最容易出现内存溢出的区域。

4.1 溢出原因

我们知道,Young GC过后的存活对象首先会先尝试进行一块Survivor区,如果Survivor区无法容纳,则尝试进入老年代,如果此时老年代也满了就会触发Full GC。但是, 如果Full GC之后,老年代的空间还是不够呢? 这时只能抛出内存溢出异常了。

所以,堆内存溢出的原因,总结起来就是一句话:有限的内存中放了过多的对象,而且大多数对象是存活的,此时要继续放入更多对象已经不可能了,只能抛出内存溢出异常。

通常,能引发堆内存溢出的场景主要有两种:

  • 系统承载高并发请求,因为请求量过大,导致大量对象都是存活的,所以要继续放入新的对象实在是不行了,只能抛OOM。
  • 程序存在bug导致内存泄漏,这即使触发GC也无法回收掉这些泄漏的对象,导致内存占用越来越多,直到OOM。

五、总结

本章我们介绍了JVM中可能出现内存溢出的几个区域,以及引发OOM的基本原因。一般来说,元数据区和Java虚拟机栈是不会出现OOM的,而Java堆内存则是最容易出现内存溢出的区域。

针对各类内存溢出问题,生产环境的系统需要有配套的监控系统对OS、JVM的状态进行监控,重点关注CPU、内存、JVM的GC频率这三个指标,一般来说成熟的公司都会有Zabbix、Open-Falcon之类的监控平台,小型公司则可以通过日志结合jhat进行内存快照分析的方式来排查。

后续章节,我们将通过实际案例和代码来分析和解决各类常见的内存溢出问题。


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文