JVM内存管理
JVM將內(nèi)存主要劃分為:方法區(qū)、虛擬機(jī)棧、本地方法棧、堆、程序計數(shù)器。JVM運行時數(shù)據(jù)區(qū).
關(guān)系圖:
程序計數(shù)器
記錄當(dāng)前線程鎖執(zhí)行的字節(jié)碼的行號。
虛擬機(jī)棧
存放方法運行時所需的數(shù)據(jù),成為棧幀。其實它很簡單!它里面存放的是一個函數(shù)的上下文,具體存放的是執(zhí)行的函數(shù)的一些數(shù)據(jù)。執(zhí)行的函數(shù)需要的數(shù)據(jù)無非就是局部變量表(保存函數(shù)內(nèi)部的變量)、操作數(shù)棧(執(zhí)行引擎計算時需要),方法出口等等。
-
棧幀:執(zhí)行引擎每調(diào)用一個方法時,就為這個函數(shù)創(chuàng)建一個棧幀,并加入虛擬機(jī)棧。換個角度理解,每個函數(shù)從調(diào)用到執(zhí)行結(jié)束,其實是對應(yīng)一個棧幀的入棧和出棧。
-
局部變量表: 存放編譯期間可知的各種基本數(shù)據(jù)類型、引用類型、return Address類型
- 局部變量表的內(nèi)存空間在編譯期間就完成分配,運行期間不會改變。
相關(guān)報錯: StackOverflowError:當(dāng)棧幀大于我們設(shè)置的棧大小,就會出現(xiàn)棧溢出(遞歸沒有出口等因素) OutOfMemoryError:
本地方法棧
本地方法棧與虛擬機(jī)棧所發(fā)揮的作用很相似,他們的區(qū)別在于虛擬機(jī)棧為執(zhí)行Java代碼方法服務(wù),而本地方法棧是為Native方法服務(wù)。與虛擬機(jī)棧一樣,本地方法棧也會拋出StackOverflowError和OutOfMemoryError異常。
堆內(nèi)存
存儲對象實例。
Java堆可以說是虛擬機(jī)中最大一塊內(nèi)存了。它是所有線程所共享的內(nèi)存區(qū)域,幾乎所有的實例對象都是在這塊區(qū)域中存放。當(dāng)然,睡著JIT編譯器的發(fā)展,所有對象在堆上分配漸漸變得不那么“絕對”了。
Java堆是垃圾收集器管理的主要區(qū)域。由于現(xiàn)在的收集器基本上采用的都是分代收集算法,所有Java堆可以細(xì)分為:新生代和老年代。在細(xì)致分就是把新生代分為:Eden空間、From Survivor空間、To Survivor空間。當(dāng)堆無法再擴(kuò)展時,會拋出OutOfMemoryError異常。
分配堆內(nèi)存指令參數(shù):-Xms -Xmx
方法區(qū)
存儲運行時常量池,已被虛擬機(jī)加載的類信息,常量,靜態(tài)變量,即時編譯器編譯后的代碼等數(shù)據(jù)。(類版本、字段、方法、接口)。
運行時常量池:占用方法區(qū)中的一塊。
方法區(qū)是各個線程共享區(qū)域,很容易理解,我們在寫Java代碼時,每個線程度可以訪問同一個類的靜態(tài)變量對象。
由于使用反射機(jī)制的原因,虛擬機(jī)很難推測那個類信息不再使用,因此這塊區(qū)域的回收很難。另外,對這塊區(qū)域主要是針對常量池回收,值得注意的是JDK1.7已經(jīng)把常量池轉(zhuǎn)移到堆里面了。同樣,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,會拋出OutOfMemoryError。 制造方法區(qū)內(nèi)存溢出,注意,必須在JDK1.6及之前版本才會導(dǎo)致方法區(qū)溢出,原因后面解釋,執(zhí)行之前,可以把虛擬機(jī)的參數(shù)-XXpermSize和-XX:MaxPermSize限制方法區(qū)大小。
List<String> list =new ArrayList<String>(); int i =0; while(true){list.add(String.valueOf(i).intern()); } 復(fù)制代碼運行后會拋出java.lang.OutOfMemoryError:PermGen space異常。 解釋一下,String的intern()函數(shù)作用是如果當(dāng)前的字符串在常量池中不存在,則放入到常量池中。上面的代碼不斷將字符串添加到常量池,最終肯定會導(dǎo)致內(nèi)存不足,拋出方法區(qū)的OOM。
下面解釋一下,為什么必須將上面的代碼在JDK1.6之前運行。我們前面提到,JDK1.7后,把常量池放入到堆空間中,這導(dǎo)致intern()函數(shù)的功能不同,具體怎么個不同法,且看看下面代碼:
String str1 =new StringBuilder("fant").append("j").toString(); System.out.println(str1.intern()==str1);String str2=new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern()==str2); 復(fù)制代碼這段代碼在JDK1.6和JDK1.7運行的結(jié)果不同。JDK1.6結(jié)果是:false,false ,JDK1.7結(jié)果是true, false。原因是:JDK1.6中,intern()方法會吧首次遇到的字符串實例復(fù)制到常量池中,返回的也是常量池中的字符串的引用,而StringBuilder創(chuàng)建的字符串實例是在堆上面,所以必然不是同一個引用,返回false。
在JDK1.7中,intern不再復(fù)制實例,常量池中只保存首次出現(xiàn)的實例的引用,因此intern()返回的引用和由StringBuilder創(chuàng)建的字符串實例是同一個。為什么對str2比較返回的是false呢?這是因為,JVM中內(nèi)部在加載類的時候,就已經(jīng)有"java"這個字符串,不符合“首次出現(xiàn)”的原則,因此返回false。
更深入的了解常量池和intern:
/*** Created by Fant.J.*/ public class Test {public static void main(String[] args) {String a = "fantj";String b = "fantj";//a和b 會存到常量池里,常量池類似一個set集合,不允許有重復(fù)的值,所以加入第二個重復(fù)的值會返回已存在值的索引System.out.println(a == b);//new操作會實例化一個對象,會把他放到棧中。String c = new String("fantj");//所以a和c比較,a在常量池,b在堆,索引肯定不同,結(jié)果自然不同,返回falseSystem.out.println(a == c);//a和c.intern比較,intern會把c搬到常量池,所以加入第二個重復(fù)的值會返回已存在值的索引,返回trueSystem.out.println(a == c.intern());} } 復(fù)制代碼有注釋,仔細(xì)看注釋。
總結(jié)
- 上一篇: spawn-fcgi启动的一些报错问题
- 下一篇: CNN如何识别一幅图像中的物体