16

我有这段代码可以动态生成类并加载它

import javassist.CannotCompileException;
import javassist.ClassPool;

public class PermGenLeak {
    private static final String PACKAGE_NAME = "com.jigarjoshi.permgenleak.";

    public static void main(String[] args) throws CannotCompileException, InterruptedException {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            ClassPool pool = ClassPool.getDefault();
            pool.makeClass(PACKAGE_NAME + i).toClass();
            Thread.sleep(3);
        }

    }
}

我针对 Java 7 (jdk1.7.0_60) 启动了这个类,正如预期的那样,它填满了 PermGenSpace 并且堆仍未使用Java 7 内存使用情况 图像显示 permgen 使用超时,最后 JVM 被终止

现在相同的代码在 Java 8 (jdk1.8.0_40-ea) 上运行,并且正如预期的那样,它一直在扩展本机内存 (Metaspace),但令人惊讶的是,对于 1g 的 Metaspace,它在 OldGen 中消耗了 3g 的堆(随着时间的推移,几乎是 Metaspace 的 3 倍)

Java8 内存使用情况 图像显示 Metaspace 使用超时和系统内存使用示例

这封来自 Jon Masamitsu 的电子邮件这张 JEP 票

实习生String和班级统计数据以及一些杂项数据已移至堆

当将更多类加载到 Metaspace 时,究竟是什么导致了堆的增加?

4

2 回答 2

10

运行jmap -histo PID以查看哪些对象消耗了堆空间。
当我运行您的示例时,我看到堆满了 Javassist 辅助对象:

 num     #instances         #bytes  class name
----------------------------------------------
   1:        592309      312739152  [Ljavassist.bytecode.ConstInfo;
   2:       6515673      208501536  java.util.HashMap$Node
   3:       2964403      169188824  [C
   4:       1777622      102165184  [Ljava.lang.Object;
   5:       4146200       99508800  javassist.bytecode.Utf8Info
   6:       3553889       85293336  java.util.ArrayList
   7:       2964371       71144904  java.lang.String
   8:        593075       56944008  java.lang.Class
   9:        592332       47388032  [Ljava.util.HashMap$Node;
  10:        592309       37907776  javassist.bytecode.ClassFile
  11:        592308       37907712  javassist.CtNewClass
  12:       1185118       28555808  [B
  13:        592342       28432416  java.util.HashMap
  14:       1184624       28430976  javassist.bytecode.ClassInfo
  15:        592309       28430832  [[Ljavassist.bytecode.ConstInfo;
  16:        592322       23692880  javassist.bytecode.MethodInfo
  17:        592315       23692600  javassist.bytecode.CodeAttribute
  18:        592434       18957888  java.util.Hashtable$Entry
  19:        592309       18953888  javassist.bytecode.ConstPool
  20:        592308       18953856  java.lang.ref.WeakReference
  21:        592318       14215632  javassist.bytecode.MethodrefInfo
  22:        592318       14215632  javassist.bytecode.NameAndTypeInfo
  23:        592315       14215560  javassist.bytecode.ExceptionTable
  24:        592309       14215416  javassist.bytecode.LongVector
  25:        592309       14215416  javassist.bytecode.SourceFileAttribute
  26:        592507        9487584  [I
  27:             8        6292528  [Ljava.util.Hashtable$Entry;
  28:           212          18656  java.lang.reflect.Method
  29:           407          13024  java.util.concurrent.ConcurrentHashMap$Node
  30:           124           8928  java.lang.reflect.Field
于 2014-10-19T22:51:15.037 回答
3

当将更多类加载到 Metaspace 时,究竟是什么导致了堆的增加?

我的假设是,这是由您的示例创建的“普通”垃圾。我推测:

  • javaassist代码创建常规堆对象。它们大多是“大”的,这导致它们被直接分配到 OldGen 堆中。或者其他原因。

    更新- 查看@apangin 的答案,我现在怀疑他们是在 YoungGen 堆中开始的并且是终身的......)

  • classLoader.defineClass在后台调用时,它会从包含类文件的字节数组中创建元空间中的对象。

  • OldGen 的用法仍然存在……因为还没有触发完整的 GC。

如果您调整示例以使类可以访问,然后强制执行完整的 GC,我希望(希望)看到 OldHeap 使用量下降,这表明它是“普通”垃圾而不是存储泄漏。

于 2014-10-19T22:50:26.257 回答