性能文章>JVM高阶面试:Java8为什么使用元空间替换永久代?>

JVM高阶面试:Java8为什么使用元空间替换永久代?原创

4月前
223357

1. 方法区简介

JVM 的内存模型主要包括程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、堆(Heap)和方法区(Method Area)。

方法区(Method Area)是所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

具体来说,方法区用来存储以下数据:

  1. 类的元数据信息:包括类的名称、访问标志、父类、接口、字段、方法等信息。

  2. 运行时常量池:在Java代码中,常量可以被直接定义在类或接口中,这些常量在编译后被存储在Class文件的常量池中,而运行时常量池则是从Class文件中加载的。

  3. 静态变量和常量:类的静态变量和常量都存储在方法区中,它们在类加载的时候被初始化并分配内存空间。

  4. 方法字节码:在Java中,方法的字节码被编译成Class文件并存储在方法区中。

  5. 即时编译器(JIT)编译后的代码:为了提高程序的执行效率,JIT会将热点代码编译成本地机器码并存储在方法区中。

方法区只是 JVM 规范中定义的一个概念,针对 Hotspot 虚拟机,Java8 之前使用永久代(Permanent Generation,简称 PermGen)实现,而 Java8 之后使用元空间(Metaspace)实现。

JDK8 之前可以通过 -XX:PermSize-XX:MaxPermSize 来设置永久代大小,JDK8 之后,使用元空间替换了永久代,改为通过 -XX:MetaspaceSize-XX:MaxMetaspaceSize 来设置元空间大小。

2. 永久代问题

2.1 内存溢出

永久代的空间是有限制的,可以通过 -XX:PermSize 设置永久代初始容量,通过-XX:MaxPermSize 设置永久代最大容量。

但是当加载过多的类或者常量的时候,就可能导致永久代的空间不足,抛出 java.lang.OutOfMemoryError: PermGen space 异常。尤其是web应用会使用很多框架,这些框架会动态加载很多基础类,更容易导致OOM。

2.2 垃圾回收效率低下

永久代中的类信息一般是在应用程序运行期间不会发生变化的,因此,如果开启了永久代的垃圾回收,就会造成大量的垃圾回收操作,导致垃圾回收效率低下,甚至会引起应用程序的暂停。

此外,由于永久代主要存放 JVM 加载的类信息等永久存在的数据,这使得它在垃圾回收过程中的回收效率相对较低。在某些情况下,频繁触发的 Full GC 不仅无法有效回收永久代空间,还会严重影响 JVM 的性能。

2.3 无法动态调整大小

永久代的大小一旦被设置,就无法动态调整,如果预估错误,就可能导致浪费内存或内存不足的问题。

2.4 无法回收常量池中的内存

在永久代中,常量池是一个非常重要的部分,但是其中的常量无法被回收,即使这些常量已经不再被使用,也无法被垃圾回收器回收,这会浪费内存。

3. 元空间简介

元空间(Metaspace)是 Java8 中引入的一个新概念,用来替代原来的永久代。与永久代不同,元空间并不在虚拟机中,而是存储在本地内存(Native Memory)。

从 Java7 已经开始逐步移除永久代,在Java7中把 interned Strings 、 class statics 和 String Pool 从永久代移到堆中。在 Java8 中彻底移除了永久代,把将类的元数据信息、常量、静态变量、即时编译器编译后的代码从永久代中移到了元空间中。

4. 元空间的优点

与永久代相比,使用元空间使用方法区具有以下优点:

  1. 突破内存限制,减少OOM。 由于元空间使用的是本地内存,而不是 JVM 内存,因此理论上,其大小只受限于操作系统的实际可用内存。这大大减少了内存溢出的可能性。相较于永久代在 JVM 堆中预分配的有限空间,元空间的引入提供了更大的空间来存储类元数据。

  2. 提高 Full GC 的效率。 在永久代中,Full GC 的触发比较频繁,而且效率较低。因为永久代中存放了很多 JVM 需要的类信息,这些数据大多数是不会被清理的,所以 Full GC 往往无法回收多少空间。但在元空间模型中,由于字符串常量池已移至堆中,静态变量也移至 Java 堆或者本地内存,因此可以更有效地进行垃圾回收,避免了因频繁的 Full GC 导致的性能影响。

  3. 满足不同的类加载需求和动态类加载的情况。 在一些大型的、模块化的应用中,可能需要加载大量的类,这就需要大量的元数据存储空间。元空间可以动态地调整大小,能更好地满足这种需求。

  4. 避免永久代调优和大小设置的复杂性。 在 Java8 之前的版本中,通常需要手动设置永久代的大小,以避免内存溢出的错误。这增加了应用的配置和管理的复杂性。而元空间使用本地内存,根据实际需求动态调整,大大简化了内存管理的复杂性。

5. 元空间问题

尽管元空间解决了永久代的一些问题,可能也同时引入了一些新问题:

  • 可能导致本地内存溢出:虽然元空间使用的是本地内存,理论上其大小只受限于操作系统的实际可用内存,但是如果元空间的使用不加以控制,可能会导致大量的本地内存被占用,从而导致 OutOfMemoryError。

  • 内存管理和调优策略:永久代的内存管理和调优策略无法直接应用到元空间,需要重新考虑和设计。例如,如何确定元空间的初始大小、最大大小,如何进行垃圾回收,等等。

因此,虽然元空间为 JVM 的内存管理带来了新的可能,但也带来了新的挑战。为了充分利用元空间的优势,开发者需要理解其工作原理,掌握正确的使用和调优方法。

6. 总结

Java8 选择使用元空间(Metaspace)替代永久代(PermGen)是 JVM 内存模型的一次重大改进。解决了永久代面临的空间限制、低效的垃圾回收、以及复杂的内存管理等问题。元空间利用本地内存,能够动态调整大小,提供了更大的空间来存储类元数据,也更好地适应了大型、模块化应用的需求。

但是元空间也引入了一些新问题。如何避免本地内存溢出,如何制定有效的内存管理和调优策略,都是开发者需要重新考虑的问题。

 

点赞收藏
一灯架构

只分享有趣的技术干货

请先登录,查看5条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

万字完整深入解析JVM面试必备,原来这就是和年薪百万的差距

万字完整深入解析JVM面试必备,原来这就是和年薪百万的差距

搭稳Netty开发的地基,用漫画帮你分清同步异步阻塞非阻塞

搭稳Netty开发的地基,用漫画帮你分清同步异步阻塞非阻塞

为什么 JVM 叫做基于栈的 RISC 虚拟机

为什么 JVM 叫做基于栈的 RISC 虚拟机

如何随心所欲调试HotSpot VM源代码?(改造为CMakeLists项目)

如何随心所欲调试HotSpot VM源代码?(改造为CMakeLists项目)

朋友们,就在今天,JDK 21,它终于带着重磅新特性正式发布了!!!

朋友们,就在今天,JDK 21,它终于带着重磅新特性正式发布了!!!

13 轻量级锁的重入 以及 线程1获取轻量级锁并释放线程2获取锁 的调试

13 轻量级锁的重入 以及 线程1获取轻量级锁并释放线程2获取锁 的调试

7
5