为什么Java 8中取消了永久代?

  • Post category:Java

Java 8取消了永久代的原因是为了解决永久代的一些痛点和限制。永久代是Java 7及其之前版本的内存布局,用于存储类的元数据信息、字符串常量池等非堆内存数据。然而永久代存在一些问题:

  1. 大小限制:永久代的大小必须在JVM启动时指定,且不易动态调整。如果类的元数据信息等非堆内存数据太多,可能导致永久代OOM(Out Of Memory),而难以通过调整堆大小来解决。
  2. 并发访问:永久代是一块不可并发收集的内存区域,可能会导致一些原本可以并发进行的GC操作需要STW(Stop-The-World)暂停所有线程才能进行,对性能和用户体验产生不良影响。
  3. 业务限制:对于一些需要动态生成类的业务场景,永久代的大小可能无法确定,可能会导致需要人工调整JVM启动参数并重启服务,无法动态适应不同的业务需求。

为了解决这些问题,Java 8取消了永久代,采用元数据空间(Metaspace)取代了永久代。元数据空间是一块可扩展的内存区域,存储类的元数据信息等非堆内存数据。它的优点如下:

  1. 大小弹性:元数据空间可根据应用的需要动态调整大小。
  2. 并发访问:元数据空间支持并发收集,不存在永久代的STW问题。
  3. 业务支持:元数据空间可以根据业务需求自动调整大小,不需要手工调整JVM启动参数。

但是也要注意到,使用元数据空间,可能会出现内存泄漏等问题,需要进行合理的监测和管理。

以下是两条示例说明:

示例1:

在Java 7中配置永久代大小为100M,并启动了一个SpringMVC应用,内存占用巨大,服务频繁报OOM错。此时,需要重新调整JVM启动参数,修改永久代大小为200M,再重新启动服务,这样不仅需要重启,还涉及一定风险。

在Java 8中,取消了永久代的限制,如果同上述的应用程序在JVM启动时增加配置-XX:MetaspaceSize=200M即可。

示例2:

一个Java 7应用中,需要频繁动态生成类。如果生成的类过多,可能会导致永久代OOM,服务崩溃。此时,需要人工调整JVM启动参数,增加永久代的大小,并重启服务。

Java 8中,由于取消了永久代,可以采用以下方式来动态生成类:

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
ClassWriter classWriter = new ClassWriter(0);
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "GeneratedClass", null, "java/lang/Object", null);
classWriter.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "message", "Ljava/lang/String;", null, "Hello world!");
classWriter.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null).visitCode();
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "hello", "()Ljava/lang/String;", null, null);
mv.visitCode();
mv.visitLdcInsn("Hello world!");
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
classWriter.visitEnd();
byte[] code = classWriter.toByteArray();
Class<?> generatedClass = classLoader.defineClass("GeneratedClass", code, 0, code.length);
Method helloMethod = generatedClass.getDeclaredMethod("hello");
String result = helloMethod.invoke(null).toString();
System.out.println(result);

这就可以在运行时动态生成类,无需永久代的限制和手工调整JVM启动参数。