`

Permanent generation

阅读更多

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://fallenlord.blogbus.com/logs/57543373.html

 

众所周知,Java从1.2开始引入分带GC策略,JVM内存被分成了3个带:young generate、tenured generation和permanent generation
前面两个带相信大家已经非常熟悉了,一般我们所说的GC主要是在这两个带里面运作,我们这里主要讨论Permanent Generation

Perm带是存储类元数据信息的地方,一直以来大家都认为是不会被GC的——确实,类元数据信息被回收了别的类怎么玩?

要说明这个问题,主要需要弄清楚Perm带除了元数据信息外还存了些什么?
栈存基本类型和引用、堆存对象,这个简单的道理大家都懂,但真的是所有的基本类型都存在栈里吗?不见得
还是那句话——无码无真相,我们先从最简单的例子来看一个问题,下面这段代码相信大家都看过很多遍了:

@Test
public void literal() {
    String a = "abc";
    String b = "abc";
    Assert.assertTrue(a == b);
}

没啥好说的,字符串字面量在编译期就会被编译器直接植入.class文件常量池中,并在运行期被JVM当做常量加载,所有存储超过一个字节大小的基本类型都会被编译器优化成这样,这点用javap反编译看下汇编如何压栈的就知道了。关键问题是,常量池在运行期是放在堆里的还是放在栈里的?——答案是都不在

JVM Spec中的Runtime Data Area分为5个区域:pc register、java stack、native stack、java heap、method area,前三个和大多数语言类似比较容易理解,java Heap就是我们常说的堆了,也是Young Generation和Tenured Generation所在,而Method Area就是我们所说的Permanent Generation,上面代码中的字面字符串常量就存在了这里(也有人认为PermGen属于广义上的Heap)

不信?OK,看看下面的代码:

@Test
public void permGenOOM() {
    List<String> list = new ArrayList<String>();
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        String t = String.valueOf(i).intern();
        list.add(t);
    }
}

运行前先设置下JVM参数:-XX:PermSize=2M -XX:MaxPermSize=4M,将PermSize调小点,这样比较容易出结果
执行一下,PermGen应该很快就爆了

这里用到了String的intern方法,作用我就不多说了,如果不了解的可以自己看看JDK API,大致就是将一个字符串变量转存到常量区的String Pool中,和直接写字面常量是一样的效果,以下代码可以证明:

@Test
public void literalAndIntern() {
    String a = "abc";
    String b = new String("abc");
    Assert.assertFalse(a == b);
    Assert.assertTrue(a == b.intern());
}

OK,既然知道了Permanent Generation中还存着这个东东,那么我们就可以试验GC了
去掉上一段代码的第3和第6行,也就是整个程序不再持有创建出来的intern对象的引用,使得对象可以被GC,同事在JVM参数中追加GC观察-verbose:gc -XX:+PrintGCDetails

@Test
public void permGenGC() {
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        String t = String.valueOf(i).intern();
    }
}

运行代码,这次是不是没有PermGen OOM了?观察Console中的GC日志,看到很多minor GC,我们主要关注Major GC(Full GC)的内容:
[Full GC [Tenured: 340K->340K(4096K), 0.0197170 secs] 959K->340K(5056K), [Perm : 4096K->799K(4096K)], 0.0199235 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
看到Perm段,显示PermGen总大小为4096K,此次full gc将其从4096K清理到了799K

到这里应该已经大功告成了,这篇帖子的主题也达到了——GC会清理PermGen

但事情还不算完,既然GC会去动PermGen,那是否会清理类元数据信息呢?虽然看起来很荒谬的理论,但是还是值得尝试一下的:

@Test
public void permGenCglibOOM() {
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        createInstance();
    }
}

private static ValueObject createInstance() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(ValueObject.class);
    enhancer.setUseCache(false);  // 关闭CGLib缓存,否则总是生成同一个类
    enhancer.setCallback(new MethodInterceptor() {
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
                throws Throwable {
            return proxy.invokeSuper(obj, args);
        }
    });
    return (ValueObject) enhancer.create();
}

public static class ValueObject {

    private String username = "guolin";

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

这里我们仿造Hibernate用CGLib动态构造了一堆ValueObject的子类,如愿以偿,很快PermGen就OOM了,观察Full GC日志,PermGen一点都没压下去
[Full GC [Tenured: 1950K->1560K(4096K), 0.0323010 secs] 2637K->1560K(5056K), [Perm : 4095K->4095K(4096K)], 0.0323519 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]

打开Yourkit Memory 一栏,看到 loaded classes and unloaded classes. 既然有unloaded classes, which means the classes can be unloaded.

来分析一下原因,我们生成的classes,是否是因为被引用才不会full gc 回收?

确实有Classloader,这里是系统自带的application class loader,他会hold住所有的class Object 引用。我们尝试用自己的classloader试试。

     ClassLoader cl = null;
     for(int i = 0; i < Integer.MAX_VALUE; i++)
     {
      Enhancer enhancer = new Enhancer();
      if(i%100 == 0)
      {
       cl = null;
       cl = new MyClassLoader();
      }
      enhancer.setClassLoader(cl);
      enhancer.setUseCache(false);
      enhancer.setCallbackType(MyMethodInterceptor.class);
      enhancer.createClass();
     }

 

[Full GC [Tenured[Unloading class net.sf.cglib.empty.Object$$EnhancerByCGLIB$$62f811b3_63]

.......
[Unloading class net.sf.cglib.empty.Object$$EnhancerByCGLIB$$62f811b3_38]

: 1025K->866K(2504K), 0.0152307 secs] 1104K->866K(3080K), [Perm : 4095K->2677K(4096K)], 0.0155104 secs]

这次Full GC的时候有unload classes.

 

So let me sum up when a class is qulified to be unloaded.

 

- they will not be unloaded while any instances are still in
existence (have not been GC-ed).

- they will not be unloaded while their owning classloader is still in
instances is still in existence (has not been GC-ed).

- they will not be unloaded while their java.lang.Class object is still
referenced from anywhere (same goes for reflective access to their
members).

分享到:
评论

相关推荐

    WebLogic调优与监控(包含weblogic11g)

    GC的分代包括Young Generation、Tenured Generation和Permanent Generation。Young Generation又可以分为Eden和Survivor Spaces。 六、GC的调优 GC的调优可以手动进行,也可以使用自适应调优功能。在手动调优中,...

    java虚拟机

    - 永久代(Permanent Generation) 当一个对象被创建时,它首先进入新生代,之后有可能被转移到老年代中。新生代存放着大量的生命很短的对象,因此新生代在三个区域中垃圾回收的频率最高。为了更高效地进行垃圾回收...

    Java内存不足PermGen space错误探究.pdf

    这个错误的根本原因在于Java虚拟机(JVM)的永久代(Permanent Generation Space)内存空间不足。永久代主要存储类的元数据,包括类的名称、方法信息、字段信息等。当应用程序加载的类数量过多或者类的元数据占用...

    技术人员也应懂艺术~~:不要把“老年代”叫成“Old Generation”

    在Java的内存模型中,堆内存被分为新生代(Young Generation)、老年代(Tenured Generation)和持久代(Permanent Generation,Java 8之后被元空间Metaspace取代)。新生代主要用于存放新创建的对象,经过几次垃圾...

    Android内存OOM优化详解.pdf

    Young Generation包含Eden和两个Survivor区,新生对象首先在Eden区分配,然后经过几次垃圾回收后,存活对象逐渐转移到Old Generation,而Permanent Generation主要存储静态类和方法。 针对Bitmap的使用,由于Bitmap...

    JAVA内存溢出

    JVM管理的内存大致包括三种不同类型的内存区域:Permanent Generation space(永久保存区域)、Heap space(堆区域)和Java Stacks(Java栈)。其中,永久保存区域主要存放Class(类)和Meta的信息,Class第一次被...

    JVM内存调优.docx

    JVM 的内存模型可以划分为永久代(Permanent Generation)、年轻代(Young Generation)和老年代(Old Generation)三个部分。Heap(堆)= 年轻代(Eden + survivor1+survivor2)+ 老年代。 触发 GC 和 Full GC GC...

    JVM原理及内存溢出案列分析PPT教案学习.pptx

    * 老年代(Old Generation):包括“Tenured Generation”和“Permanent Generation”。 JVM收集方式 * 一种称为 copying 或 scavenge,将所有仍然生存的对象搬到另外一块内存后,整块内存就可回收。 * 另一种称为...

    JAVA垃圾回收简介知识.pdf

    分代收集策略将堆内存划分为年轻代(Young Generation)、年老代(Tenured Generation)和持久代(Permanent Generation)。年轻代又细分为Eden区、两个Survivor区(From和To)。大多数对象首先在Eden区分配,当Eden...

    WebLogic调优与监控(new).ppt

    Heap 分布可以分为三个部分:Young generation、Tenured generation 和 Permanent generation。Young generation 又可以分为 Eden 和 Survivor spaces 两个部分。 7. GC 参数 GC 参数是 GC 调优的重要参数。常见的...

    java.lang.OutOfMemoryError: PermGen space

    这个错误提示表明,应用程序在运行过程中,内存的永久代(Permanent Generation)空间不足,导致了内存溢出。了解这个问题的原因和解决方案对于优化Java应用的性能至关重要。 PermGen空间,全称为Permanent ...

    Java垃圾回收机制总结

    该算法将对象分为三个代:新生代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation)。新生代是用于存储新生的对象,老年代是用于存储生命周期较长的对象,永久代是用于存储类似于Java...

    hllvm.新生代回收调试的一些心得1

    1. **堆(Heap)**:Java对象存储的主要区域,分为新生代、老生代(Tenured Generation)和持久代(Permanent Generation)。 2. **新生代(Young Generation)**:存放生命周期较短的对象,分为伊甸园(Eden Space...

    JVM_GC_-调优总结.pdf

    - **永久代(Permanent Generation)** **2.2 新生代(Young Generation)** - **分区**: - **Eden**: 为新对象分配的空间。 - **Survivor Spaces**: 分为两个部分,一个为空,另一个用于存储从Eden区迁移过来...

    tomcat-jvm调优

    - **最大永久代大小(Maximum Permanent Generation Size)**:通过`-XX:MaxPermSize`参数设置,在JDK 8及以下版本可用。 - 在JDK 9及以后版本中,永久代被元空间(Metaspace)所替代,相关参数也发生了变化: - ...

    JVM垃圾回收与调优详解(1)1

    首先,JVM内存分为新生代(Young Generation)、老年代(Tenured Generation)和持久代(Permanent Generation,Java 8以后变为Metaspace)。新生代又细分为Eden区、Survivor区(S0和S1)。对象创建时,通常会优先在...

    JVM内存模型以及垃圾回收相关资料

    JVM(Java Virtual Machine)内存模型分为多个区域,包括新生代(New Generation)、老年代(Old Generation)和永久代(Permanent Generation)。新生代又细分为Eden区和两个Survivor区(from和to),用于存储生命...

    JVM垃圾回收机制与GC性能调优

    它分为三个主要区域:新域(Young Generation)、旧域(Old Generation)和永久域(Permanent Generation)。新域进一步划分为Eden区和两个辅助生存空间(Survivor Spaces,通常称为From和To空间)。新生成的对象...

    JVM参数设置详细说明

    - `-XX:PermSize` 和 `-XX:MaxPermSize` 分别设置永久代(`Permanent Generation`)的最小值和最大值,用于存储类元数据。在Java 8及以后版本中,这部分被元空间(`Metaspace`)取代。 2. **线程栈大小**: - `-Xss` ...

Global site tag (gtag.js) - Google Analytics