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).

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值