8 种 Java- 内存溢出之三 -Permgen space

简介: 8 种 Java- 内存溢出之三 -Permgen space

3.1 Permgen space 概述

Java 应用只允许使用有限的内存. 你的应用的内存大小是在启动的时候指定好的. 进一步来说, Java 内存被分成 2 个不同的区域, 如下图:

这些区域, 包括 perm 区, 会在 JVM 启动时设置. 如果你没有设置, 会使用与平台有关的默认配置.

java.lang.OutOfMemoryError: PermGen Space 消息表示 永久代 (Permgen) 内存耗尽.

3.2 原因

要理解 java.lang.OutOfMemoryError: PermGen Space 的原因, 我们需要理解这个特殊的内存区域是用来干嘛.

实际上, 永久代主要是加载和存储类声明. 包括组成类的名字和字段 (fields), 方法的字节码, 常量池信息, 对象数组和类型数组, 以及 实时编译 (Just In Time compiler) 优化.

从上边的定义, 你可以推断出 PerGen 大小需求取决于 加载的类的数量 这些类声明的大小 . 因此我们可以说, java.lang.OutOfMemoryError: PermGen Space 的主要原因是: 太多类或者太大的类被加载到永久代.

3.3 示例

3.3.1 极简案例

正如之前的描述, 永久代的使用量和加载到 JVM 里的类的数量强相关. 下列代码就是最直接的例子:

import javassist.ClassPool;
 
public class MicroGenerator {
    public static void main(String[] args) throws Exception {
        for (int i = 0;i<100_000_000;i++) {
            genetate("eu.plumbr.demo.Generated" + i);
        }
    }
 
    public static Class generate(String name) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        return pool.makeClass(name).toClass();
    }
}
JAVA

在本例中,源代码在运行时循环迭代并生成类. 类 javassist 库对生成的复杂性进行了处理.

上面的代码将持续生成新的类并将其定义加载到永久代中直到该区被占满, 并且抛出java.lang.OutOfMemoryError: PermGen Space

3.3.2 重部署案例

一个更复杂和现实的例子, 我们经常会在 应用重部署 时出现 java.lang.OutOfMemoryError: PermGen Space 错误. 当你重新部署一个应用时, 您的意图是删除以前的类加载器引用的所有以前加载的类,并将其替换新的类加载器加载新版本的类。

不幸的是, 很多 第三方库 处理不当的资源 线程 , JDBC 驱动文件系统句柄 使卸载以前使用的类加载器变得不可能. 这反过来意味着: 在每次重新部署期间,您的类的前一个版本仍然驻留在 PermGen 中,在每次重新部署时生成数十 MB(甚至更多)的垃圾。

我们假设一个示例应用通过 JDBC 驱动连接到一个关系型数据库. 当应用启动时, 初始化代码加载 JDBC 驱动来连接数据库. 对应于规范,JDBC 驱动程序将自己注册到 java.sql.DriverManager。这个注册包含存储在一个静态的驱动程序管理(DriverManager) 字段中的一个驱动实例.

现在, 当应用从应用服务器卸载, java.sql.DriverManager仍然会持有那个引用. 最后,我们对驱动类进行了实时引用,而驱动类又引用了用于加载应用程序的java.lang.Classloader

java.lang.Classloader的那个实例 仍然引用这个应用的所有的类, 通常会在 Perm 区里占用数十 MB 内存 . 这也意味着: 只需几次重新部署就可以填充一个常见大小的 PermGen 并在日志中出现java.lang.OutOfMemoryError: PermGen Space 错误.

3.4 解决方案

3.4.1 解决初始化时的 OutOfMemoryError

当由于 PermGen 耗尽导致在应用运行时出现 OutOfMemoryError 错误, 解决方案很简单. 应用只需要更多的空间来加载所有类到 Perm 区, 因此我们只需要增加它的大小. 要这么做, 调整应用启动配置, 并添加 (如果有就增加)-XX:MaxPermSize 参数如下:

java -XX:MaxPermSize=512m com.yourcompany.YourClass

上述配置会告诉 JVM, Perm 区在开始报 OutOfMemoryError 之前允许增大到 512MB.

3.4.2 解决重部署时的 OutOfMemoryError

当 OutOfMemoryError 就发生在你重新部署应用之后的时候, 你的应用有 类加载器泄漏 的问题. 这时, 你应该做 heap dump 分析 - 在重部署后做这个 heap dump:

jmap -dump:format=b,file=dump.hprof <process-id>

然后用你最喜欢的 heap dump 分析工具 (Eclipse MAT 是个好工具) 打开. 在分析工具中, 你可以看 重复的类(duplicate classes), 特别是你自己的应用的. 从那里, 你需要处理所有的类加载器来找到当前活动的类加载器.

对于非活动的类加载器, 您需要通过 从非活动类加载器获取最短 GC root 路径 来确定 阻止它们来进行 gc 的引用。 有了这些信息, 你就能定位 root cause. 如果 root cause 是第三方库, 你可以通过 Google/StackOverflow 来搜索是否这是一个已知问题来获取 patch 或解决方法. 如果是你自己的代码, 你需要避免违规引用.

3.4.3 解决运行时的 OutOfMemoryError

当应用在运行时 PermGen 内存溢出, 联系我就是最好的方式(@ ̄ー ̄@).

另一种可选的, 不用联系我的方法也是可行的. 在这种情况下第一步就是要检查是否 GC 允许卸载来自 PerGen 的这些类. 一般 JVM 在这方面是相当保守的 – 类是永生不灭的. 所以一旦加载, 即使没有代码再使用它们, 它们仍然会呆在内存中. 这就会变成一个问题: 当应用创建了大量的 动态类 , 而且生成的这些类是不需要长久存在的. 在这种情况下, 允许 JVM 卸载类定义 会有所帮助. 在你的启动脚本种加入以下字段即可实现:

-XX:+CMSClassUnloadingEnabled

默认这是设为 false 的, 所以要启用这个, 你需要显示地在 Java 选项中设置下列参数. 如果你启用了CMSClassUnloadingEnabled, GC 将也会清理 PermGen, 移除不再需要使用的类. 要记住这个参数只在UseConcMarkSweepGC 也启用的时候才会生效. 所以, 当使用并行 GC, 或者, 我的天呐 – 串行 GC 时, 确保你指定你的 GC 策略到 CMS 通过:

-XX:+UseConcMarkSweepGC

如果类可以卸载, 问题仍然存在, 你应该做 heap dump 分析 – 用类似如下的命令:

jmap -dump:file=dump.hprof,format=b <process-id>

然后用你的最爱的 heap dump 分析工具 (如: Eclipse MAT) 打开这个 dump, 找到 加载最多类的类加载器 . 从这个类加载器中, 您可以继续提取加载的类,并 通过实例对这些类进行排序,以获得最大的怀疑项列表。

对于每个可疑项,您需要手工地追溯 root cause 到生成此类类的应用程序代码.

后续会有一篇我通过 Dynatrace 分析某财险公司 运行时的 Perm 区 OutOfMemoryError的案例

相关文章
|
9月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
1088 3
|
10月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
8月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
251 4
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
383 0
|
8月前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
9月前
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
1027 17
|
10月前
|
监控 Kubernetes Java
最新技术栈驱动的 Java 绿色计算与性能优化实操指南涵盖内存优化与能效提升实战技巧
本文介绍了基于Java 24+技术栈的绿色计算与性能优化实操指南。主要内容包括:1)JVM调优,如分代ZGC配置和结构化并发优化;2)代码级优化,包括向量API加速数据处理和零拷贝I/O;3)容器化环境优化,如K8s资源匹配和节能模式配置;4)监控分析工具使用。通过实践表明,这些优化能显著提升性能(响应时间降低40-60%)同时降低资源消耗(内存减少30-50%,CPU降低20-40%)和能耗(服务器功耗减少15-35%)。建议采用渐进式优化策略。
571 2
|
Java 物联网 数据处理
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
Java Solon v3.2.0 是一款性能卓越的后端开发框架,新版本并发性能提升700%,内存占用节省50%。本文将从核心特性(如事件驱动模型与内存优化)、技术方案示例(Web应用搭建与数据库集成)到实际应用案例(电商平台与物联网平台)全面解析其优势与使用方法。通过简单代码示例和真实场景展示,帮助开发者快速掌握并应用于项目中,大幅提升系统性能与资源利用率。
333 6
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
|
11月前
|
SQL 缓存 安全
深度理解 Java 内存模型:从并发基石到实践应用
本文深入解析 Java 内存模型(JMM),涵盖其在并发编程中的核心作用与实践应用。内容包括 JMM 解决的可见性、原子性和有序性问题,线程与内存的交互机制,volatile、synchronized 和 happens-before 等关键机制的使用,以及在单例模式、线程通信等场景中的实战案例。同时,还介绍了常见并发 Bug 的排查与解决方案,帮助开发者写出高效、线程安全的 Java 程序。
568 0