Java的永久代的那些事

本文深入探讨了Java8中永久代的替代方案——元空间。从JDK8开始,类的元数据信息被移至本地内存区域,解决了永久代调优困难、内存溢出等问题,提升了垃圾回收效率。

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

方法区:是JVM的一种规范,存放类信息、常量、静态变量、即时编译器编译后的代码等;
永久代:是HotSpot的一种具体实现,实际指的就是方法区,

备注:由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。

JDK7之前,字符串常量池被存储在永久代(默认大小是4m)中,因此导致性能问题和OOM;实际上1.7时,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap;

JDK8后,永久代被移到与一个堆不相连的本地内存区域,也即就是元空间
由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。

元空间虚拟机采用了组块分配的形式,会导致内存碎片存在。

JDK 8 中永久代向元空间的转换的原因:

  • 1、字符串存在永久代中,容易出现性能问题和内存溢出。
  • 2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
  • 3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
  • 4、Oracle 可能会将HotSpot 与 JRockit 合二为一。

在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm区的,Perm区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 是会直接产生java.lang.OutOfMemoryError:PermGen space错误的。
在 jdk7 的版本中,字符串常量池已经从Perm区移到正常的Java Heap区域了。为什么要移动,Perm 区域太小是一个主要原因,
Jdk8已经直接取消了Perm区域,而新建立了一个元区域。应该是jdk开发者认为Perm区域已经不适合现在 JAVA 的发展了。

参考:
Java永久代去哪儿了
Java8内存模型—永久代(PermGen)和元空间(Metaspace)

详细说明

在JVM中,类包含其对应的元数据,比如类的层级信息,方法数据和方法信息(如字节码,栈和变量大小),运行时常量池,已确定的符号引用和虚方法表。

类几乎是“静态的”并且很少被卸载和回收,因此类也可以被看成“永久的”。

在JDK8之前的HotSpot虚拟机中,类的这些“永久的”数据存放在一个叫做永久代的区域。永久代一段连续的内存空间;

在JVM启动之前可以通过设置-XX:MaxPermSize的值来控制永久代的大小;
永久代的垃圾回收和老年代的垃圾回收是绑定的,一旦其中一个区域被占满,这两个区都要进行垃圾回收;

由于我们可以通过‑XX:MaxPermSize 设置永久代的大小,一旦类的元数据超过了设定的大小,程序就会耗尽内存,并出现内存溢出错误(OOM);

在JDK7之前的HotSpot虚拟机中,纳入字符串常量池的字符串被存储在永久代中,因此导致了一系列的性能问题和内存溢出错误;

辞永久代,迎元空间

随着Java8的到来,我们再也见不到永久代了。但是这并不意味着类的元数据信息也消失了。这些数据被移到了一个与堆不相连的本地内存区域,这个区域就是我们要提到的元空间;

这项改动是很有必要的,因为对永久代进行调优是很困难的。永久代中的元数据可能会随着每一次Full GC发生而进行移动。并且为永久代设置空间大小也是很难确定的,因为这其中有很多影响因素,比如类的总数,常量池的大小和方法数量等。

同时,HotSpot虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化Full GC以及对以后的并发隔离类元数据等方面进行优化。

 

image.png

移除永久代的影响

由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。因此,我们就不会遇到永久代存在时的内存溢出错误,也不会出现泄漏的数据移到交换区这样的事情。最终用户可以为元空间设置一个可用空间最大值,如果不进行设置,JVM会自动根据类的元数据大小动态增加元空间的容量。

元空间内存管理

元空间的内存管理由元空间虚拟机来完成。先前,对于类的元数据我们需要不同的垃圾回收器进行处理,现在只需要执行元空间虚拟机的C++代码即可完成。在元空间中,类和其元数据的生命周期和其对应的类加载器是相同的。话句话说,只要类加载器存活,其加载的类的元数据也是存活的,因而不会被回收掉。

我们从前文到现在提到的元空间稍微有点不严谨。准确的来说,每一个类加载器的存储区域都称作一个元空间,所有的元空间合在一起就是我们一直说的元空间。当一个类加载器被垃圾回收器标记为不再存活,其对应的元空间会被回收。在元空间的回收过程中没有重定位和压缩等操作。但是元空间内的元数据会进行扫描来确定Java引用。

元空间虚拟机负责元空间的分配,其采用的形式为组块分配。组块的大小因类加载器的类型而异。在元空间虚拟机中存在一个全局的空闲组块列表。当一个类加载器需要组块时,它就会从这个全局的组块列表中获取并维持一个自己的组块列表。当一个类加载器不再存活,那么其持有的组块将会被释放,并返回给全局组块列表。类加载器持有的组块又会被分成多个块,每一个块存储一个单元的元信息。组块中的块是线性分配(指针碰撞分配形式)。组块分配自内存映射区域。这些全局的虚拟内存映射区域以链表形式连接,一旦某个虚拟内存映射区域清空,这部分内存就会返回给操作系统。

image.png

元空间调优与工具

元空间虚拟机控制元空间的增长

存在的问题

前面已经提到,元空间虚拟机采用了组块分配的形式,同时区块的大小由类加载器类型决定。类信息并不是固定大小,因此有可能分配的空闲区块和类需要的区块大小不同,这种情况下可能导致碎片存在。元空间虚拟机目前并不支持压缩操作,所以碎片化是目前最大的问题。

内存碎片



作者:小小少年Boy
链接:https://www.jianshu.com/p/c1ac5e7a5f87
 

<think>嗯,用户想了解Java永久PermGen内存分配及调优。首先,我需要回忆一下PermGen的基本概念。根据引用[1],PermGen是JVM中存储元数据的区域,比如类信息、方法、常量池等。在Java 8之前,PermGen是存在的一个内存区域,但之后被元空间Metaspace取了。所以,我得先说明PermGen的用途和结构。 接下来,用户可能想知道为什么会有PermGen相关的内存问题,比如OutOfMemoryError。引用[2]提到java.lang.OutOfMemoryError: PermGen space异常,这说明当PermGen空间不足时会发生错误。这时候需要调优,比如调整-XX:PermSize和-XX:MaxPermSize参数。但要注意,这些参数在Java 8之后不再适用,因为Metaspace取了PermGen,所以调优方法也有所不同,比如使用-XX:MaxMetaspaceSize。 然后,需要解释PermGen的分配机制。根据引用[3],在JDK7之前,类的元数据、静态变量和字符串常量都存放在PermGen中。但随着应用动态加载类的增加,比如使用框架如Spring、Hibernate,PermGen容易耗尽,导致内存溢出。这时候需要调整JVM参数来扩大PermGen的大小,或者优化码减少类的加载。 另外,用户可能想知道如何解决PermGen的问题。除了调整内存参数,还可以使用类卸载,但PermGen中的类只有在加载器被回收时才能被卸载,所以需要确保类加载器不再被引用。此外,升级到Java 8使用Metaspace也是一个解决方案,因为Metaspace使用本地内存,并且可以动态调整大小,减少了OOM的风险。 还需要注意,引用[4]提到Metaspace的监控和调优方法,比如使用jstat或VisualVM工具来观察内存使用情况。这可能对用户有帮助,尤其是在迁移到Java 8之后的情况。 总结一下,回答应该包括PermGen的定义、常见内存问题、调优方法以及迁移到Metaspace的建议。同时,要确保引用相关的资料,比如引用[1]-[4],来支持各个观点。另外,用户可能对PermGen和Metaspace的区别不太清楚,需要简明扼要地解释两者的不同,以及为什么Metaspace更优。</think>### Java 永久(PermGen)内存分配及调优 #### 一、永久(PermGen)基本概念 永久是JVM内存模型中用于存储**类元数据**、**常量池**、**静态变量**、**方法码**等数据的区域[^1]。它在JDK 7及之前版本中属于堆内存的一部分,但采用独立管理方式。例如: $$ \text{JVM内存结构} = \text{堆(Heap)} + \text{非堆(PermGen)} + \text{栈(Stack)} $$ #### 二、常见内存问题 1. **`java.lang.OutOfMemoryError: PermGen space`** 当应用程序频繁动态加载类(如使用反射、动态理或框架如Spring、Hibernate时),可能导致PermGen空间耗尽[^2]。 2. **类卸载困难** PermGen中的类信息仅在对应的类加载器被回收时才能卸载。若存在内存泄漏(如未关闭的线程上下文类加载器),会导致PermGen无法释放。 #### 三、调优方法(JDK 7及之前) 1. **调整PermGen大小** 通过JVM参数手动设置初始值和最大值: ```shell -XX:PermSize=256m -XX:MaxPermSize=512m ``` 2. **优化码行为** - 减少动态生成类(如CGLIB理) - 避免重复加载同一类 - 使用`-XX:+CMSClassUnloadingEnabled`启用类卸载支持 3. **监控工具** 使用`jstat -gcpermcapacity <pid>`或VisualVM观察PermGen使用情况。 #### 四、从PermGen到元空间(Metaspace) JDK 8及之后版本使用**元空间(Metaspace)** 替PermGen[^3],核心改进包括: - 元数据存储在本地内存(Native Memory)而非JVM堆中 - 动态调整上限(默认无限制,可通过`-XX:MaxMetaspaceSize`限制) - 自动触发垃圾回收,减少手动调优需求 #### 五、迁移到元空间后的调优建议 1. 监控元空间使用: ```shell jstat -gcmetacapacity <pid> ``` 2. 设置上限防止失控: ```shell -XX:MaxMetaspaceSize=512m ``` 3. 调整元空间垃圾回收阈值: ```shell -XX:MetaspaceSize=128m ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值