Java內(nèi)存溢出的幾個(gè)區(qū)域總結(jié)(注意避坑!)
前言
在開(kāi)發(fā)過(guò)程中,時(shí)常會(huì)遇到內(nèi)存溢出的問(wèn)題,有可能是在生產(chǎn)環(huán)境,有的就在開(kāi)發(fā)中,今天就聊一聊內(nèi)存溢出。
存在內(nèi)存的區(qū)域:
- Java堆溢出
- 虛擬機(jī)棧和本地方法棧溢出
- 方法區(qū)和運(yùn)行時(shí)常量池溢出
- 本機(jī)內(nèi)存溢出
1、Java堆溢出
Java堆用于儲(chǔ)存對(duì)象實(shí)例,我們只要不斷地創(chuàng)建對(duì)象,并且保證GC Roots到對(duì)象之間有可達(dá)路徑來(lái)避免垃圾回收機(jī)制清除這些對(duì)象,那么隨著對(duì)象數(shù)量的增加,總?cè)萘坑|及最大堆的容量限制后就會(huì)產(chǎn)生內(nèi)存溢出異常。
1、案例創(chuàng)建
需要手動(dòng)調(diào)節(jié)JVM參數(shù),不然需要等很長(zhǎng)時(shí)間:-Xms20m -Xmx20m
public class JavaHeapDemo { static class OOMObject { } public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); //利用while循環(huán)不斷創(chuàng)建對(duì)象 while (true) { list.add(new OOMObject()); } } }
2、處理方法
常規(guī)的處理方法是首先通過(guò)內(nèi)存映像分析工具(如Eclipse Memory Analyzer)對(duì)Dump出來(lái)的堆轉(zhuǎn)儲(chǔ)快照進(jìn)行分析
- 分清楚到底是出現(xiàn)了內(nèi)存泄漏(Memory Leak)還是內(nèi)存溢出(Memory Overflow)
- 內(nèi)存泄漏:通過(guò)工具查看泄漏對(duì)象到GC Roots的引用鏈,找到泄漏對(duì)象是通過(guò)怎樣的引用路徑、與哪些GC Roots相關(guān)聯(lián),才導(dǎo)致垃圾收集器無(wú)法回收它們,根據(jù)泄漏對(duì)象的類(lèi)型信息以及它到GC Roots引用鏈的信息,一般可以比較準(zhǔn)確地定位到這些對(duì)象創(chuàng)建的位置,進(jìn)而找出產(chǎn)生內(nèi)存泄漏的代碼的具體位置
- 內(nèi)存溢出:檢查Java虛擬機(jī)的堆參數(shù)(-Xmx與-Xms)設(shè)置,與機(jī)器的內(nèi)存對(duì)比,看看是否還有向上調(diào)整的空間。再?gòu)拇a上檢查 是否存在某些對(duì)象生命周期過(guò)長(zhǎng)、持有狀態(tài)時(shí)間過(guò)長(zhǎng)、存儲(chǔ)結(jié)構(gòu)設(shè)計(jì)不合理等情況,盡量減少程序運(yùn)行期的內(nèi)存消耗
2、虛擬機(jī)棧和本地方法棧溢出
關(guān)于虛擬機(jī)棧和本地方法棧,在《Java虛擬機(jī)規(guī)范》中描述了兩種異常:
- 如果線(xiàn)程請(qǐng)求的棧深度大于虛擬機(jī)所允許的最大深度,將拋出StackOverflowError異常。
- 如果虛擬機(jī)的棧內(nèi)存允許動(dòng)態(tài)擴(kuò)展,當(dāng)擴(kuò)展棧容量無(wú)法申請(qǐng)到足夠的內(nèi)存時(shí),將拋出OutOfMemoryError異常
《Java虛擬機(jī)規(guī)范》明確允許Java虛擬機(jī)實(shí)現(xiàn)自行選擇是否支持棧的動(dòng)態(tài)擴(kuò)展,而HotSpot虛擬機(jī)的選擇是不支持?jǐn)U展,所以除非在創(chuàng)建
線(xiàn)程申請(qǐng)內(nèi)存時(shí)就因無(wú)法獲得足夠內(nèi)存而出現(xiàn)OutOfMemoryError異常,否則在線(xiàn)程運(yùn)行時(shí)是不會(huì)因?yàn)閿U(kuò)展而導(dǎo)致內(nèi)存溢出的,只會(huì)因?yàn)?/p>
棧容量無(wú)法容納新的棧幀而導(dǎo)致StackOverflowError異常
1、使用-Xss參數(shù)減少棧內(nèi)存容量
public class JavaVMStackSOF { private int stackLength = 1; public void stackLength() { stackLength++; //無(wú)限遞歸 stackLength(); } public static void main(String[] args) { JavaVMStackSOF sof = new JavaVMStackSOF(); try { sof.stackLength(); } catch (Throwable e) { System.out.println("stack length:" + sof.stackLength); throw e; } } }
這里可以通過(guò)指定參數(shù)-Xss128k,用來(lái)測(cè)試棧溢出的情況
3、方法區(qū)和運(yùn)行時(shí)常量池溢出
HotSpot從JDK 7開(kāi)始逐步“去永久代”的計(jì)劃,并在JDK 8中完全使用元空間來(lái)代替永久代的背景故事,使用“永久代”還是“元空間”來(lái)
實(shí)現(xiàn)方法區(qū),對(duì)程序有什么實(shí)際的影響。
String::intern()是一個(gè)本地方法,它的作用是如果字符串常量池中已經(jīng)包含一個(gè)等于此String對(duì)象的字符串,則返回代表池中這個(gè)字符串的
String對(duì)象的引用;否則,會(huì)將此String對(duì)象包含的字符串添加到常量池中,并且返回此String對(duì)象的引用。
這里測(cè)試需要JDK6:-XX:PermSize=6M -XX:MaxPermSize=6M
public class RuntimeConstantPoolOOM { public static void main(String[] args) { // 使用Set保持著常量池引用,避免Full GC回收常量池行為 Set<String> set = new HashSet<String>(); // 在short范圍內(nèi)足以讓6MB的PermSize產(chǎn)生OOM了 short i = 0; while (true) { set.add(String.valueOf(i++).intern()); } } }
JDK8模擬測(cè)試
package jdk8; import java.io.File; import java.lang.management.ClassLoadingMXBean; import java.lang.management.ManagementFactory; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; /** * * @ClassName:OOMTest * @Description:模擬類(lèi)加載溢出(元空間oom) * 為了快速溢出,設(shè)置參數(shù):-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m * @author diandian.zhang */ public class OOMTest { public static void main(String[] args) { try { //準(zhǔn)備url URL url = new File("D:/58workplace/11study/src/main/java/jdk8").toURI().toURL(); URL[] urls = {url}; //獲取有關(guān)類(lèi)型加載的JMX接口 ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean(); //用于緩存類(lèi)加載器 List<ClassLoader> classLoaders = new ArrayList<ClassLoader>(); while (true) { //加載類(lèi)型并緩存類(lèi)加載器實(shí)例 ClassLoader classLoader = new URLClassLoader(urls); classLoaders.add(classLoader); classLoader.loadClass("ClassA"); //顯示數(shù)量信息(共加載過(guò)的類(lèi)型數(shù)目,當(dāng)前還有效的類(lèi)型數(shù)目,已經(jīng)被卸載的類(lèi)型數(shù)目) System.out.println("total: " + loadingBean.getTotalLoadedClassCount()); System.out.println("active: " + loadingBean.getLoadedClassCount()); System.out.println("unloaded: " + loadingBean.getUnloadedClassCount()); } } catch (Exception e) { e.printStackTrace(); } } }
方法區(qū)溢出也是一種常見(jiàn)的內(nèi)存溢出異常,一個(gè)類(lèi)如果要被垃圾收集器回收,要達(dá)成的條件是比較苛刻的。在經(jīng)常運(yùn)行時(shí)生成大量動(dòng)態(tài)類(lèi)的應(yīng)用場(chǎng)景里,就應(yīng)該特別關(guān)注這些類(lèi)的回收狀況。這類(lèi)場(chǎng)景除了之前提到的程序使用了CGLib字節(jié)碼增強(qiáng)和動(dòng)態(tài)語(yǔ)言外,常見(jiàn)的還有:大量JSP或動(dòng)態(tài)產(chǎn)生JSP文件的應(yīng)用(JSP第一次運(yùn)行時(shí)需要編譯為Java類(lèi))、基于OSGi的應(yīng)用(即使是同一個(gè)類(lèi)文件,被不同的加載器加載也會(huì)視為不同的類(lèi))等。
在JDK 8以后,永久代便完全退出了歷史舞臺(tái),元空間作為其替代者登場(chǎng)。在默認(rèn)設(shè)置下,前面列舉的那些正常的動(dòng)態(tài)創(chuàng)建新類(lèi)型的測(cè)試用例已經(jīng)很難再迫使虛擬機(jī)產(chǎn)生方法區(qū)的溢出異常了。不過(guò)為了讓使用者有預(yù)防實(shí)際應(yīng)用里出現(xiàn)類(lèi)似于代碼清單2-9那樣的破壞性的操作,HotSpot還是提供了一些參數(shù)作為元空間的防御措施,主要包括:
- -XX:MaxMetaspaceSize:設(shè)置元空間最大值,默認(rèn)是-1,即不限制,或者說(shuō)只受限于本地內(nèi)存大小。
- -XX:MetaspaceSize:指定元空間的初始空間大小,以字節(jié)為單位,達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類(lèi)型卸載,同時(shí)收集器會(huì)對(duì)該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放了很少的空間,那么在不超過(guò)-XX:MaxMetaspaceSize(如果設(shè)置了的話(huà))的情況下,適當(dāng)提高該值。
- -XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空間剩余容量的百分比,可減少因?yàn)樵臻g不足導(dǎo)致的垃圾收集的頻率。類(lèi)似的還有-XX:Max-MetaspaceFreeRatio,用于控制最大的元空間剩余容量的百分比。
4、本機(jī)直接內(nèi)存溢出
直接內(nèi)存(Direct Memory)的容量大小可通過(guò)-XX:MaxDirectMemorySize參數(shù)來(lái)指定,如果不去指定,則默認(rèn)與Java堆最大值(由-Xmx指定)一致。
JVM參數(shù):-Xmx20M -XX:MaxDirectMemorySize=10M
public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws Exception { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true) { unsafe.allocateMemory(_1MB); } } }
越過(guò)了DirectByteBuffer類(lèi)直接通過(guò)反射獲取Unsafe實(shí)例進(jìn)行內(nèi)存分配(Unsafe類(lèi)的getUnsafe()方法指定只有引導(dǎo)類(lèi)加載器才會(huì)返回實(shí)
例,體現(xiàn)了設(shè)計(jì)者希望只有虛擬機(jī)標(biāo)準(zhǔn)類(lèi)庫(kù)里面的類(lèi)才能使用Unsafe的功能,在JDK 10時(shí)才將Unsafe的部分功能通過(guò)VarHandle開(kāi)放給
外部使用),因?yàn)殡m然使用DirectByteBuffer分配內(nèi)存也會(huì)拋出內(nèi)存溢出異常,但它拋出異常時(shí)并沒(méi)有真正向操作系統(tǒng)申請(qǐng)分配內(nèi)存,而
是通過(guò)計(jì)算得知內(nèi)存無(wú)法分配就會(huì)在代碼里手動(dòng)拋出溢出異常,真正申請(qǐng)分配內(nèi)存的方法是Unsafe::allocateMemory()
總結(jié)
到此這篇關(guān)于Java內(nèi)存溢出的幾個(gè)區(qū)域的文章就介紹到這了,更多相關(guān)Java內(nèi)存溢出區(qū)域內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java基于socket實(shí)現(xiàn)簡(jiǎn)易聊天室實(shí)例
這篇文章主要介紹了Java基于socket實(shí)現(xiàn)簡(jiǎn)易聊天室的方法,實(shí)例分析了java基于socket實(shí)現(xiàn)聊天室服務(wù)端與客戶(hù)端的相關(guān)技巧,需要的朋友可以參考下2015-05-05springboot使用redis緩存亂碼(key或者value亂碼)的解決
在通過(guò)springboot緩存數(shù)據(jù)的時(shí)候,發(fā)現(xiàn)key是一堆很不友好的東西,本文主要介紹了springboot使用redis緩存亂碼(key或者value亂碼)的解決,感興趣的可以了解一下2023-11-11使用Autowired為什么會(huì)被IDEA警告最佳修改方法
這篇文章主要介紹了使用Autowired為什么會(huì)被IDEA警告,應(yīng)該怎么修改最佳,除了使用@Autowired以外,我們其實(shí)也有幾種好用的方式,使用@Resource替代@Autiwired方法是其中一種,只需要改變一個(gè)注解,這里就不展示了,需要的朋友可以參考下2023-02-02解析阿里GTS開(kāi)源版本fescar分布式事務(wù)
這篇文章主要為大家介紹解析阿里GTS開(kāi)源版本fescar分布式事務(wù)的原理及使用說(shuō)明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多進(jìn)步2022-02-02SpringMVC DispatcherServlet組件實(shí)現(xiàn)解析
這篇文章主要介紹了SpringMVC DispatcherServlet組件實(shí)現(xiàn)解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03Spring MVC過(guò)濾器-登錄過(guò)濾的代碼實(shí)現(xiàn)
本篇文章主要介紹了Spring MVC過(guò)濾器-登錄過(guò)濾,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧。2017-01-01SpringBoot實(shí)戰(zhàn):Spring如何找到對(duì)應(yīng)轉(zhuǎn)換器優(yōu)雅使用枚舉參數(shù)
這篇文章主要介紹了SpringBoot實(shí)戰(zhàn)中Spring是如何找到對(duì)應(yīng)轉(zhuǎn)換器優(yōu)雅的使用枚舉參數(shù),文中附有詳細(xì)的實(shí)例代碼有需要的朋友可以參考下,希望可以有所幫助2021-08-08Java對(duì)時(shí)間的簡(jiǎn)單操作實(shí)例
這篇文章主要介紹了Java對(duì)時(shí)間的簡(jiǎn)單操作,實(shí)例分析了針對(duì)java.util.Date的各類(lèi)常見(jiàn)操作,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01mybatis 查詢(xún)sql中in條件用法詳解(foreach)
這篇文章主要介紹了mybatis 查詢(xún)sql中in條件用法詳解(foreach),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02