国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > Java > 正文

詳解Java內存泄露的示例代碼

2024-07-13 10:14:20
字體:
來源:轉載
供稿:網友

在定位JVM性能問題時可能會遇到內存泄露導致JVM OutOfMemory的情況,在使用Tomcat容器時如果設置了reloadable=”true”這個參數,在頻繁熱部署應用時也有可能會遇到內存溢出的情況。Tomcat的熱部署原理是檢測到WEB-INF/classes或者WEB-INF/lib目錄下的文件發生了變更后會把應用先停止然后再啟動,由于Tomcat默認給每個應用分配一個WebAppClassLoader,熱替換的原理就是創建一個新的ClassLoader來加載類,由于JVM中一個類的唯一性由它的class文件和它的類加載器來決定,因此重新加載類可以達到熱替換的目的。當熱部署的次數比較多會導致JVM加載的類比較多,如果之前的類由于某種原因(比如內存泄露)沒有及時卸載就可能導致永久代或者MetaSpace的OutOfMemory。這篇文章通過一個Demo來簡要介紹下ThreadLocal和ClassLoader導致內存泄露最終OutOfMemory的場景。

類的卸載

在類使用完之后,滿足下面的情形,會被卸載:

1.該類在堆中的所有實例都已被回收,即在堆中不存在該類的實例對象。

2.加載該類的classLoader已經被回收。

3.該類對應的Class對象沒有任何地方可以被引用,通過反射訪問不到該Class對象。

如果類滿足卸載條件,JVM就在GC的時候,對類進行卸載,即在方法區清除類的信息。

場景介紹

上一篇文章我介紹了ThreadLocal的原理,每個線程有個ThreadLocalMap,如果線程的生命周期比較長可能會導致ThreadLocalMap里的Entry沒法被回收,那ThreadLocal的那個對象就一直被線程持有強引用,由于實例對象會持有Class對象的引用,Class對象又會持有加載它的ClassLoader的引用,這樣就會導致Class無法被卸載了,當加載的類足夠多時就可能出現永久代或者MetaSpace的內存溢出,如果該類有大對象,比如有比較大的字節數組,會導致Java堆區的內存溢出。

源碼介紹

這里定義了一個內部類Inner,Inner類有個靜態的ThreadLocal對象,主要用于讓線程持有Inner類的強引用導致Inner類無法被回收,定義了一個自定義的類加載器去加載Inner類,如下所示:

public class MemoryLeak {  public static void main(String[] args) { //由于線程一直在運行,因此ThreadLocalMap里的Inner對象一直被Thread對象強引用    new Thread(new Runnable() {      @Override      public void run() {        while (true) {   //每次都新建一個ClassLoader實例去加載Inner類          CustomClassLoader classLoader = new CustomClassLoader              ("load1", MemoryLeak.class.getClassLoader(), "com.ezlippi.MemoryLeak$Inner", "com.ezlippi.MemoryLeak$Inner$1");          try {            Class<?> innerClass = classLoader.loadClass("com.ezlippi.MemoryLeak$Inner");            innerClass.newInstance();   //幫助GC進行引用處理            innerClass = null;            classLoader = null;            Thread.sleep(10);          } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InterruptedException e) {            e.printStackTrace();          }        }      }    }).start();  } //為了更快達到堆區  public static class Inner {    private byte[] MB = new byte[1024 * 1024];    static ThreadLocal<Inner> threadLocal = new ThreadLocal<Inner>() {      @Override      protected Inner initialValue() {        return new Inner();      }    }; //調用ThreadLocal.get()才會調用initialValue()初始化一個Inner對象    static {      threadLocal.get();    }    public Inner() {    }  } //源碼省略  private static class CustomClassLoader extends ClassLoader {}

堆區內存溢出

為了觸發堆區內存溢出,我在Inner類里面設置了一個1MB的字節數組,同時要在靜態塊中調用threadLocal.get(),只有調用才會觸發initialValue()來初始化一個Inner對象,不然只是創建了一個空的ThreadLocal對象,ThreadLocalMap里并沒有數據。

JVM參數如下:

-Xms100m -Xmx100m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintClassHistogram -XX:+HeapDumpOnOutOfMemoryError

最后執行了814次后JVM堆區內存溢出了,如下所示:

java.lang.OutOfMemoryError: Java heap spaceDumping heap to java_pid11824.hprof ...Heap dump file created [100661202 bytes in 1.501 secs]Heap par new generation  total 30720K, used 30389K [0x00000000f9c00000, 0x00000000fbd50000, 0x00000000fbd50000) eden space 27328K, 99% used [0x00000000f9c00000, 0x00000000fb6ad450, 0x00000000fb6b0000) from space 3392K, 90% used [0x00000000fb6b0000, 0x00000000fb9b0030, 0x00000000fba00000) to  space 3392K,  0% used [0x00000000fba00000, 0x00000000fba00000, 0x00000000fbd50000) concurrent mark-sweep generation total 68288K, used 67600K [0x00000000fbd50000, 0x0000000100000000, 0x0000000100000000) Metaspace    used 3770K, capacity 5134K, committed 5248K, reserved 1056768K class space  used 474K, capacity 578K, committed 640K, reserved 1048576KException in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space at com.ezlippi.MemoryLeak$Inner.<clinit>(MemoryLeak.java:34) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) at java.lang.reflect.Constructor.newInstance(Unknown Source) at java.lang.Class.newInstance(Unknown Source) at com.ezlippi.MemoryLeak$1.run(MemoryLeak.java:20) at java.lang.Thread.run(Unknown Source)

可以看到JVM已經沒有內存來創建新的Inner對象,因為堆區存放了很多個1MB的字節數組,這里我把類的直方圖打印出來了(下圖是堆大小為1024M的場景),省略了一些無關緊要的類,可以看出字節數組占了855M的空間,創建了814個 com.ezlippi.MemoryLeak$CustomClassLoader 的實例,和字節數組的大小基本吻合:

 num   #instances     #bytes class name----------------------------------------------  1:     6203   855158648 [B  2:     13527    1487984 [C  3:      298     700560 [I  4:     2247     228792 java.lang.Class  5:     8232     197568 java.lang.String  6:     3095     150024 [Ljava.lang.Object;  7:     1649     134480 [Ljava.util.HashMap$Node; 11:      813     65040 com.ezlippi.MemoryLeak$CustomClassLoader 12:      820     53088 [Ljava.util.Hashtable$Entry; 15:      817     39216 java.util.Hashtable 16:      915     36600 java.lang.ref.SoftReference 17:      543     34752 java.net.URL 18:      697     33456 java.nio.HeapCharBuffer 19:      817     32680 java.security.ProtectionDomain 20:      785     31400 java.util.TreeMap$Entry 21:      928     29696 java.util.Hashtable$Entry 22:     1802     28832 java.util.HashSet 23:      817     26144 java.security.CodeSource 24:      814     26048 java.lang.ThreadLocal$ThreadLocalMap$Entry

Metaspace溢出

為了讓Metaspace溢出,那就必須把MetaSpace的空間調小一點,要在堆溢出之前加載足夠多的類,因此我調整了下JVM參數,并且把字節數組的大小調成了1KB,如下所示:

private byte[] KB = new byte[1024];-Xms100m -Xmx100m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintClassHistogram -XX:MetaspaceSize=2m -XX:MaxMetaspaceSize=2m

從 GC日志可以看出在Meraspace達到GC閾值(也就是MaxMetaspaceSize配置的大小時)會觸發一次FullGC:

java.lang.OutOfMemoryError: Metaspace <<no stack trace available>>{Heap before GC invocations=20 (full 20): par new generation  total 30720K, used 0K [0x00000000f9c00000, 0x00000000fbd50000, 0x00000000fbd50000) eden space 27328K,  0% used [0x00000000f9c00000, 0x00000000f9c00000, 0x00000000fb6b0000) from space 3392K,  0% used [0x00000000fb6b0000, 0x00000000fb6b0000, 0x00000000fba00000) to  space 3392K,  0% used [0x00000000fba00000, 0x00000000fba00000, 0x00000000fbd50000) concurrent mark-sweep generation total 68288K, used 432K [0x00000000fbd50000, 0x0000000100000000, 0x0000000100000000) Metaspace    used 1806K, capacity 1988K, committed 2048K, reserved 1056768K class space  used 202K, capacity 384K, committed 384K, reserved 1048576K[Full GC (Metadata GC Threshold) [CMSProcess finished with exit code 1

通過上面例子可以看出如果類加載器和ThreadLocal使用的不當確實會導致內存泄露的問題,完整的源碼在github


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 健康| 江源县| 湖北省| 双鸭山市| 徐州市| 家居| 九江市| 新河县| 昌乐县| 富平县| 秭归县| 绥宁县| 浪卡子县| 商水县| 肇源县| 广灵县| 微博| 始兴县| 宿州市| 峨眉山市| 鸡西市| 溧水县| 兰考县| 天镇县| 嘉兴市| 黄石市| 永泰县| 桃江县| 永州市| 滨海县| 溧阳市| 武宣县| 乳山市| 樟树市| 鄂托克旗| 巩留县| 东莞市| 舟曲县| 建宁县| 连云港市| 平阳县|