您好,登錄后才能下訂單哦!
這篇文章主要介紹“如何理解Java中的內(nèi)存溢出問題”,在日常操作中,相信很多人在如何理解Java中的內(nèi)存溢出問題問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何理解Java中的內(nèi)存溢出問題”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
一、內(nèi)存溢出原因
內(nèi)存溢出就是內(nèi)存不夠,引起內(nèi)存溢出的原因有很多種,常見的有以下幾種:
1、內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù);
2、集合類中有對對象的引用,使用完后未清空,使得JVM不能回收;
3、代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復(fù)的對象實(shí)體;
4、使用的第三方軟件中的BUG;
5、啟動(dòng)參數(shù)內(nèi)存值設(shè)定的過小;
當(dāng)然實(shí)際情況中內(nèi)存溢出的原因就太多了。下面我們就對這些原因分類一下:
以上的圖是基于java7來敘述的,從上面這張圖我們能夠得到如下信息:java虛擬機(jī)把內(nèi)存分為5個(gè)模塊。
(1)程序計(jì)數(shù)器:程序計(jì)數(shù)器是線程私有的,主要的作用是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。既然每個(gè)線程都有一個(gè),那么這些線程的計(jì)數(shù)器是互不影響的。也不會(huì)拋出任何異常。
(2)虛擬機(jī)棧和本地方法棧:虛擬機(jī)棧描述的是java方法執(zhí)行的內(nèi)存模型,每個(gè)方法在執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)連接、方法出口等信息。本地方法棧與虛擬機(jī)棧的區(qū)別是,虛擬機(jī)棧為虛擬機(jī)執(zhí)行java方法服務(wù),而本地方法棧則為虛擬機(jī)提供native方法服務(wù)。
在單線程的操作中,無論是由于棧幀太大,還是虛擬機(jī)棧空間太小,當(dāng)棧空間無法分配時(shí),虛擬機(jī)拋出的都是StackOverflowError異常,而不會(huì)得到OutOfMemoryError異常。而在多線程環(huán)境下,則會(huì)拋出OutOfMemoryError異常。
(3)java堆和方法區(qū):java堆區(qū)主要存放對象實(shí)例和數(shù)組等,方法區(qū)保存類信息、常量、靜態(tài)變量等等。運(yùn)行時(shí)常量池也是方法區(qū)的一部分。這兩塊區(qū)域是線程共享的區(qū)域,只會(huì)拋出OutOfMemoryError。
不知道各位在B站看見過那個(gè)面試經(jīng)典場景沒,在回答java的內(nèi)存運(yùn)行數(shù)據(jù)區(qū)結(jié)構(gòu)時(shí),以上的功能作用是一方面,如果回答時(shí)把內(nèi)存溢出問題添加上是一個(gè)極大的加分項(xiàng)。
二、內(nèi)存溢出實(shí)例
1、堆溢出
既然堆是存放實(shí)例對象的,那我們就無線創(chuàng)建實(shí)例對象。這樣堆區(qū)遲早會(huì)滿。
public class HeapOOM { static class User {} public static void main(String[] args) { List<User> list = new ArrayList<User>(); while (true) { list.add(new User()); } } } /*Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded at com.fdd.test.HeapOOM.main(HeapOOM.java:11)*/
因?yàn)槲姨崆霸O(shè)置了堆區(qū)內(nèi)存,所以無限創(chuàng)建就會(huì)拋出異常。
2、虛擬機(jī)棧和本地方法棧溢出
Java虛擬機(jī)規(guī)范中描述了兩種異常:
如果線程請求的棧深度大于虛擬機(jī)鎖允許的最大深度,將拋出StackOverflowError異常。
如果虛擬機(jī)在擴(kuò)展棧時(shí)無法申請到足夠的內(nèi)存空間,則拋出OutOfMemoryError異常。
第一種我們只需要使用方法遞歸調(diào)用即可模擬:
public class StackOutOfMemoryError { public static void main(String[] args) { test(); } private static void go() { System.out.println("StackOverflowError異常"); test(); } } /*Exception in thread "main" java.lang.StackOverflowError at sun.nio.cs.ext.DoubleByte$Encoder.encodeLoop(DoubleByte.java:617) at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579) at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271) at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125) at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207) at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129) at java.io.PrintStream.write(PrintStream.java:526) at java.io.PrintStream.print(PrintStream.java:597) at java.io.PrintStream.println(PrintStream.java:736) at com.fdd.test.StackOutOfMemoryError.go(StackOutOfMemoryError.java:11) at com.fdd.test.StackOutOfMemoryError.go(StackOutOfMemoryError.java:13)*/
第二種也可以遞歸調(diào)用模擬,,但是使用的是類直接調(diào)用。
public class JavaVMStackSOF { private int stackLength = 1; public void stackLeak() { stackLength++; stackLeak(); } public static void main(String[] args) { JavaVMStackSOF oom = new JavaVMStackSOF(); oom.stackLeak(); } } /*Exception in thread "main" java.lang.StackOverflowError at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:18) at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19) at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19) at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19) at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19) at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19) at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19) at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19) at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19) ... */
3、方法區(qū)和運(yùn)行時(shí)常量池溢出
public class JavaMethodAreaOOM { public static void main(String[] args) { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(User.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, args); } }); enhancer.create(); } } static class User {} } /*Exception in thread "main" Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main" */
4、本機(jī)直接內(nèi)存溢出
DirectMemory容量可通過-XX: MaxDirectMemorySize指定,如果不指定,則默認(rèn)與Java堆最大值 (-Xmx指定)一樣。
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); } } }
上面介紹了幾個(gè)實(shí)例,那遇到這種問題如何排查呢?
三、內(nèi)存溢出排查
排查其實(shí)最主要的就是檢查代碼,而且內(nèi)存溢出往往都是代碼的問題。當(dāng)然一下幾點(diǎn)都是需要注意的:
(1)內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù);
(2)集合類中有對對象的引用,使用完后未清空,使得JVM不能回收;
(3)代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復(fù)的對象實(shí)體;
(4)使用的第三方軟件中的BUG;
(5)啟動(dòng)參數(shù)內(nèi)存值設(shè)定的過小;
最后就是解決了。
第一步,修改JVM啟動(dòng)參數(shù),直接增加內(nèi)存。
第二步,檢查錯(cuò)誤日志
第三步,對代碼進(jìn)行走查和分析,找出可能發(fā)生內(nèi)存溢出的位置。
一般情況下代碼出錯(cuò)的概率會(huì)比較大一些,當(dāng)然了不同的場景不同錯(cuò)誤總是復(fù)雜多樣的。
到此,關(guān)于“如何理解Java中的內(nèi)存溢出問題”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。