java8中使用Metaspace就不会出现OOM吗?

本文介绍了Java 8中Metaspace的内存模型及其容量特点,并通过实验演示了当Metaspace达到极限时如何触发OOM(Out Of Memory)错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于jvm测试的一些代码我统一整理在了git上,我的git地址为:

https://github.com/wenjieyatou/jvmtest

在java8中,Metaspace的出现,使我们现在不会再遇到java.lang.OutOfMemoryError: PermGen问题,但是我们要记住,这个新特性并不会使类加载导致的内存泄露就此消失。

(一)Metaspace的简单介绍
(1)内存模型:大部分类元数据都在本地内存分配,用于描述类元数据的“klasses“已经被移除。
  (2) 容量:默认情况下只受本地内存限制,但是我们可以限制。

(二)接下来看看Metaspace会不会出现OOM

当我们跑我们的代码:

ClassMetadataLeakSimulator

这个类是不断通过反射来设置创造元数据,在没有对堆栈内存限制的情况下,是可以自动扩充的。可以看出,Metaspace进行了动态扩展,本地内存达到了300多M,加载超过5万个类后还没有出现OOM事件。接下来我们限定一下

但是当我们设置运行参数:


在执行时,就会出现异常。

ClassMetadataLeakSimulator
Class metadata leak simulator
[GC (Metadata GC Threshold) [PSYoungGen: 173016K->14024K(917504K)] 173016K->14032K(1966080K), 0.1189913 secs] [Times: user=0.25 sys=0.01, real=0.14 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 14024K->0K(917504K)] [ParOldGen: 8K->13933K(1048576K)] 14032K->13933K(1966080K), [Metaspace: 13453K->13453K(1064960K)], 0.2131648 secs] [Times: user=0.50 sys=0.00, real=0.21 secs] 
[GC (Metadata GC Threshold) [PSYoungGen: 110101K->11488K(917504K)] 124034K->25421K(1966080K), 0.0272360 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 11488K->0K(917504K)] [ParOldGen: 13933K->25280K(1048576K)] 25421K->25280K(1966080K), [Metaspace: 21815K->21815K(1075200K)], 0.1506802 secs] [Times: user=0.45 sys=0.00, real=0.15 secs] 
[GC (Metadata GC Threshold) [PSYoungGen: 188745K->19296K(917504K)] 214026K->44576K(1966080K), 0.0377606 secs] [Times: user=0.09 sys=0.00, real=0.04 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 19296K->0K(917504K)] [ParOldGen: 25280K->44321K(1048576K)] 44576K->44321K(1966080K), [Metaspace: 35828K->35828K(1089536K)], 0.2502764 secs] [Times: user=0.84 sys=0.02, real=0.25 secs] 
[GC (Metadata GC Threshold) [PSYoungGen: 283121K->32000K(917504K)] 327442K->76321K(1966080K), 0.0621390 secs] [Times: user=0.12 sys=0.00, real=0.06 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 32000K->0K(917504K)] [ParOldGen: 44321K->75904K(1048576K)] 76321K->75904K(1966080K), [Metaspace: 59015K->59015K(1116160K)], 0.3571642 secs] [Times: user=1.17 sys=0.00, real=0.36 secs] 
[GC (Metadata GC Threshold) [PSYoungGen: 235934K->25920K(917504K)] 311839K->101824K(1966080K), 0.0463302 secs] [Times: user=0.14 sys=0.02, real=0.05 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 25920K->0K(917504K)] [ParOldGen: 75904K->101709K(1048576K)] 101824K->101709K(1966080K), [Metaspace: 78076K->78076K(1138688K)], 0.3398833 secs] [Times: user=1.23 sys=0.00, real=0.34 secs] 
[GC (Last ditch collection) [PSYoungGen: 0K->0K(1008128K)] 101709K->101709K(2056704K), 0.0031836 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Last ditch collection) [PSYoungGen: 0K->0K(1008128K)] [ParOldGen: 101709K->95434K(1048576K)] 101709K->95434K(2056704K), [Metaspace: 78076K->78076K(1138688K)], 0.8621092 secs] [Times: user=2.00 sys=0.00, real=0.86 secs] 
ERROR: java.lang.OutOfMemoryError: Metaspace
Done!
Heap
 PSYoungGen      total 1008128K, used 38912K [0x00000000c0000000, 0x0000000100000000, 0x0000000100000000)
  eden space 972800K, 4% used [0x00000000c0000000,0x00000000c2600180,0x00000000fb600000)
  from space 35328K, 0% used [0x00000000fdd80000,0x00000000fdd80000,0x0000000100000000)
  to   space 37888K, 0% used [0x00000000fb600000,0x00000000fb600000,0x00000000fdb00000)
 ParOldGen       total 1048576K, used 95434K [0x0000000080000000, 0x00000000c0000000, 0x00000000c0000000)
  object space 1048576K, 9% used [0x0000000080000000,0x0000000085d32898,0x00000000c0000000)
 Metaspace       used 78084K, capacity 130944K, committed 131072K, reserved 1138688K
  class space    used 12068K, capacity 42536K, committed 42536K, reserved 1048576K

Process finished with exit code 0
可以看出,此时Metaspace被耗尽,将会抛出OOM事件。这里要注意MaxPermSize的区别,MaxMetaspaceSize并不会在jvm启动的时候分配一块这么大的内存出来,而MaxPermSize是会分配一块这么大的内存的。

那么问题来了,如果我们不设置MaxMetaspaceSize,那么我们能用到系统的全部内存吗??
metaspace很可能因为被无止境使用而被OS Kill,所以一般还是设置比较好。

<think>是的,我可以帮助排查Java中的内存溢出(OOM)问题。排查OOM通常涉及以下步骤: 1. **确认错误类型**: - 常见的OOM错误类型: - `java.lang.OutOfMemoryError: Java heap space`:堆空间不足 - `java.lang.OutOfMemoryError: Metaspace`:元空间(方法区)溢出 - `java.lang.OutOfMemoryError: unable to create new native thread`:线程创建过多 - `java.lang.OutOfMemoryError: GC overhead limit exceeded`:GC效率低下 2. **获取内存快照**: - 添加JVM参数在OOM时自动生成堆转储文件: ```bash -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump ``` 3. **使用分析工具**: - **Eclipse MAT**:分析堆转储文件,查找内存泄漏 - **VisualVM**:实时监控堆内存使用情况 - **jstat**:命令行监控GC统计信息 ```bash jstat -gcutil <pid> 1000 # 每秒打印一次GC情况 ``` 4. **代码修复示例**: **场景:内存泄漏(静态集合持有对象引用)** ```java public class MemoryLeak { static List<Object> list = new ArrayList<>(); // 静态集合长期持有对象 public void addData() { for (int i = 0; i < 100000; i++) { list.add(new byte[1024]); // 添加大对象 } } } ``` **修复方案**: ```java public class FixedMemoryLeak { List<Object> list = new ArrayList<>(); // 改为非静态 public void addData() { // 方法执行完后list可被GC回收 for (int i = 0; i < 100000; i++) { list.add(new byte[1024]); } } } ``` 5. **元空间溢出处理**: - 增大元空间大小: ```bash -XX:MaxMetaspaceSize=256m ``` - 检查类加载器泄漏(如动态生成类未卸载) --- ### 常见OOM场景及解决方案 | 错误类型 | 原因 | 解决方案 | |----------------------------------|-----------------------|----------------------------| | `Java heap space` | 对象过多/内存泄漏 | 分析堆转储,修复泄漏 | | `Metaspace` | 加载类过多 | 增大元空间,检查类加载泄漏 | | `unable to create new native thread` | 线程数超过系统限制 | 减少线程数或调整系统限制 | | `GC overhead limit exceeded` | GC回收效率低(>98%时间在GC) | 检查内存泄漏或增大堆内存 | §§相关问题§§: 1. 如何通过 `jmap` 命令手动获取堆转储文件? 2. Eclipse MAT 中的 `Shallow Heap` 和 `Retained Heap` 分别代表什么? 3. 哪些 JVM 参数可以调整元空间大小? 4. 如何诊断和解决 `java.lang.OutOfMemoryError: Requested array size exceeds VM limit`?</think>### 如何排查 Java 内存溢出(OOM)问题? #### 排查步骤: 1. **确认 OOM 类型** 通过错误日志识别具体类型: ```java java.lang.OutOfMemoryError: Java heap space // 堆内存溢出 java.lang.OutOfMemoryError: Metaspace // 元空间溢出 java.lang.OutOfMemoryError: unable to create new native thread // 线程溢出 java.lang.OutOfMemoryError: Requested array size exceeds VM limit // 数组过大 ``` 2. **获取内存快照** 添加 JVM 参数在 OOM 时自动生成堆转储: ```bash -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof ``` 3. **使用分析工具** | 工具 | 用途 | 关键操作 | |--------------|-----------------------------|----------------------------| | **Eclipse MAT** | 分析堆转储文件 | 查看 `Dominator Tree` 和 `Leak Suspects` | | **VisualVM** | 实时监控内存使用 | 抽样器 → 内存 | | **jcmd** | 命令行获取堆转储 | `jcmd <pid> GC.heap_dump /path/to/dump.hprof` | 4. **关键诊断命令** ```bash # 监控堆内存使用 jstat -gcutil <pid> 1000 # 每秒输出一次GC数据 # 查看对象分布 jmap -histo:live <pid> | head -20 ``` --- ### 常见 OOM 场景及解决方案 #### 1. 堆内存溢出(Java heap space) **原因**:对象数量超过堆容量,通常是内存泄漏或大对象分配 **修复示例**: ```java // 错误:静态集合导致内存泄漏 public class MemoryLeak { static List<byte[]> cache = new ArrayList<>(); // 静态集合长期持有对象 public void addData() { cache.add(new byte[10 * 1024 * 1024]); // 每次添加10MB } } // 修复:移除静态修饰符,使用弱引用 public class FixedMemoryLeak { List<byte[]> cache = new ArrayList<>(); // 改为实例变量 public void addData() { // 添加本地处理逻辑,确保对象可回收 byte[] data = new byte[10 * 1024 * 1024]; process(data); // 处理数据 // 不长期持有引用 } } ``` #### 2. 元空间溢出(Metaspace) **原因**:加载过多类(如动态生成类、框架反射滥用) **解决方案**: ```bash # 增加元空间大小 -XX:MaxMetaspaceSize=256m ``` #### 3. 线程溢出(unable to create new native thread) **原因**:线程数超过系统限制 **诊断命令**: ```bash ulimit -u # 查看系统线程限制 cat /proc/<pid>/limits | grep processes ``` --- ### 内存分析技巧 1. **定位大对象** 在 MAT 中按 `Retained Heap` 排序,找出占用最大的对象 2. **识别内存泄漏** 对比多次堆转储,观察持续增长的对象 3. **GC 日志分析** 启用详细 GC 日志: ```bash -Xlog:gc*:file=gc.log -XX:+PrintGCDetails ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值