日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java内存泄漏总结

發(fā)布時(shí)間:2024/1/17 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java内存泄漏总结 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講,就是該被釋放的對(duì)象沒有釋放,一直被某個(gè)或某些實(shí)例所持有卻不再被使用導(dǎo)致 GC 不能回收。最近自己閱讀了大量相關(guān)的文檔資料,打算做個(gè) 總結(jié) 沉淀下來跟大家一起分享和學(xué)習(xí),也給自己一個(gè)警示,以后 coding 時(shí)怎么避免這些情況,提高應(yīng)用的體驗(yàn)和質(zhì)量。

我會(huì)從 java 內(nèi)存泄漏的基礎(chǔ)知識(shí)開始,并通過具體例子來說明 Android 引起內(nèi)存泄漏的各種原因,以及如何利用工具來分析應(yīng)用內(nèi)存泄漏,最后再做總結(jié)。

Java 內(nèi)存分配策略

Java 程序運(yùn)行時(shí)的內(nèi)存分配策略有三種,分別是靜態(tài)分配,棧式分配,和堆式分配,對(duì)應(yīng)的,三種存儲(chǔ)策略使用的內(nèi)存空間主要分別是靜態(tài)存儲(chǔ)區(qū)(也稱方法區(qū))、棧區(qū)和堆區(qū)。

靜態(tài)存儲(chǔ)區(qū)(方法區(qū)):主要存放靜態(tài)數(shù)據(jù)、全局 static 數(shù)據(jù)和常量。這塊內(nèi)存在程序編譯時(shí)就已經(jīng)分配好,并且在程序整個(gè)運(yùn)行期間都存在。

棧區(qū) :當(dāng)方法被執(zhí)行時(shí),方法體內(nèi)的局部變量(其中包括基礎(chǔ)數(shù)據(jù)類型、對(duì)象的引用)都在棧上創(chuàng)建,并在方法執(zhí)行結(jié)束時(shí)這些局部變量所持有的內(nèi)存將會(huì)自動(dòng)被釋放。因?yàn)闂?nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。

堆區(qū) : 又稱動(dòng)態(tài)內(nèi)存分配,通常就是指在程序運(yùn)行時(shí)直接 new 出來的內(nèi)存,也就是對(duì)象的實(shí)例。這部分內(nèi)存在不使用時(shí)將會(huì)由 Java 垃圾回收器來負(fù)責(zé)回收。

棧與堆的區(qū)別:

在方法體內(nèi)定義的(局部變量)一些基本類型的變量和對(duì)象的引用變量都是在方法的棧內(nèi)存中分配的。當(dāng)在一段方法塊中定義一個(gè)變量時(shí),Java 就會(huì)在棧中為該變量分配內(nèi)存空間,當(dāng)超過該變量的作用域后,該變量也就無效了,分配給它的內(nèi)存空間也將被釋放掉,該內(nèi)存空間可以被重新使用。

堆內(nèi)存用來存放所有由 new 創(chuàng)建的對(duì)象(包括該對(duì)象其中的所有成員變量)和數(shù)組。在堆中分配的內(nèi)存,將由 Java 垃圾回收器來自動(dòng)管理。在堆中產(chǎn)生了一個(gè)數(shù)組或者對(duì)象后,還可以在棧中定義一個(gè)特殊的變量,這個(gè)變量的取值等于數(shù)組或者對(duì)象在堆內(nèi)存中的首地址,這個(gè)特殊的變量就是我們上面說的引用變量。我們可以通過這個(gè)引用變量來訪問堆中的對(duì)象或者數(shù)組。

舉個(gè)例子:

public class Sample {int s1 = 0;Sample mSample1 = new Sample();public void method() {int s2 = 1;Sample mSample2 = new Sample(); } }Sample mSample3 = new Sample();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Sample 類的局部變量 s2 和引用變量 mSample2 都是存在于棧中,但 mSample2 指向的對(duì)象是存在于堆上的。?
mSample3 指向的對(duì)象實(shí)體存放在堆上,包括這個(gè)對(duì)象的所有成員變量 s1 和 mSample1,而它自己存在于棧中。

結(jié)論:

局部變量的基本數(shù)據(jù)類型和引用存儲(chǔ)于棧中,引用的對(duì)象實(shí)體存儲(chǔ)于堆中。—— 因?yàn)樗鼈儗儆诜椒ㄖ械淖兞?#xff0c;生命周期隨方法而結(jié)束。

成員變量全部存儲(chǔ)與堆中(包括基本數(shù)據(jù)類型,引用和引用的對(duì)象實(shí)體)—— 因?yàn)樗鼈儗儆陬?#xff0c;類對(duì)象終究是要被new出來使用的。

了解了 Java 的內(nèi)存分配之后,我們?cè)賮砜纯?Java 是怎么管理內(nèi)存的。

Java是如何管理內(nèi)存

Java的內(nèi)存管理就是對(duì)象的分配和釋放問題。在 Java 中,程序員需要通過關(guān)鍵字 new 為每個(gè)對(duì)象申請(qǐng)內(nèi)存空間 (基本類型除外),所有的對(duì)象都在堆 (Heap)中分配空間。另外,對(duì)象的釋放是由 GC 決定和執(zhí)行的。在 Java 中,內(nèi)存的分配是由程序完成的,而內(nèi)存的釋放是由 GC 完成的,這種收支兩條線的方法確實(shí)簡(jiǎn)化了程序員的工作。但同時(shí),它也加重了JVM的工作。這也是 Java 程序運(yùn)行速度較慢的原因之一。因?yàn)?#xff0c;GC 為了能夠正確釋放對(duì)象,GC 必須監(jiān)控每一個(gè)對(duì)象的運(yùn)行狀態(tài),包括對(duì)象的申請(qǐng)、引用、被引用、賦值等,GC 都需要進(jìn)行監(jiān)控。

監(jiān)視對(duì)象狀態(tài)是為了更加準(zhǔn)確地、及時(shí)地釋放對(duì)象,而釋放對(duì)象的根本原則就是該對(duì)象不再被引用。

為了更好理解 GC 的工作原理,我們可以將對(duì)象考慮為有向圖的頂點(diǎn),將引用關(guān)系考慮為圖的有向邊,有向邊從引用者指向被引對(duì)象。另外,每個(gè)線程對(duì)象可以作為一個(gè)圖的起始頂點(diǎn),例如大多程序從 main 進(jìn)程開始執(zhí)行,那么該圖就是以 main 進(jìn)程頂點(diǎn)開始的一棵根樹。在這個(gè)有向圖中,根頂點(diǎn)可達(dá)的對(duì)象都是有效對(duì)象,GC將不回收這些對(duì)象。如果某個(gè)對(duì)象 (連通子圖)與這個(gè)根頂點(diǎn)不可達(dá)(注意,該圖為有向圖),那么我們認(rèn)為這個(gè)(這些)對(duì)象不再被引用,可以被 GC 回收。?
以下,我們舉一個(gè)例子說明如何用有向圖表示內(nèi)存管理。對(duì)于程序的每一個(gè)時(shí)刻,我們都有一個(gè)有向圖表示JVM的內(nèi)存分配情況。以下右圖,就是左邊程序運(yùn)行到第6行的示意圖。

Java使用有向圖的方式進(jìn)行內(nèi)存管理,可以消除引用循環(huán)的問題,例如有三個(gè)對(duì)象,相互引用,只要它們和根進(jìn)程不可達(dá)的,那么GC也是可以回收它們的。這種方式的優(yōu)點(diǎn)是管理內(nèi)存的精度很高,但是效率較低。另外一種常用的內(nèi)存管理技術(shù)是使用計(jì)數(shù)器,例如COM模型采用計(jì)數(shù)器方式管理構(gòu)件,它與有向圖相比,精度行低(很難處理循環(huán)引用的問題),但執(zhí)行效率很高。

什么是Java中的內(nèi)存泄露

在Java中,內(nèi)存泄漏就是存在一些被分配的對(duì)象,這些對(duì)象有下面兩個(gè)特點(diǎn),首先,這些對(duì)象是可達(dá)的,即在有向圖中,存在通路可以與其相連;其次,這些對(duì)象是無用的,即程序以后不會(huì)再使用這些對(duì)象。如果對(duì)象滿足這兩個(gè)條件,這些對(duì)象就可以判定為Java中的內(nèi)存泄漏,這些對(duì)象不會(huì)被GC所回收,然而它卻占用內(nèi)存。

在C++中,內(nèi)存泄漏的范圍更大一些。有些對(duì)象被分配了內(nèi)存空間,然后卻不可達(dá),由于C++中沒有GC,這些內(nèi)存將永遠(yuǎn)收不回來。在Java中,這些不可達(dá)的對(duì)象都由GC負(fù)責(zé)回收,因此程序員不需要考慮這部分的內(nèi)存泄露。

通過分析,我們得知,對(duì)于C++,程序員需要自己管理邊和頂點(diǎn),而對(duì)于Java程序員只需要管理邊就可以了(不需要管理頂點(diǎn)的釋放)。通過這種方式,Java提高了編程的效率。

因此,通過以上分析,我們知道在Java中也有內(nèi)存泄漏,但范圍比C++要小一些。因?yàn)镴ava從語言上保證,任何對(duì)象都是可達(dá)的,所有的不可達(dá)對(duì)象都由GC管理。

對(duì)于程序員來說,GC基本是透明的,不可見的。雖然,我們只有幾個(gè)函數(shù)可以訪問GC,例如運(yùn)行GC的函數(shù)System.gc(),但是根據(jù)Java語言規(guī)范定義, 該函數(shù)不保證JVM的垃圾收集器一定會(huì)執(zhí)行。因?yàn)?#xff0c;不同的JVM實(shí)現(xiàn)者可能使用不同的算法管理GC。通常,GC的線程的優(yōu)先級(jí)別較低。JVM調(diào)用GC的策略也有很多種,有的是內(nèi)存使用到達(dá)一定程度時(shí),GC才開始工作,也有定時(shí)執(zhí)行的,有的是平緩執(zhí)行GC,有的是中斷式執(zhí)行GC。但通常來說,我們不需要關(guān)心這些。除非在一些特定的場(chǎng)合,GC的執(zhí)行影響應(yīng)用程序的性能,例如對(duì)于基于Web的實(shí)時(shí)系統(tǒng),如網(wǎng)絡(luò)游戲等,用戶不希望GC突然中斷應(yīng)用程序執(zhí)行而進(jìn)行垃圾回收,那么我們需要調(diào)整GC的參數(shù),讓GC能夠通過平緩的方式釋放內(nèi)存,例如將垃圾回收分解為一系列的小步驟執(zhí)行,Sun提供的HotSpot JVM就支持這一特性。

同樣給出一個(gè) Java 內(nèi)存泄漏的典型例子,

Vector v = new Vector(10); for (int i = 1; i < 100; i++) {Object o = new Object();v.add(o);o = null; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在這個(gè)例子中,我們循環(huán)申請(qǐng)Object對(duì)象,并將所申請(qǐng)的對(duì)象放入一個(gè) Vector 中,如果我們僅僅釋放引用本身,那么 Vector 仍然引用該對(duì)象,所以這個(gè)對(duì)象對(duì) GC 來說是不可回收的。因此,如果對(duì)象加入到Vector 后,還必須從 Vector 中刪除,最簡(jiǎn)單的方法就是將 Vector 對(duì)象設(shè)置為 null。

詳細(xì)Java中的內(nèi)存泄漏

1.Java內(nèi)存回收機(jī)制

不論哪種語言的內(nèi)存分配方式,都需要返回所分配內(nèi)存的真實(shí)地址,也就是返回一個(gè)指針到內(nèi)存塊的首地址。Java中對(duì)象是采用new或者反射的方法創(chuàng)建的,這些對(duì)象的創(chuàng)建都是在堆(Heap)中分配的,所有對(duì)象的回收都是由Java虛擬機(jī)通過垃圾回收機(jī)制完成的。GC為了能夠正確釋放對(duì)象,會(huì)監(jiān)控每個(gè)對(duì)象的運(yùn)行狀況,對(duì)他們的申請(qǐng)、引用、被引用、賦值等狀況進(jìn)行監(jiān)控,Java會(huì)使用有向圖的方法進(jìn)行管理內(nèi)存,實(shí)時(shí)監(jiān)控對(duì)象是否可以達(dá)到,如果不可到達(dá),則就將其回收,這樣也可以消除引用循環(huán)的問題。在Java語言中,判斷一個(gè)內(nèi)存空間是否符合垃圾收集標(biāo)準(zhǔn)有兩個(gè):一個(gè)是給對(duì)象賦予了空值null,以下再?zèng)]有調(diào)用過,另一個(gè)是給對(duì)象賦予了新值,這樣重新分配了內(nèi)存空間。

2.Java內(nèi)存泄漏引起的原因

內(nèi)存泄漏是指無用對(duì)象(不再使用的對(duì)象)持續(xù)占有內(nèi)存或無用對(duì)象的內(nèi)存得不到及時(shí)釋放,從而造成內(nèi)存空間的浪費(fèi)稱為內(nèi)存泄漏。內(nèi)存泄露有時(shí)不嚴(yán)重且不易察覺,這樣開發(fā)者就不知道存在內(nèi)存泄露,但有時(shí)也會(huì)很嚴(yán)重,會(huì)提示你Out of memory。

Java內(nèi)存泄漏的根本原因是什么呢?長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用就很可能發(fā)生內(nèi)存泄漏,盡管短生命周期對(duì)象已經(jīng)不再需要,但是因?yàn)殚L(zhǎng)生命周期持有它的引用而導(dǎo)致不能被回收,這就是Java中內(nèi)存泄漏的發(fā)生場(chǎng)景。具體主要有如下幾大類:

1、靜態(tài)集合類引起內(nèi)存泄漏:

像HashMap、Vector等的使用最容易出現(xiàn)內(nèi)存泄露,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,他們所引用的所有的對(duì)象Object也不能被釋放,因?yàn)樗麄円矊⒁恢北籚ector等引用著。

例如

Static Vector v = new Vector(10); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在這個(gè)例子中,循環(huán)申請(qǐng)Object 對(duì)象,并將所申請(qǐng)的對(duì)象放入一個(gè)Vector 中,如果僅僅釋放引用本身(o=null),那么Vector 仍然引用該對(duì)象,所以這個(gè)對(duì)象對(duì)GC 來說是不可回收的。因此,如果對(duì)象加入到Vector 后,還必須從Vector 中刪除,最簡(jiǎn)單的方法就是將Vector對(duì)象設(shè)置為null。

2、當(dāng)集合里面的對(duì)象屬性被修改后,再調(diào)用remove()方法時(shí)不起作用。

例如:

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ā)生改變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); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3、監(jiān)聽器

在java 編程中,我們都需要和監(jiān)聽器打交道,通常一個(gè)應(yīng)用當(dāng)中會(huì)用到很多監(jiān)聽器,我們會(huì)調(diào)用一個(gè)控件的諸如addXXXListener()等方法來增加監(jiān)聽器,但往往在釋放對(duì)象的時(shí)候卻沒有記住去刪除這些監(jiān)聽器,從而增加了內(nèi)存泄漏的機(jī)會(huì)。

4、各種連接

比如數(shù)據(jù)庫連接(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í)候都無法自動(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ì)象無法釋放,從而引起內(nèi)存泄漏。這種情況下一般都會(huì)在try里面去的連接,在finally里面釋放連接。

5、內(nèi)部類和外部模塊的引用

內(nèi)部類的引用是比較容易遺忘的一種,而且一旦沒釋放可能導(dǎo)致一系列的后繼類對(duì)象沒有釋放。此外程序員還要小心外部模塊不經(jīng)意的引用,例如程序員A 負(fù)責(zé)A 模塊,調(diào)用了B 模塊的一個(gè)方法如:?
public void registerMsg(Object b);?
這種調(diào)用就要非常小心了,傳入了一個(gè)對(duì)象,很可能模塊B就保持了對(duì)該對(duì)象的引用,這時(shí)候就需要注意模塊B 是否提供相應(yīng)的操作去除引用。

6、單例模式

不正確使用單例模式是引起內(nèi)存泄漏的一個(gè)常見問題,單例對(duì)象在初始化后將在JVM的整個(gè)生命周期中存在(以靜態(tài)變量的方式),如果單例對(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... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

顯然B采用singleton模式,它持有一個(gè)A對(duì)象的引用,而這個(gè)A類的對(duì)象將不能被回收。想象下如果A是個(gè)比較復(fù)雜的對(duì)象或者集合類型會(huì)發(fā)生什么情況。

總結(jié)

以上是生活随笔為你收集整理的java内存泄漏总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。