工作中常见的OOM?你了解JVM调优吗?
工作中常见的6种OOM问题
堆内存OOM
堆内存OOM是最常见的OOM了。
出现堆内存OOM问题的异常信息如下:
java.lang.OutOfMemoryError: Java heap space
此OOM是由于JVM中heap的最大值,已经不能满足需求了。
举个例子:
@Test public void test01() {
List list = Lists.newArrayList();
while (true) {
list.add(new OOMTests());
}
}
这里创建了一个list集合,在一个死循环中不停往里面添加对象。
执行结果:

出现了java.lang.OutOfMemoryError: Java heap space的堆内存溢出。
很多时候,excel一次导出大量的数据,获取在程序中一次性查询的数据太多,都可能会出现这种OOM问题。
我们在日常工作中一定要避免这种情况。
栈内存OOM
有时候,我们的业务系统创建了太多的线程,可能会导致栈内存OOM。
出现堆内存OOM问题的异常信息如下:
java.lang.OutOfMemoryError: unable to create new native thread
举个例子
public class StackOOMTest {
public static void main(String[] args) {
while (true) {
new Thread().start();
}
}
}
使用一个死循环不停创建线程,导致系统产生了大量的线程。
如果实际工作中,出现这个问题,一般是由于创建的线程太多,或者设置的单个线程占用内存空间太大导致的。
建议在日常工作中,多用线程池,少自己创建线程,防止出现这个OOM。
栈内存溢出
我们在业务代码中可能会经常写一些 递归调用,如果递归的深度超过了JVM允许的最大深度,可能会出现栈内存溢出问 题。
出现栈内存溢出问题的异常信息如下:
java.lang.StackOverflowError
举个例子
@Test
public void test03() {
recursiveMethod();
}
public static void recursiveMethod() {
// 递归调用自身
recursiveMethod();
}

出现了java.lang.StackOverflowError栈溢出的错误。
我们在写递归代码时,一定要考虑递归深度。即使是使用 parentId一层层往上找的逻辑,也最好加一个参数控制递归 深度。防止因为数据问题导致无限递归的情况,比如:id和 parentId的值相等。
直接内存OOM
直接内存不是虚拟机运行时数据区的一部分,也不是《Java 虚拟机规范》中定义的内存区域。
它来源于 NIO ,通过存在堆中的 DirectByteBuffer 操作 Native内存,是属于堆外内存 ,可以直接向系统申请的内存空间。 出现直接内存OOM问题时异常信息如下:
java.lang.OutOfMemoryError: Direct buffer memory
例如:
private static final int BUFFER = 1024 * 1024 * 20;
@Test
public void test04() {
ArrayList<ByteBuffer> list = new ArrayList<>();
int count = 0;
try {
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
list.add(byteBuffer);
count++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
System.out.println(count);
}
}
会看到报出来java.lang.OutOfMemoryError: Direct buffer memory直接内存空间不足的异常。
GC OOM
GC OOM 是由于JVM在GC时,对象过多,导致内存溢出,建 议调整GC的策略。 出现GC OOM问题时异常信息如下:
java.lang.OutOfMemoryError: GC overhead limit exceeded
为了方便测试,我先将idea中的最大和最小堆大小都设置成 10M,例如下面这个例子:
public class GCOverheadOOM {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(() -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
});
}
}
}
出现这个问题是由于JVM在GC的时候,对象太多,就会报这 个错误。 我们需要改变GC的策略。 在老代80%时就是开始GC,并且将-XX:SurvivorRatio(- XX:SurvivorRatio=8)和-XX:NewRatio(- XX:NewRatio=4)设置的更合理。
元空间OOM
JDK8 之后使用 Metaspace 来代替 永久代 ,Metaspace是方 法区在 HotSpot 中的实现。
Metaspace不在虚拟机内存中,而是使用本地内存也就是在 JDK8中的 ClassMetadata ,被存储在叫做Metaspace的 native memory。
出现元空间OOM问题时异常信息如下:
java.lang.OutOfMemoryError: Metaspace
为了方便测试,我们修改一下idea中的JVM参数,增加下面的配 置:
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
指定了元空间和最大元空间都是10M。 接下来,看看下面这个例子:
public class MetaspaceOOMTest {
static class OOM {
}
public static void main(String[] args) {
int i = 0;
try {
while (true) {
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOM.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[]
return methodProxy.invokeSuper(o, args);
}
});
enhancer.create();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
程序最后会报java.lang.OutOfMemoryError: Metaspace的 元空间OOM。 这个问题一般是由于加载到内存中的类太多,或者类的体积太 大导致的。
OOM一定会导致JVM退出吗
在Java中,发生了OutOfMemoryError(OOM)不一定会导致整个JVM退出。是否退出取决于发生OOM错误的线程和错误处理逻辑。这是一个复杂的问题,具体行为会因应用程序实现方式、错误发生的情境以及错误处理策略而异。
- 主线程中未处理的OOM: 如果在主线程中发生OOM且没有被捕获,JVM通常会终止程序并退出。这是因为JVM中没有其他存活的非守护线程来保持程序运行。
- 子线程中未处理的OOM: 在非主线程中,如果OOM发生且未被捕获,该线程会停止执行。但如果其他非守护线程仍在运行,JVM不会退出。
- 捕获并处理OOM: 如果在代码中捕获并正确处理了OOM错误,JVM则可以继续执行其余的程序代码。合适的错误处理可能包括释放内存资源或提示用户进行适当的操作。
注意:
- 不建议频繁捕获OOM并继续执行程序,因为这样可能表明程序有严重的内存管理问题,应尽量优化内存使用。
- 在关键路径中发生OOM时,通常应记录日志并考虑安全停机,因为无法保证系统在内存压力下的正确性。
垃圾回收调优的主要目标是什么?
分别是最短暂停时间和高吞吐量
- 最短暂停时间:垃圾回收调优的首要目标是减少应用程序的停顿时间,确保在垃圾回收过程中尽量保持应用的响应能力,特别是对于实时或高并发应用
- 高吞吐量:第二个目标是提高应用的吞吐量,即在单位时间内完成更多的业务处理,通过合理的GC策略和配置,减少GC的频率和时间,从而提升整体性
针对最短暂停时间和高吞吐举个例子:
- 方案一:每次 GC 停顿 100 ms,每秒停顿 5 次。
- 方案二:每次 GC 停顿 200 ms,每秒停顿 2次。
两个方案相对而言第一个时延低,第二个吞吐高,基本上两者不可兼得。所以调优时候需要明确应用的目标。
如何对 Java 的垃圾回收进行调优?
GC调优这种问题肯定是具体场景具体分析,但是在面试中就不要讲太细,大方向说清楚就行,不需要涉及具体的垃圾收集器比如 CMS 调什么参数,G1 调什么参数之类的。
GC 调优的核心思路就是尽可能的使对象在年轻代被回收,减少对象进入老年代。
具体调优还是得看场景根据 GC日志具体分析,常见的需要关注的指标是 Young GC 和 Ful GC 触发频率、原因、晋升的速率、老年代内存占用量等等。比加发现频繁产生FullGC,分析日志之后发现没有内存泄漏,只是 Young GC之后会有大量的对象进入老年代,然后最终触发FullGC,所以就能得知是 Survivor 空间设置太小,导致对象过早进入老年代,因此调大Survivor。或者是晋升年龄设置的太小,也有可能分析日志之后发现是内存泄漏、或者有第三方类库调用了 System.gc 等等。反正具体场景具体分析,核心思想就是尽量在新生代把对象给回收了,
基本上这样说就行了,然后就等着面试官延伸了
常用的JVM配置参数有哪些?
记住前两个,其它的使用时再查就行
JVM(Java虚拟机)的启动参数用于配置和调整Java应用程序的运行时行为。以下是一些常用的JVM启动参数:
- -Xmx:指定Java堆内存的最大限制。例如,-Xmx512m 表示最大堆内存为512兆字节。
- -Xms:指定Java堆内存的初始大小。例如,-Xms256m 表示初始堆内存为256兆字节。
- -Xss:指定每个线程的堆栈大小。例如,-Xss256k 表示每个线程的堆栈大小为256千字节。
- -XX:MaxPermSize(对于Java 7及之前的版本)或 -XX:MaxMetaspaceSize(对于Java 8及以后的版本):指定永久代(Java 7及之前)或元空间(Java 8及以后)的最大大小。
- -XX:PermSize(对于Java 7及之前的版本)或 -XX:MetaspaceSize(对于Java 8及以后的版本):指定永久代(Java 7及之前)或元空间(Java 8及以后)的初始大小。
- -Xmn:指定年轻代的大小。例如,-Xmn256m 表示年轻代大小为256兆字节。
- -XX:SurvivorRatio:指定年轻代中Eden区与Survivor区的大小比例。例如,-XX:SurvivorRatio=8 表示Eden区与每个Survivor区的大小比例为8:1。
- -XX:NewRatio:指定年轻代与老年代的大小比例。例如,-XX:NewRatio=2 表示年轻代和老年代的比例为1:2。
- -XX:MaxGCPauseMillis:设置垃圾回收的最大暂停时间目标。例如,-XX:MaxGCPauseMillis=100 表示垃圾回收的最大暂停时间目标为100毫秒。
- -XX:ParallelGCThreads:指定并行垃圾回收线程的数量。例如,-XX:ParallelGCThreads=4 表示使用4个线程进行并行垃圾回收。
- -XX:+UseConcMarkSweepGC:启用并发标记清除垃圾回收器。
- -XX:+UseG1GC:启用G1(Garbage First)垃圾回收器。
- -Dproperty=value:设置Java系统属性,可以在应用程序中使用 System.getProperty("property") 来获取这些属性的值。
这些是一些常见的JVM启动参数,可以根据应用程序的需求和性能调优的目标进行调整。JVM启动参数的使用可以显著影响应用程序的性能和行为,因此在设置这些参数时需要谨慎。同时,JVM支持的启动参数因不同的JVM版本和供应商而有所不同,建议查阅相关文档以获取更详细的信息。
你常用哪些工具来分析 JVM 性能?
- jmap:用于生成堆转储的命令行工具,可以用于分析JVM内存使用情况,尤其是内存泄漏问题
- jstack:用于生成线程转储的命令行工具,可以用于分析线程状态,排查死锁等问题
- jistat:用于监控JVM统计信息的命令行工具,提供了实时的性能数据,如类加载、垃圾回收、编译器等信息
- MAT:用于分析堆转储文件的工具,可以帮助识别内存泄漏和优化内存使用
- jconsole:可以监控JVM的内存使用、垃圾回收、线程、类加载等信息
- VisualVM:可实时显示 JVM 的内存使用、垃圾回收、类加载等信息,也可以分析 Heap Dump 等.
- Arthas:一个强大的Java 诊断工具,提供了实时监控和分析功能。通过命令行界面,可以查看 的状态、监控方法调用、追踪 SQL 查询、分析性能瓶颈等。
如何在 Java 中进行内存泄漏分析?
先确认是否真的发生了内存泄漏,即观察内存使用情况。
利用 jstat 命令(jstat -gc <pid> <interal in ms>)来观察 gc 概要信息,如果发现,GC 后内存并没有明显的减少目还是持续增加持续触发gc,那说明内存泄漏的概率很大。
此时可以利用 jmap(jmap -dump:format=b,fi1e=heapdump.hprof <pid> )生成 heap dump,然后将其导入 Ecdipse MAT 或者 VsuaVM 工具内进行分析,通过大量内存的占用可以找到对应的对象。
通过对象找到对应的代码分析,确认是否可能存在内存泄漏的场景,最终修复代码,解决内存泄漏的问题。
工作中常见的OOM?你了解JVM调优吗?的更多相关文章
- OOM排除与JVM调优
仅先记录,后续整理 1. 常用命令: jstat gcutil jmap 2. 打印GC执行情况: 通过执行jinfo -flag +PrintGCDetails <pid>直接动态开启, ...
- spark调优——JVM调优
对于JVM调优,首先应该明确,(major)full gc/minor gc,都会导致JVM的工作线程停止工作,即stop the world. JVM调优一:降低cache操作的内存占比 1. ...
- 四:JVM调优原理与常见异常处理方案
在jvm调优之前,我们必须先了解jvm的内存模型与GC回收机制,这些在我前面的文章里面有介绍!接下来我们通过一个案例来调整jvm性能. 一测试案例: 1.1 编写demo import java.te ...
- JVM调优学习 【更新中】
JVM调优(jdk1.8) 老生常谈,面试吹牛的的最佳谈资,在接下来的几天里,我找了点资料来对其进行一波学习: 本地环境是不需要对我们的虚拟机进行优化的,一般在生产环境下,也就是Linux下才有对JV ...
- JVM调优实战
JVM调优实战 文档修订记录 版本 日期 撰写人 审核人 批准人 变更摘要 & 修订位置 ...
- 老李分享:JVM调优
老李分享:JVM调优 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:908821478,咨 ...
- JVM调优常用参数和注意点备忘录
本文主要是工作过程中总结的一些jvm调优的参数和注意的地方,作为一个备忘录,先占个坑,有时间在来细化具体的实例. gc日志是覆盖的方式如果文件名字固定会导致上一次被覆盖可以采用这个-Xloggc:ba ...
- JVM调优基础到进阶
GC和GC Tuning GC的基础知识 1.什么是垃圾 C语言申请内存:malloc free C++: new delete c/C++ 手动回收内存 Java: new ? 自动内存回收,编程上 ...
- JVM调优浅谈
1.数据类型 java虚拟机中,数据类型可以分为两类:基本类型和引用类型.基本类型的变量保存原始值,即:它代表的值就是数值本身,而引用类型的变量保存引用值.“引用值”代表了某个对象的引用,而不是对象本 ...
- JVM调优总结 + jstat 分析(转)
[转] JVM调优总结 + jstat 分析 JVM调优总结 + jstat 分析 jstat -gccause pid 1 每格1毫秒输出结果jstat -gccause pid 2000 每格2秒 ...
随机推荐
- JVM 方法区是否会出现内存溢出?
JVM 方法区是否会出现内存溢出? 方法区内存溢出的可能性 方法区是 JVM 内存中的一个重要组成部分,存储类的元信息.静态变量和运行时常量池等.尽管它是一个独立的内存区域,但如果内存使用过多,也可能 ...
- Go 切片的扩容规则是怎么样的
切片是动态数组,容量是根据元素动态增加的. 本来想看看源码怎么写的,发现切片追加元素的方法是内置的,看起来还挺麻烦 源码位于 builtin.go 中: // The append built-in ...
- 腾讯CodeBuddy,一款自带MCP市场的编程助手
今天我发现了一个非常实用的腾讯云编程助手--CodeBuddy.之前它的名称是腾讯云代码助手,但现在已经正式更名为CodeBuddy,并且在更名的同时,其功能也得到了显著增强.今天,我们将详细了解一下 ...
- 【HUST】网安|操作系统实验|实验四 设备管理、文件管理
文章目录 任务 任务1 编写一个Linux内核模块,并完成安装/卸载等操作. 1. 提示 2. 任务代码 3. 结果及说明 任务2 编写Linux驱动程序并编程应用程序测试. 1. 提示 2. 任务代 ...
- HarmonyOS NEXT实战教程:菜谱App
随着鸿蒙系统5.0的发布,兼容的机型越来越多,对于开发者来说机会也越来越多,大家不要气馁,学习鸿蒙肯定会有用武之地,我们要做的就是做好准备. 今天跟大家分享实战教程是一个菜谱App. ListO& ...
- 玩转C++11多线程:让你的程序飞起来的std::thread终极指南
大家好,我是小康. 你还在为 C++ 多线程编程发愁吗?别担心,今天咱们就用大白话彻底搞定std::thread!看完这篇,保证你对C++11多线程的理解从"一脸懵逼"变成&quo ...
- 保姆教程系列:生成 SSH Key 并配置连接远程仓库
@ 目录 前言 第 1 步:检查是否已有 SSH Key 第 2 步:生成新的 SSH Key 第 3 步:启动 SSH Agent 并添加密钥 第 4 步:复制 SSH 公钥 第 5 步:添加 SS ...
- 信息工程大学第五届超越杯程序设计竞赛(同步赛)A遗失的旋律
题目链接 :A-遗失的旋律_信息工程大学第五届超越杯程序设计竞赛(同步赛) (nowcoder.com) 本场比赛的数据都很水,导致很多题暴力都能过,(出题人背大锅, 说实话,如果数据不水, 这场感觉 ...
- Qt 的一个大坑:visual studio中setStyleSheet不支持jpg
在代码中设置QWidget的背景图,一般会使用setStyleSeeht函数去设置样式表: border-image:("c:x.png"); 这里有个大坑:不支持jpg图片!
- MyBatis-Plus实现部分字段存取加解密
前言 网上教程大致有两种 1.基于MyBatis-Plus自定义类型处理器(TypeHandler)的方法 2.基于MyBatis的方法(拦截器) 这里使用的第二种,为了保护隐私,这里把package ...