Java基礎(chǔ)之內(nèi)存泄漏與溢出詳解
一、淺析
內(nèi)存泄露( memory leak):是指程序在申請(qǐng)內(nèi)存后,無(wú)法釋放已申請(qǐng)的內(nèi)存空間,多次內(nèi)存泄露堆積后果很?chē)?yán)重,內(nèi)存遲早會(huì)被占光。內(nèi)存泄漏最終會(huì)造成內(nèi)存溢出。
內(nèi)存溢出(out of memory) :是指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存空間供其使用
JVM中有一下幾種內(nèi)存空間:
- 棧內(nèi)存(Stack):每個(gè)線(xiàn)程私有的。
- 堆內(nèi)存(Heap):所有線(xiàn)程公用的。
- 方法區(qū)(Method Area):有點(diǎn)像以前常說(shuō)的“進(jìn)程代碼段”,這里面存放了每個(gè)加載類(lèi)的反射信息、類(lèi)函數(shù)的代碼、編譯時(shí)常量等信息。
- 原生方法棧(Native Method Stack):主要用于JNI中的原生代碼,平時(shí)很少涉及。
Java的內(nèi)存回收機(jī)制:
Java堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū),類(lèi)的實(shí)例(對(duì)象)從中分配空間,JVM堆中儲(chǔ)存著正在運(yùn)行的應(yīng)用程序所建立的所有對(duì)象,“垃圾回收”主要也是和堆有關(guān)。
不論哪種語(yǔ)言的內(nèi)存分配方式,都需要返回所分配內(nèi)存的真實(shí)地址,也就是返回一個(gè)指針到內(nèi)存塊的首地址,Java中對(duì)象是采用new或者反射的方法創(chuàng)建的,這些對(duì)象的創(chuàng)建都是在堆(Heap)中分配的。
二、Java內(nèi)存泄露
內(nèi)存泄露是指當(dāng)前未被引用的對(duì)象持續(xù)占用內(nèi)存導(dǎo)致內(nèi)存空間的浪費(fèi)。常見(jiàn)的內(nèi)存泄漏有以下幾大類(lèi):
(1)靜態(tài)集合類(lèi)引起
比如說(shuō)靜態(tài)HashMap、Vector等,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,他們所引用的所有的對(duì)象Object也不能被釋放。
Static Vector v = new Vector(10); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; }//
如上所示,循環(huán)申請(qǐng)Object 對(duì)象,并將所申請(qǐng)的對(duì)象放入一個(gè)Vector 中,如果僅僅釋放引用本身(o=null),那么Vector 仍然引用該對(duì)象,所以這個(gè)對(duì)象對(duì)GC 來(lái)說(shuō)是不可回收的。
必須要降Vector對(duì)象設(shè)置為null,才能回收這部分占用的內(nèi)存
(2)當(dāng)集合里面的對(duì)象屬性被修改后,再調(diào)用remove()方法時(shí)不起作用。
主要原因是:set類(lèi)存儲(chǔ)對(duì)象是通過(guò)hashcode存儲(chǔ),如對(duì)象屬性被修改,remove方法就不能通過(guò)原先的hashcode刪除對(duì)象。
public static void main(String[] args) { Set<Person> set = new HashSet<Person>(); Person p1 = new Person("唐僧","pwd1",25); Person p2 = new Person("孫悟空","pwd2",26); Person p3 = new Person("豬八戒","pwd3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("總共有:"+set.size()+" 個(gè)元素!"); //結(jié)果:總共有:3 個(gè)元素! p3.setAge(2); //修改p3的年齡,此時(shí)p3元素對(duì)應(yīng)的hashcode值發(fā)生改變,remove是通過(guò)hashcode刪除對(duì)象 set.remove(p3); //此時(shí)remove不掉,造成內(nèi)存泄漏 set.add(p3); //重新添加,居然添加成功 System.out.println("總共有:"+set.size()+" 個(gè)元素!"); //結(jié)果:總共有:4 個(gè)元素! for (Person person : set) { System.out.println(person); } }
(3)監(jiān)聽(tīng)器
監(jiān)聽(tīng)器調(diào)用太多,釋放對(duì)象時(shí)未刪除監(jiān)聽(tīng)器也可能造成內(nèi)存泄漏
(4)各種連接
數(shù)據(jù)庫(kù)連接(dataSourse.getConnection()),網(wǎng)絡(luò)連接(socket)和io連接,除非其顯式的調(diào)用了其close()方法將其連接關(guān)閉,否則是不會(huì)自動(dòng)被GC 回收的。Connection一旦回收,Resultset 和Statement 對(duì)象就會(huì)立即為NULL
如果使用連接池,Resultset 和Statement 對(duì)象也需要顯式的關(guān)閉,否則就會(huì)造成大量的Statement 對(duì)象無(wú)法釋放,從而引起內(nèi)存泄漏,這種情況下一般都會(huì)在try里面去的連接,在finally里面釋放連接。
(5)單例模式
如果單例對(duì)象持有外部對(duì)象的引用,那么這個(gè)外部對(duì)象將不能被jvm正常回收,就會(huì)導(dǎo)致內(nèi)存泄露。
比如說(shuō):
class A{ public A(){ B.getInstance().setA(this); } .... } //B類(lèi)采用單例模式 class B{ private A a; private static B instance=new B(); public B(){} public static B getInstance(){ return instance; } public void setA(A a){ this.a=a; } //getter... }
顯然B采用singleton模式,它持有一個(gè)A對(duì)象的引用,而這個(gè)A類(lèi)的對(duì)象將不能被回收
三、Java內(nèi)存溢出
當(dāng)內(nèi)存占有量超過(guò)了虛擬機(jī)的分配的最大值時(shí)就會(huì)產(chǎn)生內(nèi)存溢出(JVM里面分配不出更多的page)
一般出現(xiàn)情況:
- 加載的圖片太多或圖片過(guò)大時(shí)
- 分配特大的數(shù)組
- 內(nèi)存相應(yīng)資源過(guò)多沒(méi)有來(lái)不及釋放。
JVM內(nèi)存模型:
Java應(yīng)用程序在啟動(dòng)時(shí)會(huì)指定所需要的內(nèi)存大小,它被分割成兩個(gè)不同的區(qū)域:Heap space(堆空間)和Permgen(永久代)。
(1)JVM Heap堆溢出:java.lang.OutOfMemoryError: Java heap space
在JVM中如果98%的時(shí)間是用于GC,且可用的Heap size 不足2%的時(shí)候?qū)伋龃水惓P畔ⅰ?/p>
JVM啟動(dòng)時(shí)會(huì)自動(dòng)設(shè)置JVM Heap的值,可以利用JVM提供的-Xmn -Xms -Xmx等選項(xiàng)可進(jìn)行設(shè)置,當(dāng)需要為對(duì)象實(shí)例分配內(nèi)存,而堆的內(nèi)存占用又已經(jīng)達(dá)到-Xmx設(shè)置的最大值。將會(huì)拋出OutOfMemoryError異常。
解決方法:手動(dòng)設(shè)置JVM Heap(堆)的大小。
(2)PermGen space溢出: java.lang.OutOfMemoryError: PermGen space
PermGen space的全稱(chēng)是Permanent Generation space,是指內(nèi)存的永久保存區(qū)域。
PermGen space主要是被JVM存放Class和Meta信息的,Class在被Load的時(shí)候被放入PermGen space區(qū)域,它和存放Instance的Heap區(qū)域不同,sun的 GC不會(huì)在主程序運(yùn)行期對(duì)PermGen space進(jìn)行清理,所以如果你的APP會(huì)載入很多CLASS的話(huà),就很可能出現(xiàn)PermGen space溢出。一般發(fā)生在程序的啟動(dòng)階段
解決方法: 通過(guò)-XX:PermSize和-XX:MaxPermSize設(shè)置永久代大小即可。
(3)棧溢出: java.lang.StackOverflowError : Thread Stack space
調(diào)用構(gòu)造函數(shù)的 “層”太多了,以致于把棧區(qū)溢出了。通俗一點(diǎn)講就是單線(xiàn)程的程序需要的內(nèi)存太大了。 通常遞歸也不要遞歸的層次過(guò)多,很容易溢出。
解決方法:
1:修改程序。
2:通過(guò) -Xss: 來(lái)設(shè)置每個(gè)線(xiàn)程的Stack大小即可。
在Java虛擬機(jī)規(guī)范中,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常狀況:
- StackOverflowError異常:?jiǎn)?dòng)一個(gè)新線(xiàn)程時(shí),Java虛擬機(jī)都會(huì)為它分配一個(gè)Java棧。Java棧以幀為單位保存線(xiàn)程的運(yùn)行狀態(tài)。當(dāng)線(xiàn)程調(diào)用java方法時(shí),虛擬機(jī)壓入一個(gè)新的棧幀到該線(xiàn)程的java棧中。只要這個(gè)方法還沒(méi)有返回,它就一直存在。如果線(xiàn)程的方法嵌套調(diào)用層次太多(如遞歸調(diào)用),隨著java棧中幀的逐漸增多,最終會(huì)由于該線(xiàn)程java棧中所有棧幀大小總和大于-Xss設(shè)置的值,而產(chǎn)生StackOverflowError內(nèi)存溢出異常
- OutOfMemoryError異常:?jiǎn)?dòng)一個(gè)新線(xiàn)程時(shí),沒(méi)有足夠的內(nèi)存空間為該線(xiàn)程分配java棧(一個(gè)線(xiàn)程java棧的大小由-Xss參數(shù)確定),jvm則拋出OutOfMemoryError異常
到此這篇關(guān)于Java基礎(chǔ)之內(nèi)存泄漏與溢出詳解的文章就介紹到這了,更多相關(guān)Java內(nèi)存泄漏與溢出內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java使用ffmpeg實(shí)現(xiàn)上傳視頻的轉(zhuǎn)碼提取視頻的截圖等功能(代碼操作)
這篇文章主要介紹了java使用ffmpeg實(shí)現(xiàn)上傳視頻的轉(zhuǎn)碼,提取視頻的截圖等功能,本文圖文并茂給大家介紹的非常詳細(xì),對(duì)大家的工作或?qū)W習(xí)具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03Java常用數(shù)字工具類(lèi) 大數(shù)乘法、加法、減法運(yùn)算(2)
這篇文章主要為大家詳細(xì)介紹了Java常用數(shù)字工具類(lèi),大數(shù)乘法、加法、減法運(yùn)算,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05Java8中stream和functional interface的配合使用詳解
這篇文章主要給大家介紹了關(guān)于Java8中stream和functional interface配合使用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java8具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-11-11Springboot啟動(dòng)報(bào)錯(cuò)時(shí)實(shí)現(xiàn)異常定位
這篇文章主要介紹了Springboot啟動(dòng)報(bào)錯(cuò)時(shí)實(shí)現(xiàn)異常定位,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06IntelliJ?IDEA?2024.2?發(fā)布新功能介紹Spring?Data?JPA即時(shí)查詢(xún)、自動(dòng)補(bǔ)全cro
在2024.2?Ultimate版本中,對(duì)?Spring?Data?JPA?的支持做了增強(qiáng),新功能允許您在不運(yùn)行應(yīng)用程序和分析日志文件的情況下查看方法將生成的查詢(xún),下面就來(lái)一起看看這個(gè)版本中推出的幾個(gè)強(qiáng)大新特性2024-08-08JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)劃分原理詳解
這篇文章主要介紹了JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)劃分原理詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05SpringMVC?Restful風(fēng)格與中文亂碼問(wèn)題解決方案介紹
Restful就是一個(gè)資源定位及資源操作的風(fēng)格,不是標(biāo)準(zhǔn)也不是協(xié)議,只是一種風(fēng)格,是對(duì)http協(xié)議的詮釋,下面這篇文章主要給大家介紹了關(guān)于SpringMVC對(duì)Restful風(fēng)格支持的相關(guān)資料,需要的朋友可以參考下2022-10-10spring cloud zuul 與 sentinel的結(jié)合使用操作
這篇文章主要介紹了spring cloud zuul 與 sentinel 的結(jié)合使用操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06