4年前 (2020-09-30)  Java系列 |   抢沙发  805 
文章评分 0 次,平均分 0.0

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

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

永久代Permgen缺陷

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

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

Java8中的元空间

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

  • 大小 - 元空间的最大大小可以用参数-XX:MaxMetaspaceSize固定,就像永久代一样。但是,如果缺少此参数,则元空间的默认大小仅限于服务器上可用的本机内存。此外,元空间占用的空间将根据运行时的应用程序需求动态重新计算。换句话说,当类加载器将加载越来越多的类时,元空间保留的内存将增加(或在某些类数据被垃圾收集时减少)。
  • 垃圾收集 - 一旦metaspace的限制达到,metaspace上的垃圾收集器就开始工作。这个限制可以通过JVM的参数-XX:MetaspaceSize来指定,这种方式有助于优化metaspace大小。例如,当垃圾回收器在这个空间上的活动太重要时,这可能意味着metaspace指定的大小不足以满足实际情况,或者可能存在一些内存泄漏。
  • no klass - 元空间不再包含KlassKlass及其衍生物。在永久代中,这些对象用于描述描述最初存储的类的类。这意味着只存储原始类的信息。为了了解Klass和KlassKlass是什么,您可以想象下面这个名为瞳孔的类的对象链:
-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的一侧,我们也可以观察到元空间图的增长:

Java8的元空间到底是什么?

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

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

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/504.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册