JVM中Perm區持續上漲問題

公司一位大牛在微博上的一條,打算消化一下,畢竟從此Perm區的上漲仍是有可能遇到的。「Java應用Perm區一直呈上漲趨勢的緣由能夠用一個簡單的辦法排查,就是用btrace去跟蹤下是什麼地方在調用ClassLoader.defineClass,在大多數狀況下這招都是管用的。」java

(1)Perm區存放的啥信息?分佈式

        Perm叫作持久代,存放了類的信息、類的靜態變量、類中final類型的變量、類的方法信息,Perm是全局共享的,在必定條件下會被GC掉,方所要承載的數據超過內存區域後,就會出現OOM異常。能夠經過-XX:PermSize和-XX:MaxPermSize來指定這個區域的最小值和最大值。持久代的回收主要包括兩部分,廢棄常量和無用的類,廢棄的常量比較容易判別,沒有任何String類型的對象引用這個就算能夠廢棄的了,可是無用的類判別比較複雜,(該類全部的實例已經被回收,JVM中沒有任何類的實例;加載該類的ClassLoader被回收;該類對應的java.lang.class沒有地方引用)。可使用-verbose:class以及-XX:TraceClassLoading和-XX:TraceClassUnLoading來查看類的加載和卸載狀況。ide

(2)ClassLoader中的defineClass是幹啥的?spa

        該方法用於將二進制的字節碼轉換爲Class對象,對於自定義加載類很是重要,若是二進制的字節碼不符合JVM的規範,就會報ClassFormatError,若是生成的類名和二進制中的不符,報NoClassDefFoundError異常,若是加載的class是受保護的,則報SecurityException,若是此類已經在ClassLoader已經加載,會報LinkageError。.net

 

例子1:運行時常量溢出代理

向常量池中添加數據,能夠調用String.intern(),這個是native方法,若是常量池中已經存在一個,則返回,不然添加早常量池中。code

剛開始的例子以下:orm

1
2
3
4
5
6
7
8
9
public  class  PermTest {
         public  static  void  main(String[] args) {
             int  i =  0 ;
             while ( true ){
                 String.valueOf(i++).intern();
                 System.out.println(i);
             }
         }
}

發現沒有拋出OOM異常,用JConsole查看,(PermSize和MaxPermSize都是10M)在10M的時候曲線又下去了,原來是FullGC把常量池中沒有的引用GC掉了,因此沒有OOM。對象

增長了一個List,持有這個引用,就不會被GC掉了。內存

1
2
3
4
5
6
7
8
9
public  class  PermTest {
         public  static  void  main(String[] args) {
             List<String> all =  new  ArrayList<String>();
             int  i =  0 ;
             while ( true ){
                 all.add(String.valueOf(i++).intern());
             }
         }
}
1
2
3
Exception in thread  "main"  java.lang.OutOfMemoryError: PermGen space
     at java.lang.String.intern(Native Method)
     at PermTest.main(PermTest.java: 10 )

 

例子2:用cglib產生代理類,不斷的建立類

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import  java.lang.reflect.Method;
import  net.sf.cglib.proxy.Enhancer;
import  net.sf.cglib.proxy.MethodInterceptor;
import  net.sf.cglib.proxy.MethodProxy;
public  class  PermTest {
     public  static  void  main(String[] args) {
         while ( true ){
             Enhancer enhancer =  new  Enhancer();
             enhancer.setSuperclass(PermTestClass. class );
             enhancer.setUseCache( false );
             enhancer.setCallback( new  MethodInterceptor() {
                 @Override
                 public  Object intercept(Object obj, Method method, Object[] args,
                         MethodProxy proxy)  throws  Throwable {
                     return  proxy.invoke(obj, args);
                 }
             });
             enhancer.create();
         }
     }
     static  class  PermTestClass{
     }
}
1
2
3
4
5
Caused by: java.lang.OutOfMemoryError: PermGen space
     at java.lang.ClassLoader.defineClass1(Native Method)
     at java.lang.ClassLoader.defineClassCond(ClassLoader.java: 631 )
     at java.lang.ClassLoader.defineClass(ClassLoader.java: 615 )
     ...  8  more

Btrace的腳本文件以下(查看哪裏調用了defineClass信息):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import  static  com.sun.btrace.BTraceUtils.*;
import  com.sun.btrace.annotations.*;
public  class  BtraceAll {
     @TLS
     private  static  long  beginTime;
     @OnMethod (
             clazz= "java.lang.ClassLoader" ,
             method= "defineClass"
     )
     public  static  void  traceMethodBegin(){
         beginTime = timeMillis();
     }
     @OnMethod (
             clazz= "java.lang.ClassLoader" ,
             method= "defineClass" ,
             location= @Location (Kind.RETURN)
     )
     public  static  void  traceMethdReturn(
             @Return  String result,
             @ProbeClassName  String clazzName,
             @ProbeMethodName  String methodName){
         println( "===========================================================================" );
         println(strcat(strcat(clazzName,  "." ), methodName));
         println(strcat( "Time taken : " , str(timeMillis() - beginTime)));
         println( "java thread method trace:---------------------------------------------------" );
         jstack();
         println( "----------------------------------------------------------------------------" );
         println(strcat( "Reuslt :" ,str(result)));
         println( "============================================================================" );
     }
}

運行後,發現cglib在建立代理類的時候,確實調用了defineClass方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
===========================================================================
java.lang.ClassLoader.defineClass
Time taken :  79
java thread method trace:---------------------------------------------------
java.lang.ClassLoader.defineClass(ClassLoader.java: 615 )
sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 25 )
java.lang.reflect.Method.invoke(Method.java: 597 )
net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java: 384 )
net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java: 219 )
net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java: 377 )
net.sf.cglib.proxy.Enhancer.create(Enhancer.java: 285 )
PermTest.main(PermTest.java: 19 )
----------------------------------------------------------------------------
Reuslt : class  PermTest$PermTestClass$$EnhancerByCGLIB$$1ed16944_8
============================================================================

至此,問題場景以及如何排查,清晰了。。。

參考書籍:

一、分佈式java應用基礎

二、深刻理解JVM

相關文章
相關標籤/搜索