- 1、詳解Java語言中內存泄漏及如何檢測問題 (1)
- 2、java內存泄漏怎麼處理
- 3、java某個類出現內存泄露怎麼辦
- 4、Java 內存泄露問題
- 5、java程序會發生內存泄露的問題嗎?請簡單說說你的觀點
一般來說內存泄漏有兩種情況。一種情況,在堆中的分配的內存,在沒有將其釋放掉的時候,就將所有能訪問這塊內存的方式都刪掉(如指針重新賦值);另一種情況則是在內存對象明明已經不需要的時候,還仍然保留著這塊內存和它的訪問方式(引用)。第一種情況,在Java中已經由於垃圾回收機制的引入,得到了很好的解決。所以,Java中的內存泄漏,主要指的是第二種情況。
可能光說概念太抽象了,大家可以看一下這樣的例子:
1 Vector v=new Vector(10);
2 for (int i=1;i100; i++){
3 Object o=new Object();
4 v.add(o);
5 o=null;
6 }
在這個例子中,代碼棧中存在Vector對象的引用v和Object對象的引用o。在For循環中,我們不斷的生成新的對象,然後將其添加到Vector對象中,之後將o引用置空。問題是當o引用被置空後,如果發生GC,我們創建的Object對象是否能夠被GC回收呢?答案是否定的。因為,GC在跟蹤代碼棧中的引用時,會發現v引用,而繼續往下跟蹤,就會發現v引用指向的內存空間中又存在指向Object對象的引用。也就是說儘管o引用已經被置空,但是Object對象仍然存在其他的引用,是可以被訪問到的,所以GC無法將其釋放掉。如果在此循環之後,Object對象對程序已經沒有任何作用,那麼我們就認為此Java程序發生了內存泄漏。
儘管對於C/C++中的內存泄露情況來說,Java內存泄露導致的破壞性小,除了少數情況會出現程序崩潰的情況外,大多數情況下程序仍然能正常運行。但是,在移動設備對於內存和CPU都有較嚴格的限制的情況下,Java的內存溢出會導致程序效率低下、佔用大量不需要的內存等問題。這將導致整個機器性能變差,嚴重的也會引起拋出OutOfMemoryError,導致程序崩潰。
一般情況下內存泄漏的避免
在不涉及複雜數據結構的一般情況下,Java的內存泄露表現為一個內存對象的生命周期超出了程序需要它的時間長度。我們有時也將其稱為「對象遊離」。
例如:
1 public class FileSearch{
2
3 private byte[] content;
4 private File mFile;
5
6 public FileSearch(File file){
7 mFile = file;
8 }
9
10 public boolean hasString(String str){
11 int size = getFileSize(mFile);
12 content = new byte[size];
13 loadFile(mFile, content);
14
15 String s = new String(content);
16 return s.contains(str);
17 }
18 }
在這段代碼中,FileSearch類中有一個函數hasString,用來判斷文檔中是否含有指定的字元串。流程是先將mFile載入到內存中,然後進行判斷。但是,這裡的問題是,將content聲明為了實例變數,而不是本地變數。於是,在此函數返回之後,內存中仍然存在整個文件的數據。而很明顯,這些數據我們後續是不再需要的,這就造成了內存的無故浪費。
要避免這種情況下的內存泄露,要求我們以C/C++的內存管理思維來管理自己分配的內存。第一,是在聲明對象引用之前,明確內存對象的有效作用域。在一個函數內有效的內存對象,應該聲明為local變數,與類實例生命周期相同的要聲明為實例變數……以此類推。第二,在內存對象不再需要時,記得手動將其引用置空。
複雜數據結構中的內存泄露問題
在實際的項目中,我們經常用到一些較為複雜的數據結構用於緩存程序運行過程中需要的數據信息。有時,由於數據結構過於複雜,或者我們存在一些特殊的需求(例如,在內存允許的情況下,儘可能多的緩存信息來提高程序的運行速度等情況),我們很難對數據結構中數據的生命周期作出明確的界定。這個時候,我們可以使用Java中一種特殊的機制來達到防止內存泄露的目的。
之前我們介紹過,Java的GC機制是建立在跟蹤內存的引用機制上的。而在此之前,我們所使用的引用都只是定義一個「Object o;」這樣形式的。事實上,這只是Java引用機制中的一種默認情況,除此之外,還有其他的一些引用方式。通過使用這些特殊的引用機制,配合GC機制,就可以達到一些我們需要的效果。
一、Java內存回收機制
不論哪種語言的內存分配方式,都需要返回所分配內存的真實地址,也就是返回一個指針到內存塊的首地址。Java中對象是採用new或者反射的方法創建的,這些對象的創建都是在堆(Heap)中分配的,所有對象的回收都是由Java虛擬機通過垃圾回收機制完成的。GC為了能夠正確釋放對象,會監控每個對象的運行狀況,對他們的申請、引用、被引用、賦值等狀況進行監控,Java會使用有向圖的方法進行管理內存,實時監控對象是否可以達到,如果不可到達,則就將其回收,這樣也可以消除引用循環的問題。在Java語言中,判斷一個內存空間是否符合垃圾收集標準有兩個:一個是給對象賦予了空值null,以下再沒有調用過,另一個是給對象賦予了新值,這樣重新分配了內存空間。
二、Java內存泄露引起原因
首先,什麼是內存泄露看經常聽人談起內存泄露,但要問什麼是內存泄露,沒幾個說得清楚。內存泄露是指無用對象(不再使用的對象)持續佔有內存或無用對象的內存得不到及時釋放,從而造成的內存空間的浪費稱為內存泄露。內存泄露有時不嚴重且不易察覺,這樣開發者就不知道存在內存泄露,但有時也會很嚴重,會提示你Out of memory。
那麼,Java內存泄露根本原因是什麼呢看長生命周期的對象持有短生命周期對象的引用就很可能發生內存泄露,儘管短生命周期對象已經不再需要,但是因為長生命周期對象持有它的引用而導致不能被回收,這就是java中內存泄露的發生場景。具體主要有如下幾大類:
1、靜態集合類引起內存泄露:
像HashMap、Vector等的使用最容易出現內存泄露,這些靜態變數的生命周期和應用程序一致,他們所引用的所有的對象Object也不能被釋放,因為他們也將一直被Vector等引用著。
例:
Static Vector v = new Vector(10);
for (int i = 1; i100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}//
在這個例子中,循環申請Object 對象,並將所申請的對象放入一個Vector 中,如果僅僅釋放引用本身(o=null),那麼Vector 仍然引用該對象,所以這個對象對GC 來說是不可回收的。因此,如果對象加入到Vector 後,還必須從Vector 中刪除,最簡單的方法就是將Vector對象設置為null。
2、當集合裡面的對象屬性被修改後,再調用remove()方法時不起作用。
例:
public static void main(String[] args)
{
SetPerson set = new HashSetPerson();
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()+” 個元素!”); //結果:總共有:3 個元素!
p3.setAge(2); //修改p3的年齡,此時p3元素對應的hashcode值發生改變
set.remove(p3); //此時remove不掉,造成內存泄漏
set.add(p3); //重新添加,居然添加成功
System.out.println(“總共有:”+set.size()+” 個元素!”); //結果:總共有:4 個元素!
for (Person person : set)
{
System.out.println(person);
}
}
3、監聽器
在java 編程中,我們都需要和監聽器打交道,通常一個應用當中會用到很多監聽器,我們會調用一個控制項的諸如addXXXListener()等方法來增加監聽器,但往往在釋放對象的時候卻沒有記住去刪除這些監聽器,從而增加了內存泄漏的機會。
4、各種連接
比如資料庫連接(dataSourse.getConnection()),網路連接(socket)和io連接,除非其顯式的調用了其close()方法將其連接關閉,否則是不會自動被GC 回收的。對於Resultset 和Statement 對象可以不進行顯式回收,但Connection 一定要顯式回收,因為Connection 在任何時候都無法自動回收,而Connection一旦回收,Resultset 和Statement 對象就會立即為NULL。但是如果使用連接池,情況就不一樣了,除了要顯式地關閉連接,還必須顯式地關閉Resultset Statement 對象(關閉其中一個,另外一個也會關閉),否則就會造成大量的Statement 對象無法釋放,從而引起內存泄漏。這種情況下一般都會在try裡面去的連接,在finally裡面釋放連接。
5、內部類和外部模塊等的引用
內部類的引用是比較容易遺忘的一種,而且一旦沒釋放可能導致一系列的後繼類對象沒有釋放。此外程序員還要小心外部模塊不經意的引用,例如程序員A 負責A 模塊,調用了B 模塊的一個方法如:
public void registerMsg(Object b);
這種調用就要非常小心了,傳入了一個對象,很可能模塊B就保持了對該對象的引用,這時候就需要注意模塊B 是否提供相應的操作去除引用。
6、單例模式
不正確使用單例模式是引起內存泄露的一個常見問題,單例對象在被初始化後將在JVM的整個生命周期中存在(以靜態變數的方式),如果單例對象持有外部對象的引用,那麼這個外部對象將不能被jvm正常回收,導致內存泄露,考慮下面的例子:
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類的對象將不能被回收。想像下如果A是個比較複雜的對象或者集合類型會發生什麼情況
Android 內存泄漏總結
內存管理的目的就是讓我們在開發中怎麼有效的避免我們的應用出現內存泄漏的問題。內存泄漏大家都不陌生了,簡單粗俗的講,就是該被釋放的對象沒有釋放,一直被某個或某些實例所持有卻不再被使用導致 GC 不能回收。最近自己閱讀了大量相關的文檔資料,打算做個 總結 沉澱下來跟大家一起分享和學習,也給自己一個警示,以後 coding 時怎麼避免這些情況,提高應用的體驗和質量。
我會從 java 內存泄漏的基礎知識開始,並通過具體例子來說明 Android 引起內存泄漏的各種原因,以及如何利用工具來分析應用內存泄漏,最後再做總結。
Java 內存分配策略
Java 程序運行時的內存分配策略有三種,分別是靜態分配,棧式分配,和堆式分配,對應的,三種存儲策略使用的內存空間主要分別是靜態存儲區(也稱方法區)、棧區和堆區。
靜態存儲區(方法區):主要存放靜態數據、全局 static 數據和常量。這塊內存在程序編譯時就已經分配好,並且在程序整個運行期間都存在。
棧區 :當方法被執行時,方法體內的局部變數(其中包括基礎數據類型、對象的引用)都在棧上創建,並在方法執行結束時這些局部變數所持有的內存將會自動被釋放。因為棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
堆區 : 又稱動態內存分配,通常就是指在程序運行時直接 new 出來的內存,也就是對象的實例。這部分內存在不使用時將會由 Java 垃圾回收器來負責回收。
棧與堆的區別:
在方法體內定義的(局部變數)一些基本類型的變數和對象的引用變數都是在方法的棧內存中分配的。當在一段方法塊中定義一個變數時,Java 就會在棧中為該變數分配內存空間,當超過該變數的作用域後,該變數也就無效了,分配給它的內存空間也將被釋放掉,該內存空間可以被重新使用。
堆內存用來存放所有由 new 創建的對象(包括該對象其中的所有成員變數)和數組。在堆中分配的內存,將由 Java 垃圾回收器來自動管理。在堆中產生了一個數組或者對象後,還可以在棧中定義一個特殊的變數,這個變數的取值等於數組或者對象在堆內存中的首地址,這個特殊的變數就是我們上面說的引用變數。我們可以通過這個引用變數來訪問堆中的對象或者數組。
看了這個帖子里的回帖的討論,囧大發了。
我覺得似乎並沒有泄露,s和a引用在method return的時候符合回收條件,a指向的堆空間也符合回收條件
“abc”這個對像無論fun調用出現多少次都只有一個空間,每次”abc”的時候,vm會在常量池裡得到它。方法中的任何”abc”方法都是從常量池裡出來的:
例:
String str1 = new String(“abc”);
String str2 = new String(“abc”);
fun(str1);
fun(str2);
void fun(String s) {
System.out.println(“abc”.intern() == s.intern());
}
所以這個地方似乎也沒有泄露。
要說泄露,那只有vm沒有gc掉a指向的堆內存空間,vm一般不會立即銷毀對象。
所以感覺並沒有出現泄露。
個人理解……. 僅供參考。
答案:會。Java內存管理是通過垃圾收集器(Garbage Collection,GC)自動管理內存的回收的,java程序員不需要通過調用函數來釋放內存。因此,很多人錯誤地認為Java不存在內存泄漏問題,或者認為即使有內存泄漏也不是程序的責任,而是GC或JVM的問題。其實Java也存在內存泄露,但它的表現與C++語言有些不同。java導致內存泄露的原因很明確:長生命周期的對象持有短生命周期對象的引用就很可能發生內存泄露,儘管短生命周期對象已經不再需要,但是因為長生命周期對象持有它的引用而導致不能被回收。嚴格來說,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特點,首先,這些對象是可達的,即在有向圖中,存在通路可以與其相連;其次,這些對象是無用的,即程序以後不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定為Java中的內存泄漏,這些對象不會被GC所回收,然而它卻占 用內存。在java程序中容易發生內存泄露的場景:�0�21.集合類,集合類僅僅有添加元素的方法,而沒有相應的刪除機制,導致內存被佔用。這一點其實也不明確,這個集合類如果僅僅是局部變數,根本不會造成內存泄露,在方法棧退出後就沒有引用了會被jvm正常回收。而如果這個集合類是全局性的變數(比如類中的靜態屬性,全局性的map等即有靜態引用或final一直指向它),那麼沒有相應的刪除機制,很可能導致集合所佔用的內存只增不減,因此提供這樣的刪除機制或者定期清除策略非常必要。�0�2�0�22.單例模式。不正確使用單例模式是引起內存泄露的一個常見問題,單例對象在被初始化後將在JVM的整個生命周期中存在(以靜態變數的方式),如果單例對象持有外部對象的引用,那麼這個外部對象將不能被jvm正常回收,導致內存泄露,考慮下面的例子: class A{ public A(){ �0�2B.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類的對象將不能被回收。想像下如果A是個比較大的對象或者集合類型會發生什麼情況。�0�2 所以在Java開發過程中和代碼複審的時候要重點關注那些長生命周期對象:全局性的集合、單例模式的使用、類的static變數等等。在不使用某對象時,顯式地將此對象賦空,遵循誰創建誰釋放的原則,減少內向泄漏發生的機會。
原創文章,作者:B74LD,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/127268.html