Java基礎(chǔ)之內(nèi)存泄漏與溢出詳解
一、淺析
內(nèi)存泄露( memory leak):是指程序在申請內(nèi)存后,無法釋放已申請的內(nèi)存空間,多次內(nèi)存泄露堆積后果很嚴(yán)重,內(nèi)存遲早會被占光。內(nèi)存泄漏最終會造成內(nèi)存溢出。
內(nèi)存溢出(out of memory) :是指程序在申請內(nèi)存時,沒有足夠的內(nèi)存空間供其使用
JVM中有一下幾種內(nèi)存空間:
- 棧內(nèi)存(Stack):每個線程私有的。
- 堆內(nèi)存(Heap):所有線程公用的。
- 方法區(qū)(Method Area):有點像以前常說的“進(jìn)程代碼段”,這里面存放了每個加載類的反射信息、類函數(shù)的代碼、編譯時常量等信息。
- 原生方法棧(Native Method Stack):主要用于JNI中的原生代碼,平時很少涉及。
Java的內(nèi)存回收機制:
Java堆是一個運行時數(shù)據(jù)區(qū),類的實例(對象)從中分配空間,JVM堆中儲存著正在運行的應(yīng)用程序所建立的所有對象,“垃圾回收”主要也是和堆有關(guān)。
不論哪種語言的內(nèi)存分配方式,都需要返回所分配內(nèi)存的真實地址,也就是返回一個指針到內(nèi)存塊的首地址,Java中對象是采用new或者反射的方法創(chuàng)建的,這些對象的創(chuàng)建都是在堆(Heap)中分配的。
二、Java內(nèi)存泄露
內(nèi)存泄露是指當(dāng)前未被引用的對象持續(xù)占用內(nèi)存導(dǎo)致內(nèi)存空間的浪費。常見的內(nèi)存泄漏有以下幾大類:
(1)靜態(tài)集合類引起
比如說靜態(tài)HashMap、Vector等,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,他們所引用的所有的對象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)申請Object 對象,并將所申請的對象放入一個Vector 中,如果僅僅釋放引用本身(o=null),那么Vector 仍然引用該對象,所以這個對象對GC 來說是不可回收的。
必須要降Vector對象設(shè)置為null,才能回收這部分占用的內(nèi)存
(2)當(dāng)集合里面的對象屬性被修改后,再調(diào)用remove()方法時不起作用。
主要原因是:set類存儲對象是通過hashcode存儲,如對象屬性被修改,remove方法就不能通過原先的hashcode刪除對象。
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()+" 個元素!"); //結(jié)果:總共有:3 個元素!
p3.setAge(2); //修改p3的年齡,此時p3元素對應(yīng)的hashcode值發(fā)生改變,remove是通過hashcode刪除對象
set.remove(p3); //此時remove不掉,造成內(nèi)存泄漏
set.add(p3); //重新添加,居然添加成功
System.out.println("總共有:"+set.size()+" 個元素!"); //結(jié)果:總共有:4 個元素!
for (Person person : set)
{
System.out.println(person);
}
}
(3)監(jiān)聽器
監(jiān)聽器調(diào)用太多,釋放對象時未刪除監(jiān)聽器也可能造成內(nèi)存泄漏
(4)各種連接
數(shù)據(jù)庫連接(dataSourse.getConnection()),網(wǎng)絡(luò)連接(socket)和io連接,除非其顯式的調(diào)用了其close()方法將其連接關(guān)閉,否則是不會自動被GC 回收的。Connection一旦回收,Resultset 和Statement 對象就會立即為NULL
如果使用連接池,Resultset 和Statement 對象也需要顯式的關(guān)閉,否則就會造成大量的Statement 對象無法釋放,從而引起內(nèi)存泄漏,這種情況下一般都會在try里面去的連接,在finally里面釋放連接。
(5)單例模式
如果單例對象持有外部對象的引用,那么這個外部對象將不能被jvm正?;厥?,就會導(dǎo)致內(nèi)存泄露。
比如說:
class A{
public A(){
B.getInstance().setA(this);
}
....
}
//B類采用單例模式
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模式,它持有一個A對象的引用,而這個A類的對象將不能被回收
三、Java內(nèi)存溢出
當(dāng)內(nèi)存占有量超過了虛擬機的分配的最大值時就會產(chǎn)生內(nèi)存溢出(JVM里面分配不出更多的page)
一般出現(xiàn)情況:
- 加載的圖片太多或圖片過大時
- 分配特大的數(shù)組
- 內(nèi)存相應(yīng)資源過多沒有來不及釋放。
JVM內(nèi)存模型:

Java應(yīng)用程序在啟動時會指定所需要的內(nèi)存大小,它被分割成兩個不同的區(qū)域:Heap space(堆空間)和Permgen(永久代)。
(1)JVM Heap堆溢出:java.lang.OutOfMemoryError: Java heap space
在JVM中如果98%的時間是用于GC,且可用的Heap size 不足2%的時候?qū)伋龃水惓P畔ⅰ?/p>
JVM啟動時會自動設(shè)置JVM Heap的值,可以利用JVM提供的-Xmn -Xms -Xmx等選項可進(jìn)行設(shè)置,當(dāng)需要為對象實例分配內(nèi)存,而堆的內(nèi)存占用又已經(jīng)達(dá)到-Xmx設(shè)置的最大值。將會拋出OutOfMemoryError異常。
解決方法:手動設(shè)置JVM Heap(堆)的大小。
(2)PermGen space溢出: java.lang.OutOfMemoryError: PermGen space
PermGen space的全稱是Permanent Generation space,是指內(nèi)存的永久保存區(qū)域。
PermGen space主要是被JVM存放Class和Meta信息的,Class在被Load的時候被放入PermGen space區(qū)域,它和存放Instance的Heap區(qū)域不同,sun的 GC不會在主程序運行期對PermGen space進(jìn)行清理,所以如果你的APP會載入很多CLASS的話,就很可能出現(xiàn)PermGen space溢出。一般發(fā)生在程序的啟動階段
解決方法: 通過-XX:PermSize和-XX:MaxPermSize設(shè)置永久代大小即可。
(3)棧溢出: java.lang.StackOverflowError : Thread Stack space
調(diào)用構(gòu)造函數(shù)的 “層”太多了,以致于把棧區(qū)溢出了。通俗一點講就是單線程的程序需要的內(nèi)存太大了。 通常遞歸也不要遞歸的層次過多,很容易溢出。
解決方法:
1:修改程序。
2:通過 -Xss: 來設(shè)置每個線程的Stack大小即可。
在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常狀況:
- StackOverflowError異常:啟動一個新線程時,Java虛擬機都會為它分配一個Java棧。Java棧以幀為單位保存線程的運行狀態(tài)。當(dāng)線程調(diào)用java方法時,虛擬機壓入一個新的棧幀到該線程的java棧中。只要這個方法還沒有返回,它就一直存在。如果線程的方法嵌套調(diào)用層次太多(如遞歸調(diào)用),隨著java棧中幀的逐漸增多,最終會由于該線程java棧中所有棧幀大小總和大于-Xss設(shè)置的值,而產(chǎn)生StackOverflowError內(nèi)存溢出異常
- OutOfMemoryError異常:啟動一個新線程時,沒有足夠的內(nèi)存空間為該線程分配java棧(一個線程java棧的大小由-Xss參數(shù)確定),jvm則拋出OutOfMemoryError異常
到此這篇關(guān)于Java基礎(chǔ)之內(nèi)存泄漏與溢出詳解的文章就介紹到這了,更多相關(guān)Java內(nèi)存泄漏與溢出內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Spring Boot2中使用CompletableFuture的方法教程
這篇文章主要給大家介紹了關(guān)于在Spring Boot2中使用CompletableFuture的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧2019-01-01
如何實現(xiàn)nohup?java進(jìn)程號一直在變方法步驟詳解
這篇文章主要為大家介紹了如何實現(xiàn)nohup?java進(jìn)程號一直在變方法步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Spring內(nèi)置定時任務(wù)調(diào)度@Scheduled使用詳解
這篇文章主要介紹了Spring內(nèi)置定時任務(wù)調(diào)度@Scheduled使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-12-12
Springboot?Vue實現(xiàn)單點登陸功能示例詳解
這篇文章主要為大家介紹了Springboot?Vue實現(xiàn)單點登陸功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
SpringCloud?中防止繞過網(wǎng)關(guān)請求直接訪問后端服務(wù)的解決方法
這篇文章主要介紹了SpringCloud中如何防止繞過網(wǎng)關(guān)請求直接訪問后端服務(wù),本文給大家分享三種解決方案,需要的朋友可以參考下2023-06-06
springsecurity 企業(yè)微信登入的實現(xiàn)示例
本文主要介紹了springsecurity 企業(yè)微信登入的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04

