1.內存結構
要搞懂JVM內存分為哪幾塊,每塊分別的作用是什么。
1)程序計數器:是線程私有的,用于記錄線程執(zhí)行代碼的行號。
2)棧:線程私有的,一個線程執(zhí)行一段代碼就會在棧中為該線程開辟一個棧幀,一般用于存放局部變量表(包括基本數據類型及引用變量),操作數棧,方法出口的信息。-Xss
3)本地方法棧:線程私有的,當執(zhí)行本地native方法時開辟的空。
4)堆:是線程共享的,用于存放所有的對象實例。-Xms -Xmx
5)方法區(qū):線程共享的,存放類信息,靜態(tài)變量,常量。 -XX:PermSize ?-XX:MaxPerSize
6)運行時常量池:屬于方法區(qū)的一部分。class文件中有一個常量池,存放的是編譯器生成的各個字面量和符號引用,這部分信息在類加載后放在運行時常量池中。
7)直接內存Direct Memory:不屬于運行時數據區(qū),也不屬于JVM規(guī)范。與NIO相關,NIO可以直接調用Native方法開辟一塊堆外內存,用通過一個存儲在堆中的DirectByteBuffer對象作為這塊內存的引用來進行操作。(可使java程序直接與堆外內存進行通信,而避免了大量的java堆和native堆的來回復制數據)
需要注意的是,在設置堆大小時,往往會忘了他,導致OOM
2.OutOfMemeryError異常的發(fā)生
除了程序計數器中,其他存儲區(qū)域都有可能發(fā)生OOM異常。直接趁熱打鐵,了解一下OOM發(fā)生的原因。
1)java堆溢出
堆中存放的是對象,不可用的對象會由GC被回收掉,若扔有大量的對象導致空間不足,則會發(fā)生OOM。
先要排查原因,使用內存分析工具Eclipse Memory Analyzer 將堆轉存快照dump出來,并進行分析,主要是分析對堆的對象是否有用。
若對象大部分都無用,則認為是因為內存泄漏,于是我們要使用——來查看GCROOT的引用鏈是如何引用的,并定位原因。
若對象確實有用,則認為是內存溢出,則可通過設置-Xmx設置堆最大內存。
2)虛擬機棧和本地方法棧溢出
棧中有可能發(fā)生兩種異常。a.stackoverflow,當線程請求的棧深度超出了最大深度時。b.oom,當棧需要擴展時發(fā)現空間不夠時。
可通過-Xss嘗試減少棧容量。需要注意的是,-Xss??臻g設置的越大,(被線程瓜分了,每個線程占有資源都很大,數量少)允許的線程數就越少。
3)方法區(qū)溢出
方法區(qū)中主要是類信息,靜態(tài)變量等,當類過多,靜態(tài)變量過多,就會發(fā)生OOM。
越來越多的動態(tài)代理技術也會產生大量代理類,占用大量空間。
4)常量池溢出
當常量池常量過多時,就會發(fā)生OOM。可通過String.intern方法產生大量常量并測試。String.intern會先去常量池中找,找到即返回,找不到在常量池中創(chuàng)建一個再返回。
-XX:PermSize -XX:MaxPermSize用這個設置方法區(qū)大小,也可以間接設置常量池大小。
5)直接內存溢出
-XX:MaxDirectMemorySize調整,用native方法比如unsafe方法申請堆外內存時,申請過多會OOM.
直接內存OOM特點:在堆轉存快照中看不到明顯異常,并且文件很小,程序中又使用了NIO,則可以考慮是直接內存的OOM。