permanent gc

来源:互联网 发布:牛顿法解最优化问题 编辑:程序博客网 时间:2024/06/06 04:10

http://www.blogbus.com/fallenlord-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]
那么现在结论很明显了:
GC仅会清理PermGen中的常量池信息,而不会清理类元数据信息

分享到:0

历史上的今天:

Eclipse全键盘编码攻略之一——入门 2011-01-31
Eclipse全键盘编码攻略之引子 2011-01-31
一些关于Hibernate延迟加载的误区 2010-01-31
0 0
原创粉丝点击