詳解Java的堆內(nèi)存與棧內(nèi)存的存儲機制
堆與內(nèi)存優(yōu)化
今天測了一個項目的數(shù)據(jù)自動整理功能,對數(shù)據(jù)庫中幾萬條記錄及圖片進(jìn)行整理操作,運行接近到最后,爆出了java.lang.outOfMemoryError,java heap space方面的錯誤,以前寫程序很少遇到這種內(nèi)存上的錯誤,因為java有垃圾回收器機制,就一直沒太關(guān)注。今天上網(wǎng)找了點資料,在此基礎(chǔ)上做了個整理。
一、堆和棧
堆—用new建立,垃圾回收器負(fù)責(zé)回收
1、程序開始運行時,JVM從OS獲取一些內(nèi)存,部分是堆內(nèi)存。堆內(nèi)存通常在存儲地址的底層,向上排列。
2、堆是一個"運行時"數(shù)據(jù)區(qū),類實例化的對象就是從堆上去分配空間的;
3、在堆上分配空間是通過"new"等指令建立的,堆是動態(tài)分配的內(nèi)存大小,生存期也不必事先告訴編譯器;
4、與C++不同的是,Java自動管理堆和棧,垃圾回收器可以自動回收不再使用的堆內(nèi)存;
5、缺點是,由于要在運行時動態(tài)分配內(nèi)存,所以內(nèi)存的存取速度較慢。
?!娣呕绢愋秃鸵妙愋停俣瓤?/p>
1、先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),通常用于保存方法中的參數(shù),局部變量;
2、在java中,所有基本類型(short,int, long, byte, float, double,boolean, char)和引用類型的變量都在棧中存儲;
3、棧中數(shù)據(jù)的生存空間一般在當(dāng)前scopes內(nèi)(由{...}括起來的區(qū)域;
4、棧的存取速度比堆要快,僅次于直接位于CPU中的寄存器;
5、棧中的數(shù)據(jù)可以共享,多個引用可以指向同一個地址;
6、缺點是,棧的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性。
二、內(nèi)存設(shè)置
1、查看虛擬機內(nèi)存情況
long maxControl = Runtime.getRuntime().maxMemory();//獲取虛擬機可以控制的最大內(nèi)存數(shù)量 long currentUse = Runtime.getRuntime().totalMemory();//獲取虛擬機當(dāng)前已使用的內(nèi)存數(shù)量
默認(rèn)情況下,java虛擬機的maxControl=66650112B=63.5625M;
什么都不做的情況,在我的機子上測得的currentUse=5177344B=4.9375M;
2、設(shè)置內(nèi)存大小的命令
-Xms<size> set initial Java heap size :設(shè)置JVM初始化堆內(nèi)存大小;此值可以設(shè)置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內(nèi)存。
-Xmx<size> set maximum Java heap size:設(shè)置JVM最大的堆內(nèi)存大小;
-Xmn<size>:設(shè)置年輕代大小,整個堆大小=年輕代大小+ 年老代大小+ 持久代大小。
-Xss<size> set java thread stack size:設(shè)置JVM線程棧內(nèi)存大??;
3、具體操作
(1)JVM內(nèi)存設(shè)置:
打開MyEclipse(Eclipse) window-preferences-Java -Installed JREs -Edit -Default VM Arguments
在VM自變量中輸入:-Xmx128m -Xms64m -Xmn32m -Xss16m
(2)IDE內(nèi)存設(shè)置:
在MyEclipse根目錄下的myeclipse.ini(或Eclipse根目錄下的eclipse.ini)中修改-vmargs 下的配置:
(3)Tomcat內(nèi)存設(shè)置
打開Tomcat根目錄下的bin文件夾,編輯catalina.bat
修改為:set JAVA_OPTS= -Xms256m -Xmx512m
三、Java堆中的OutOfMemoryError錯誤分析
當(dāng)JVM啟動時,使用了-Xms 參數(shù)設(shè)置的堆內(nèi)存。當(dāng)程序繼續(xù)進(jìn)行,創(chuàng)建更多對象,JVM開始擴大堆內(nèi)存以容納更多對象。JVM也會使用垃圾回收器來回收內(nèi)存。當(dāng)快達(dá)到-Xmx設(shè)置的最大堆內(nèi)存時,如果沒有更多的內(nèi)存可被分配給新對象的話,JVM就會拋出java.lang.outofmemoryerror,程序就會宕掉。在拋出 OutOfMemoryError之前,JVM會嘗試著用垃圾回收器來釋放足夠的空間,但是發(fā)現(xiàn)仍舊沒有足夠的空間時,就會拋出這個錯誤。為了解決這個問題,需要清楚程序?qū)ο蟮男畔?,例如,你?chuàng)建了哪些對象,哪些對象占用了多少空間等等。可以使用profiler或者堆分析器來處理OutOfMemoryError錯誤。"java.lang.OutOfMemoryError: Java heap space”表示堆沒有足夠的空間了,不能繼續(xù)擴大了。"java.lang.OutOfMemoryError: PermGen space”表示permanent generation已經(jīng)裝滿了,你的程序不能再裝載類或者再分配一個字符串了。
四、堆和垃圾回收
我們知道對象創(chuàng)建在堆內(nèi)存中,垃圾回收這樣一個進(jìn)程,它將已死對象清除出堆空間,并將這些內(nèi)存再還給堆。為了給垃圾回收器使用,堆主要分成三個區(qū)域,分別叫作New Generation,Old Generation或叫Tenured Generation,以及Perm space。New Generation是用來存放新建的對象的空間,在對象新建的時候被使用。如果長時間還使用的話,它們會被垃圾回收器移動到Old Generation(或叫Tenured Generation)。Perm space是JVM存放Meta數(shù)據(jù)的地方,例如類,方法,字符串池和類級別的詳細(xì)信息。
五、總結(jié):
1、Java堆內(nèi)存是操作系統(tǒng)分配給JVM的內(nèi)存的一部分。
2、當(dāng)我們創(chuàng)建對象時,它們存儲在Java堆內(nèi)存中。
3、為了便于垃圾回收,Java堆空間分成三個區(qū)域,分別叫作New Generation, Old Generation或叫作Tenured Generation,還有Perm Space。
4、你可以通過用JVM的命令行選項 -Xms, -Xmx, -Xmn來調(diào)整Java堆空間的大小。
5、可以用JConsole或者Runtime.maxMemory(),Runtime.totalMemory(),Runtime.freeMemory()來查看Java中堆內(nèi)存的大小。
6、可以使用命令“jmap”來獲得heap dump,用“jhat”來分析heap dump。
7、Java堆空間不同于??臻g,??臻g是用來儲存調(diào)用棧和局部變量的。
8、Java垃圾回收器是用來將死掉的對象(不再使用的對象)所占用的內(nèi)存回收回來,再釋放到Java堆空間中。
9、當(dāng)遇到j(luò)ava.lang.outOfMemoryError時,不必緊張,有時候僅僅增加堆空間就可以了,但如果經(jīng)常出現(xiàn)的話,就要看看Java程序中是不是存在內(nèi)存泄露了。
10、使用Profiler和Heap dump分析工具來查看Java堆空間,可以查看給每個對象分配了多少內(nèi)存。
棧存儲詳解
Java棧存儲具有以下幾個特點:
一、存在棧中的數(shù)據(jù)大小和生命周期必須是確定的。
如基本類型的存儲:int a = 1; 這種變量存的是字面值,a是一個指向int類型的引用,指向3這個字面值。這些字面值的數(shù)據(jù),由于大小可知,生存期可知(這些字面值固定定義在某個程序塊里面,程序塊退出后,字面值就消失了),出于追求速度的原因,就存在于棧中。
二、存在棧中的數(shù)據(jù)可以共享。
(1)、基本類型數(shù)據(jù)存儲:
如:
int a = 3; int b = 3;
編譯器先處理int a = 3;首先它會在棧中創(chuàng)建一個變量為a的引用,然后查找有沒有字面值為3的地址,沒找到,就開辟一個存放3這個字面值的地址,然后將a指向3的地址。接著處理int b = 3;在創(chuàng)建完b的引用變量后,由于在棧中已經(jīng)有3這個字面值,便將b直接指向3的地址。這樣,就出現(xiàn)了a與b同時均指向3的情況。
注意:這種字面值的引用與類對象的引用不同。假定兩個類對象的引用同時指向一個對象,如果一個對象引用變量修改了這個對象的內(nèi)部狀態(tài),那么另一個對象引用變量也即刻反映出這個變化。相反,通過字面值的引用來修改其值,不會導(dǎo)致另一個指向此字面值的引用的值也跟著改變的情況。如上例,我們定義完a 與b的值后,再令a=4;那么,b不會等于4,還是等于3。在編譯器內(nèi)部,遇到a=4;時,它就會重新搜索棧中是否有4的字面值,如果沒有,重新開辟地址存放4的值;如果已經(jīng)有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
(2)、包裝類數(shù)據(jù)存儲:
如Integer, Double, String等將相應(yīng)的基本數(shù)據(jù)類型包裝起來的類。這些類數(shù)據(jù)全部存在于堆中,Java用new()語句來顯示地告訴編譯器,在運行時才根據(jù)需要動態(tài)創(chuàng)建,因此比較靈活,但缺點是要占用更多的時間。
如:以String為例。
String是一個特殊的包裝類數(shù)據(jù)。即可以用String str = new String("abc");的形式來創(chuàng)建,也可以用String str = "abc";的形式來創(chuàng)建。前者是規(guī)范的類的創(chuàng)建過程,即在Java中,一切都是對象,而對象是類的實例,全部通過new()的形式來創(chuàng)建。Java 中的有些類,如DateFormat類,可以通過該類的getInstance()方法來返回一個新創(chuàng)建的類,似乎違反了此原則。其實不然。該類運用了單例模式來返回類的實例,只不過這個實例是在該類內(nèi)部通過new()來創(chuàng)建的,而getInstance()向外部隱藏了此細(xì)節(jié)。
那為什么在String str = "abc";中,并沒有通過new()來創(chuàng)建實例,是不是違反了上述原則?其實沒有。
關(guān)于String str = "abc"的內(nèi)部工作。Java內(nèi)部將此語句轉(zhuǎn)化為以下幾個步驟:
a、先定義一個名為str的對String類的對象引用變量:String str;
b、在棧中查找有沒有存放值為"abc"的地址,如果沒有,則開辟一個存放字面值為"abc"的地址,接著創(chuàng)建一個新的String類的對象O,并將O的字符串值指向這個地址,而且在棧中這個地址旁邊記下這個引用的對象O。如果已經(jīng)有了值為"abc"的地址,則查找對象O,并返回O的地址。
c、將str指向?qū)ο驩的地址。
值得注意的是,通常String類中字符串值都是直接存值的。但像String str = "abc";這種場合下,其字符串值卻是保存了一個指向存在棧中數(shù)據(jù)的引用(即:String str = "abc";既有棧存儲,又有堆存儲)。
為了更好地說明這個問題,我們可以通過以下的幾個代碼進(jìn)行驗證。
String str1 = "abc"; String str2 = "abc"; System.out.println(str1==str2); //true
(只有在兩個引用都指向了同一個對象時才返回真值。str1與str2是否都指向了同一個對象)
結(jié)果說明,JVM創(chuàng)建了兩個引用str1和str2,但只創(chuàng)建了一個對象,而且兩個引用都指向了這個對象。
String str1 = "abc"; String str2 = "abc"; str1 = "bcd"; System.out.println(str1 + "," + str2); //bcd, abc System.out.println(str1==str2); //false
這就是說,賦值的變化導(dǎo)致了類對象引用的變化,str1指向了另外一個新對象,而str2仍舊指向原來的對象。上例中,當(dāng)我們將str1的值改為"bcd"時,JVM發(fā)現(xiàn)在棧中沒有存放該值的地址,便開辟了這個地址,并創(chuàng)建了一個新的對象,其字符串的值指向這個地址。
事實上,String類被設(shè)計成為不可改變(immutable)的類。如果你要改變其值,可以,但JVM在運行時根據(jù)新值悄悄創(chuàng)建了一個新對象(沒法在原來內(nèi)存的基礎(chǔ)上改變其值),然后將這個對象的地址返回給原來類的引用。這個創(chuàng)建過程雖說是完全自動進(jìn)行的,但它畢竟占用了更多的時間。在對時間要求比較敏感的環(huán)境中,會帶有一定的不良影響。
String str1 = "abc"; String str2 = "abc"; str1 = "bcd"; String str3 = str1; System.out.println(str3); //bcd String str4 = "bcd"; System.out.println(str1 == str4); //true
str3這個對象的引用直接指向str1所指向的對象(注意,str3并沒有創(chuàng)建新對象)。當(dāng)str1改完其值后,再創(chuàng)建一個String的引用str4,并指向因str1修改值而創(chuàng)建的新的對象。可以發(fā)現(xiàn),這回str4也沒有創(chuàng)建新的對象,從而再次實現(xiàn)棧中數(shù)據(jù)的共享。
String str1 = new String("abc"); String str2 = "abc"; System.out.println(str1==str2); //false
創(chuàng)建了兩個引用。創(chuàng)建了兩個對象。兩個引用分別指向不同的兩個對象。
String str1 = "abc"; String str2 = new String("abc"); System.out.println(str1==str2); //false
創(chuàng)建了兩個引用。創(chuàng)建了兩個對象。兩個引用分別指向不同的兩個對象。
以上兩段代碼說明,只要是用new()來新建對象的,都會在堆中創(chuàng)建,而且其字符串是單獨存值的,即使與棧中的數(shù)據(jù)相同,也不會與棧中的數(shù)據(jù)共享。
總結(jié):
(1)我們在使用諸如String str = "abc";的格式定義類時,總是想當(dāng)然地認(rèn)為,我們創(chuàng)建了String類的對象str。擔(dān)心陷阱!對象可能并沒有被創(chuàng)建!唯一可以肯定的是,指向 String類的引用被創(chuàng)建了。至于這個引用到底是否指向了一個新的對象,必須根據(jù)上下文來考慮,除非你通過new()方法來顯要地創(chuàng)建一個新的對象。因此,更為準(zhǔn)確的說法是,我們創(chuàng)建了一個指向String類的對象的引用變量str,這個對象引用變量指向了某個值為"abc"的String類。清醒地認(rèn)識到這一點對排除程序中難以發(fā)現(xiàn)的bug是很有幫助的。
(2)使用String str = "abc";的方式,可以在一定程度上提高程序的運行速度,因為JVM會自動根據(jù)棧中數(shù)據(jù)的實際情況來決定是否有必要創(chuàng)建新對象。而對于String str = new String("abc");的代碼,則一概在堆中創(chuàng)建新對象,而不管其字符串值是否相等,是否有必要創(chuàng)建新對象,從而加重了程序的負(fù)擔(dān)。
(3)由于String類的immutable性質(zhì)(因為包裝類的值不可修改),當(dāng)String變量需要經(jīng)常變換其值時,應(yīng)該考慮使用StringBuffer類,以提高程序效率。
相關(guān)文章
Java 將List中的實體類按照某個字段進(jìn)行分組并存放至Map中操作
這篇文章主要介紹了Java 將List中的實體類按照某個字段進(jìn)行分組并存放至Map中操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10Java中的CopyOnWriteArrayList你了解嗎
CopyOnWriteArrayList是Java集合框架中的一種線程安全的List實現(xiàn),這篇文章主要來和大家聊聊CopyOnWriteArrayList的簡單使用,需要的可以參考一下2023-06-06Spring中@Configuration注解和@Component注解的區(qū)別詳解
這篇文章主要介紹了Spring中@Configuration注解和@Component注解的區(qū)別詳解,@Configuration 和 @Component 到底有何區(qū)別呢?我先通過如下一個案例,在不分析源碼的情況下,小伙伴們先來直觀感受一下這兩個之間的區(qū)別,需要的朋友可以參考下2023-09-09Spring boot集成swagger2生成接口文檔的全過程
這篇文章主要給大家介紹了關(guān)于Spring boot集成swagger2生成接口文檔的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Spring boot具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Java并發(fā)系列之AbstractQueuedSynchronizer源碼分析(條件隊列)
這篇文章主要為大家詳細(xì)介紹了Java并發(fā)系列之AbstractQueuedSynchronizer源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-02-02