內(nèi)存泄漏(Memory Leak)
成都創(chuàng)新互聯(lián)公司主營(yíng)達(dá)川網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,手機(jī)APP定制開發(fā),達(dá)川h5重慶小程序開發(fā)公司搭建,達(dá)川網(wǎng)站營(yíng)銷推廣歡迎達(dá)川等地區(qū)企業(yè)咨詢
概念
內(nèi)存泄漏(Memory Leak)是指程序中己動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無(wú)法釋放,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果。
發(fā)生條件
內(nèi)存泄漏必須滿足以下兩個(gè)條件
對(duì)象是可達(dá)的。即在有向圖中,存在通道達(dá)到該對(duì)象,GC不會(huì)回收
對(duì)象是無(wú)用的。即程序以后不會(huì)再使用該對(duì)象
發(fā)生場(chǎng)景
靜態(tài)集合類引起內(nèi)存泄漏
HashMap、Vactor等集合的使用最容易出現(xiàn)內(nèi)存泄漏。因?yàn)檫@些集合屬于靜態(tài)集合,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,他們所引用的所有Object對(duì)象都不能被釋放,因?yàn)檫@些對(duì)象還一直被Vector引用著
Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object(); //每次創(chuàng)建新的對(duì)象
v.add(o);
o = null; //將對(duì)象添加到集合后將對(duì)象的引用置空
}
//因?yàn)閷?duì)象的引用置空之后,JVM已經(jīng)失去的使用該對(duì)象的價(jià)值,本應(yīng)該被GC清除,但是在vector集合中還存在著此對(duì)象的引用,導(dǎo)致沒能順利清除
循環(huán)申請(qǐng)Object 對(duì)象,并將所申請(qǐng)的對(duì)象放入一個(gè)Vector 中,如果僅僅釋放引用本身(o=null),那么Vector 仍然引用該對(duì)象,所以這個(gè)對(duì)象對(duì)GC 來(lái)說(shuō)是不可回收的。因此,如果對(duì)象加入到Vector 后,還必須從Vector 中刪除,最簡(jiǎn)單的方法就是將v = null。這樣就可以將Vector執(zhí)行那個(gè)的對(duì)象也釋放。
當(dāng)集合(Hash算法的集合)里面的對(duì)象屬性被修改后,再調(diào)用remove()方法時(shí)不起作用
public static void main(String[] args) {
Set set = new HashSet();
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ā)生改變
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);
System.out.println(person.hashCode());
}
}
監(jiān)聽器
在java 編程中,我們都需要和監(jiān)聽器打交道,通常一個(gè)應(yīng)用當(dāng)中會(huì)用到很多監(jiān)聽器,我們會(huì)調(diào)用一個(gè)控件的諸如addXXXListener()等方法來(lái)增加監(jiān)聽器,但往往在釋放對(duì)象的時(shí)候卻沒有記住去刪除這些監(jiān)聽器,從而增加了內(nèi)存泄漏的機(jī)會(huì)。
各種連接
比如數(shù)據(jù)庫(kù)連接(dataSourse.getConnection()),網(wǎng)絡(luò)連接(socket)和io連接,除非其顯式的調(diào)用了其close()方法將其連接關(guān)閉,否則是不會(huì)自動(dòng)被GC 回收的。對(duì)于Resultset 和Statement 對(duì)象可以不進(jìn)行顯式回收,但Connection 一定要顯式回收,因?yàn)镃onnection 在任何時(shí)候都無(wú)法自動(dòng)回收,而Connection一旦回收,Resultset 和Statement 對(duì)象就會(huì)立即為NULL。但是如果使用連接池,情況就不一樣了,除了要顯式地關(guān)閉連接,還必須顯式地關(guān)閉Resultset Statement 對(duì)象(關(guān)閉其中一個(gè),另外一個(gè)也會(huì)關(guān)閉),否則就會(huì)造成大量的Statement 對(duì)象無(wú)法釋放,從而引起內(nèi)存泄漏。這種情況下一般都會(huì)在try里面去的連接,在finally里面釋放連接。
單例模式
如果單例對(duì)象持有外部對(duì)象的引用,那么這個(gè)外部對(duì)象將不能被jvm正?;厥眨瑢?dǎo)致內(nèi)存泄露。
不正確使用單例模式是引起內(nèi)存泄露的一個(gè)常見問題,單例對(duì)象在被初始化后將在JVM的整個(gè)生命周期中存在(以靜態(tài)變量的方式),如果單例對(duì)象持有外部對(duì)象的引用,那么這個(gè)外部對(duì)象將不能被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模式,它持有一個(gè)A對(duì)象的引用,而這個(gè)A類的對(duì)象將不能被回收。想象下如果A是個(gè)比較復(fù)雜的對(duì)象或者集合類型會(huì)發(fā)生什么情況
分類鄭州婦科醫(yī)院 http://www.zyfuke.com/
1. 常發(fā)性內(nèi)存泄漏。 發(fā)生內(nèi)存泄漏的代碼會(huì)被多次執(zhí)行到,每次被執(zhí)行的時(shí)候都會(huì)導(dǎo)致一塊內(nèi)存泄漏。
2. 偶發(fā)性內(nèi)存泄漏。 發(fā)生內(nèi)存泄漏的代碼只有在某些特定環(huán)境或操作過(guò)程下才會(huì)發(fā)生。常發(fā)性和偶發(fā)性是相對(duì)的。對(duì)于特定的環(huán)境,偶發(fā)性的也許就變成了常發(fā)性的。所以測(cè)試環(huán)境和測(cè)試方法對(duì)檢測(cè)內(nèi)存泄漏至關(guān)重要。
3. 一次性內(nèi)存泄漏。 發(fā)生內(nèi)存泄漏的代碼只會(huì)被執(zhí)行一次,或者由于算法上的缺陷,導(dǎo)致總會(huì)有一塊僅且一塊內(nèi)存發(fā)生泄漏。比如,在類的構(gòu)造函數(shù)中分配內(nèi)存,在析構(gòu)函數(shù)中卻沒有釋放該內(nèi)存,所以內(nèi)存泄漏只會(huì)發(fā)生一次。
4. 隱式內(nèi)存泄漏。 程序在運(yùn)行過(guò)程中不停的分配內(nèi)存,但是直到結(jié)束的時(shí)候才釋放內(nèi)存。嚴(yán)格的說(shuō)這里并沒有發(fā)生內(nèi)存泄漏,因?yàn)樽罱K程序釋放了所有申請(qǐng)的內(nèi)存。但是對(duì)于一個(gè)服務(wù)器程序,需要運(yùn)行幾天,幾周甚至幾個(gè)月,不及時(shí)釋放內(nèi)存也可能導(dǎo)致最終耗盡系統(tǒng)的所有內(nèi)存。所以,我們稱這類內(nèi)存泄漏為隱式內(nèi)存泄漏。
內(nèi)存溢出(Out Of Memory)
概念
程序在申請(qǐng)內(nèi)存時(shí),沒有足夠的內(nèi)存空間供其使用
內(nèi)存溢出已經(jīng)是軟件開發(fā)歷史上存在了近40年的“老大難”問題,像在“紅色代碼”病毒事件中表現(xiàn)的那樣,它已經(jīng)成為黑客攻擊企業(yè)網(wǎng)絡(luò)的“罪魁禍?zhǔn)住薄?如在一個(gè)域中輸入的數(shù)據(jù)超過(guò)了它的要求就會(huì)引發(fā)數(shù)據(jù)溢出問題,多余的數(shù)據(jù)就可以作為指令在計(jì)算機(jī)上運(yùn)行。據(jù)有關(guān)安全小組稱,操作系統(tǒng)中超過(guò)50%的安全漏洞都是由內(nèi)存溢出引起的,其中大多數(shù)與微軟的技術(shù)有關(guān)。內(nèi)存溢出錯(cuò)誤是大數(shù)據(jù)處理平臺(tái)的常見錯(cuò)誤,例如,國(guó)際知名的程序開發(fā)者問答網(wǎng)站 stackoverflow 上關(guān)于“Hadoop out of memory”的問題超過(guò)10000個(gè),在Spark郵件列表上有10%的問題是關(guān)于“out of memory”。 內(nèi)存溢出錯(cuò)誤會(huì)導(dǎo)致處理數(shù)據(jù)的任務(wù)失敗,甚至?xí)l(fā)平臺(tái)崩潰等嚴(yán)重后果。對(duì)于內(nèi)存溢出大部分的處理方法是重新執(zhí)行任務(wù),然而, 對(duì)于由系統(tǒng)配置、數(shù)據(jù)流、用戶代碼等原因而導(dǎo)致的內(nèi)存溢出錯(cuò)誤,即使用戶重新執(zhí)行任務(wù)依然無(wú)法避免
發(fā)生條件
內(nèi)存中加載的數(shù)據(jù)過(guò)于龐大,如一次性從數(shù)據(jù)庫(kù)取出過(guò)多的數(shù)據(jù)
集合類中,有對(duì)對(duì)象的引用,使用完后未清空,使得JVM不能不嫩回收
代碼中存在死循環(huán)或循環(huán)產(chǎn)生過(guò)多重復(fù)的對(duì)象實(shí)體
使用的第三方軟件存在bug
啟動(dòng)參數(shù)內(nèi)存值設(shè)置的過(guò)小
分類
OutOfMemoryError: PermGen space
PermGen Space指的是內(nèi)存的永久保存區(qū),該塊內(nèi)存主要是被JVM用來(lái)存放class和meta信息的,當(dāng)class被加載loader的時(shí)候就會(huì)被存儲(chǔ)到該內(nèi)存區(qū)中,與存放類的實(shí)例的heap區(qū)不同,java中的垃圾回收器GC不會(huì)在主程序運(yùn)行期對(duì)PermGen space進(jìn)行清理。
因此,程序啟動(dòng)時(shí)如果需要加載的信息太多,超出這個(gè)空間的大小,則會(huì)發(fā)生溢出。
解決方案: 增加空間分配——增加java虛擬機(jī)中的XX:PermSize和XX:MaxPermSize參數(shù)的大小,其中XX:PermSize是初始永久保存區(qū)域大小,XX:MaxPermSize是最大永久保存區(qū)域大小。
OutOfMemoryError:Java heap space
heap space是Java內(nèi)存中的堆區(qū),主要用來(lái)存放對(duì)象,當(dāng)對(duì)象太多超出了空間大小,GC又來(lái)不及釋放的時(shí)候,就會(huì)發(fā)生溢出錯(cuò)誤。即內(nèi)存泄露越來(lái)越嚴(yán)重時(shí),可能會(huì)發(fā)生內(nèi)存溢出。
解決方案:(1)、檢查程序,減少大量重復(fù)創(chuàng)建對(duì)象的死循環(huán),減少內(nèi)存泄露。
(2)、增加Java虛擬機(jī)中Xms(初始堆大小)和Xmx(最大堆大小)參數(shù)的大小。
StackOverFlowError
stack是Java內(nèi)存中的棧空間,主要用來(lái)存放方法中的變量,參數(shù)等臨時(shí)性的數(shù)據(jù)的,發(fā)生溢出一般是因?yàn)榉峙淇臻g太小,或是執(zhí)行的方法遞歸層數(shù)太多創(chuàng)建了占用了太多棧幀導(dǎo)致溢出。
解決方案: 修改配置參數(shù)-Xss參數(shù)增加線程棧大小之外,優(yōu)化程序是尤其重要。
OOM排查思路
第一步,修改JVM啟動(dòng)參數(shù),直接增加內(nèi)存。(-Xms,-Xmx參數(shù)一定不要忘記加。)
第二步,檢查錯(cuò)誤日志,查看“OutOfMemery”錯(cuò)誤前,是否有其他異常或錯(cuò)誤
第三步,對(duì)代碼進(jìn)行走查分析,找出可能發(fā)生內(nèi)存溢出的位置
重點(diǎn)排查以下幾點(diǎn):
1.檢查對(duì)數(shù)據(jù)庫(kù)查詢中,是否有一次獲得全部數(shù)據(jù)的查詢。一般來(lái)說(shuō),如果一次取十萬(wàn)條記錄到內(nèi)存,就可能引起內(nèi)存溢出。這個(gè)問題比較隱蔽,在上線前,數(shù)據(jù)庫(kù)中數(shù)據(jù)較少,不容易出問題,上線后,數(shù)據(jù)庫(kù)中數(shù)據(jù)多了,一次查詢就有可能引起內(nèi)存溢出。因此對(duì)于數(shù)據(jù)庫(kù)查詢盡量采用分頁(yè)的方式查詢。
2.檢查代碼中是否有死循環(huán)或遞歸調(diào)用。
3.檢查是否有大循環(huán)重復(fù)產(chǎn)生新對(duì)象實(shí)體。
4.檢查L(zhǎng)ist、MAP等集合對(duì)象是否有使用完后,未清除的問題。List、MAP等集合對(duì)象會(huì)始終存有對(duì)對(duì)象的引用,使得這些對(duì)象不能被GC回收。
第四步,使用內(nèi)存查看工具動(dòng)態(tài)查看內(nèi)存使用情況