Java8中关于Java7的一个重大变化是用元空间替换永久代。

我们将通过提醒一些关于永久代的信息来开始本文。主要,我们将列出它的缺点,以便更好地理解Java8中用元空间替换它的原因。第二部分将描述更多的新空间在内存中。最后一部分将介绍分析元空间中发生的事情的不同方法。

永久代Permgen缺陷

永久代是一个包含JVM所需数据的池,例如类或方法。当JVM想要创建给定类的新实例时,此数据很有用。通常,靠它占用的空间不是那么大。但是,一些特性如热部署,可能会引发错误,因为永久代中没有足够的空间来创建新对象。这个家庭最常见的错误之一是java.lang.OutOfMemoryError:pergen空间。

永久代有一些缺点——即使需要创建新的对象实例。其中最重要的是它的静态特性。在启动时使用-XX:MaxPermSize=xM参数指定永久生成的大小。此外,此值是静态的,不会重新调整为更改运行时情况。由于这些限制,它被Java8中的元空间所取代。

Java8中的元空间

Metaspace负责永久代来处理与对象类关联的数据。因此,没有足够空间添加新的类描述的潜在问题仍然存在。由于对metaspace进行了一些更改,它们只减少了一点点。要继续使用,以下列表应该很有用:

-new stude().getClass()引用Klass对象,属于Class<ampid>类型
-new stude()).getClass().getClass()引用KlassKlass对象,属于Class<Class>类型

我们可以看到两个元空间的修改。但是还有两个参数可以用来优化这个内存区域:-XX:MinMetaspaceFreeRatio-XX:MaxMetaspaceFreeRatio。它们有助于定义何时(更早或更晚)进行收集。在释放类元数据时,我们使用一个称为高水位线的术语。当达到定义的级别时,将发生收集。在这个高水位线值之后,可以提高或降低。最初它等于前面介绍的-XX:MetaspaceSize参数。在JVM分析类元数据的可用分配内存与为同一事物分配的所有空间的比率之后。如果此比率的百分比大于MaxMetaspaceFreeRatio或低于MinMetaspaceFreeRatio,则高水位线值将降低或升高。这样,垃圾回收将或多或少地发生。

分析元空间 metaspace

在介绍了一些关于metaspace的段落之后,是时候使用它了。我们将编写一段代码,在一段时间后,将产生与metaspace上可用空间不足有关的内存错误。同时,我们将通过VisualVM观察元空间的使用是如何演变的。为了产生元空间内存错误,我们使用Javassist库及其特性在运行时创建类。我们从以下元空间值开始我们的测试:初始元空间大小为128M,最大元空间大小为130M。另外,我们使用-XX启用以下垃圾收集器工作:+printgdetails。用于测试metaspace的类是:

public class MemoryLeakTest {

    public static void main(String[] args) throws CannotCompileException, InterruptedException {
        System.out.println("Starting...");
        ClassPool cp = ClassPool.getDefault();
        for (int i = 0; i < 1_500_000; i++) {
            if (i%50000 == 0) {
                System.out.println(">> Sleeping for "+i);
                Thread.sleep(2000);
            }
            String className = "com.waitingforcode.metaspace.MemoryTroubler" + i;
            cp.makeClass(className).toClass();
        }
    }
}

几秒钟后,我们将会看到以下错误消息:

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
    at javassist.ClassPool.toClass(ClassPool.java:1085)
    at javassist.ClassPool.toClass(ClassPool.java:1028)
    at javassist.ClassPool.toClass(ClassPool.java:986)
    at javassist.CtClass.toClass(CtClass.java:1079)
    at com.waitingforcode.metaspace.MemoryLeakTest.main(MemoryLeakTest.java:13)

正如预期的那样,保留给metaspace的130M不足以保存1500000个类的元数据。通过分析打印的GC工作,我们可以观察到元空间大小是如何变化的:

[Full GC (Ergonomics) [PSYoungGen: 5104K->0K(70656K)] [ParOldGen: 71200K->73888K(147456K)] 76304K->73888K(218112K), [Metaspace: 25719K->25719K(1062912K)], 0,3691469 secs] [Times: user=1,09 sys=0,02, real=0,37 secs] 
[GC (Allocation Failure) [PSYoungGen: 65536K->5120K(70656K)] 139424K->114064K(218112K), 0,0521329 secs] [Times: user=0,13 sys=0,02, real=0,05 secs] 
>> Sleeping for 50000
[GC (Allocation Failure) [PSYoungGen: 70656K->39904K(129024K)] 179600K->153488K(276480K), 0,0393078 secs] [Times: user=0,11 sys=0,04, real=0,04 secs] 
[Full GC (Ergonomics) [PSYoungGen: 39904K->4566K(129024K)] [ParOldGen: 113584K->147348K(265728K)] 153488K->151914K(394752K), [Metaspace: 48867K->48867K(1073152K)], 0,5780123 secs] [Times: user=1,92 sys=0,02, real=0,58 secs]
[GC (Allocation Failure) [PSYoungGen: 93654K->48128K(137216K)] 241002K->205396K(402944K), 0,0777957 secs] [Times: user=0,24 sys=0,04, real=0,08 secs] 
[GC (Allocation Failure) [PSYoungGen: 137216K->59872K(152576K)] 294484K->260068K(418304K), 0,0972155 secs] [Times: user=0,28 sys=0,07, real=0,10 secs] 
>> Sleeping for 100000
[GC (Allocation Failure) [PSYoungGen: 152544K->87008K(179712K)] 352740K->316164K(445440K), 0,0767430 secs] [Times: user=0,26 sys=0,02, real=0,08 secs] 
[Full GC (Ergonomics) [PSYoungGen: 87008K->47974K(179712K)] [ParOldGen: 229156K->265245K(425984K)] 316164K->313219K(605696K), [Metaspace: 96691K->96691K(1095680K)], 0,9707989 secs] [Times: user=2,89 sys=0,05, real=0,97 secs] 
[GC (Allocation Failure) [PSYoungGen: 140646K->81504K(219648K)] 405891K->372453K(645632K), 0,9317130 secs] [Times: user=2,04 sys=0,06, real=0,93 secs] 
[GC (Metadata GC Threshold) [PSYoungGen: 176041K->117760K(225280K)] 466990K->432765K(651264K), 0,1372545 secs] [Times: user=0,31 sys=0,01, real=0,14 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 117760K->6130K(225280K)] [ParOldGen: 315005K->425720K(626688K)] 432765K->431850K(851968K), [Metaspace: 131763K->131763K(1110016K)], 2,1981618 secs] [Times: user=6,65 sys=0,09, real=2,20 secs] 
[GC (Last ditch collection) [PSYoungGen: 6130K->0K(268800K)] 431850K->431968K(895488K), 0,0781865 secs] [Times: user=0,30 sys=0,00, real=0,08 secs] 
[Full GC (Last ditch collection) [PSYoungGen: 0K->0K(268800K)] [ParOldGen: 431968K->431793K(682496K)] 431968K->431793K(951296K), [Metaspace: 131763K->131763K(1110016K)], 1,3399263 secs] [Times: user=4,79 sys=0,05, real=1,34 secs] 
[GC (Metadata GC Threshold) [PSYoungGen: 2568K->96K(275968K)] 434362K->431889K(958464K), 0,0066006 secs] [Times: user=0,02 sys=0,00, real=0,01 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 96K->0K(275968K)] [ParOldGen: 431793K->431794K(752128K)] 431889K->431794K(1028096K), [Metaspace: 131763K->131763K(1110016K)], 1,0349512 secs] [Times: user=3,71 sys=0,04, real=1,03 secs] 
[GC (Last ditch collection) [PSYoungGen: 0K->0K(277504K)] 431794K->431794K(1029632K), 0,0071087 secs] [Times: user=0,02 sys=0,00, real=0,01 secs] 
[Full GC (Last ditch collection) 
[PSYoungGen: 0K->0K(277504K)] [ParOldGen: 431794K->431794K(825856K)] 431794K->431794K(1103360K), [Metaspace: 131763K->131763K(1110016K)], 1,3406952 secs] [Times: user=4,78 sys=0,05, real=1,34 secs] 
Heap
 PSYoungGen      total 277504K, used 5506K [0x00000000d6900000, 0x00000000f0d00000, 0x0000000100000000)
  eden space 138240K, 3% used [0x00000000d6900000,0x00000000d6e60960,0x00000000df000000)
  from space 139264K, 0% used [0x00000000e8500000,0x00000000e8500000,0x00000000f0d00000)
  to   space 145920K, 0% used [0x00000000df000000,0x00000000df000000,0x00000000e7e80000)
 ParOldGen       total 825856K, used 431794K [0x0000000083a00000, 0x00000000b6080000, 0x00000000d6900000)
  object space 825856K, 52% used [0x0000000083a00000,0x000000009dfac810,0x00000000b6080000)
 Metaspace       used 131793K, capacity 132962K, committed 133120K, reserved 1110016K
  class space    used 72272K, capacity 72329K, committed 72448K, reserved 1048576K

more information about output meaning can be found in the article about

我们可以看到,元空间随着每个新创建的类而增长。然而,这种增长并没有伴随着垃圾收集,最后我们可以观察到java.lang.OutOfMemoryError:Metaspace error。它还证明了元空间的大小是动态变化的,直到达到MaxMetaspaceSize参数中定义的极限。在VisualVM的一侧,我们也可以观察到元空间图的增长:

metaspace

本文介绍metaspace的一些基本方面,因为java8负责处理与类相关的元数据(字段、方法)。与永久代不同,这个新空间使用本机内存,可以在运行时动态增长。然而,这种动态增长并不能消除由类加载器问题引起的内存泄漏。我们甚至可以假设,由于这些变化,类加载器问题将比固定大小的永久性生成更晚检测到。

这篇文章就是元空间metaspace泄露的典型案例: https://javakk.com/160.html