❶ 如何定位java內存泄露
1、為什麼會發生內存泄漏
Java如何檢測內在泄漏呢?我們需要一些工具進行檢測,並發現內存泄漏問題,不然很容易發生down機問題。
編寫java程序最為方便的地方就是我們不需要管理內存的分配和釋放,一切由jvm來進行處理,當java對象不再被應用時,等到堆內存不夠用時,jvm會進行垃圾回收,清除這些對象佔用的堆內存空間,如果對象一直被應用,jvm無法對其進行回收,創建新的對象時,無法從Heap中獲取足夠的內存分配給對象,這時候就會導致內存溢出。而出現內存泄露的地方,一般是不斷的往容器中存放對象,而容器沒有相應的大小限制或清除機制。容易導致內存溢出。
當伺服器應用佔用了過多內存的時候,如何快速定位問題呢?現在,Eclipse MAT的出現使這個問題變得非常簡單。EclipseMAT是著名的SAP公司貢獻的一個工具,可以在Eclipse網站下載到它,完全免費的。
要定位問題,首先你需要獲取伺服器jvm某刻內存快照。jdk自帶的jmap可以獲取內存某一時刻的快照,導出為dmp文件後,就可以用Eclipse MAT來分析了,找出是那個對象使用內存過多。
2、內存泄漏的現象:
常常地,程序內存泄漏的最初跡象發生在出錯之後,在你的程序中得到一個OutOfMemoryError。這種典型的情況發生在產品環境中,而在那裡,你希望內存泄漏盡可能的少,調試的可能性也達到最小。也許你的測試環境和產品的系統環境不盡相同,導致泄露的只會在產品中暴露。這種情況下,你需要一個低負荷的工具來監聽和尋找內存泄漏。同時,你還需要把這個工具同你的系統聯系起來,而不需要重新啟動他或者機械化你的代碼。也許更重要的是,當你做分析的時候,你需要能夠同工具分離而使得系統不會受到干擾。
一個OutOfMemoryError常常是內存泄漏的一個標志,有可能應用程序的確用了太多的內存;這個時候,你既不能增加JVM的堆的數量,也不能改變你的程序而使得他減少內存使用。但是,在大多數情況下,一個OutOfMemoryError是內存泄漏的標志。一個解決辦法就是繼續監聽GC的活動,看看隨時間的流逝,內存使用量是否會增加,如果有,程序中一定存在內存泄漏。
3、發現內存泄漏
1. jstat -gc pid
可以顯示gc的信息,查看gc的次數,及時間。
其中最後五項,分別是young gc的次數,young gc的時間,full gc的次數,full gc的時間,gc的總時間。
2.jstat -gccapacity pid
可以顯示,VM內存中三代(young,old,perm)對象的使用和佔用大小,
如:PGCMN顯示的是最小perm的內存使用量,PGCMX顯示的是perm的內存最大使用量,
PGC是當前新生成的perm內存佔用量,PC是但前perm內存佔用量。
其他的可以根據這個類推,OC是old內純的佔用量。
3.jstat -gcutil pid
統計gc信息統計。
4.jstat -gcnew pid
年輕代對象的信息。
5.jstat -gcnewcapacity pid
年輕代對象的信息及其佔用量。
6.jstat -gcold pid
old代對象的信息。
7.stat -gcoldcapacity pid
old代對象的信息及其佔用量。
8.jstat -gcpermcapacity pid
perm對象的信息及其佔用量。
9.jstat -class pid
顯示載入class的數量,及所佔空間等信息。
10.jstat -compiler pid
顯示VM實時編譯的數量等信息。
11.stat -printcompilation pid
當前VM執行的信息。
一些術語的中文解釋:
S0C:年輕代中第一個survivor(倖存區)的容量(位元組)
S1C:年輕代中第二個survivor(倖存區)的容量(位元組)
S0U:年輕代中第一個survivor(倖存區)目前已使用空間(位元組)
S1U:年輕代中第二個survivor(倖存區)目前已使用空間(位元組)
EC:年輕代中Eden(伊甸園)的容量(位元組)
EU:年輕代中Eden(伊甸園)目前已使用空間(位元組)
OC:Old代的容量(位元組)
OU:Old代目前已使用空間(位元組)
PC:Perm(持久代)的容量(位元組)
PU:Perm(持久代)目前已使用空間(位元組)
YGC:從應用程序啟動到采樣時年輕代中gc次數
YGCT:從應用程序啟動到采樣時年輕代中gc所用時間(s)
FGC:從應用程序啟動到采樣時old代(全gc)gc次數
FGCT:從應用程序啟動到采樣時old代(全gc)gc所用時間(s)
GCT:從應用程序啟動到采樣時gc用的總時間(s)
NGCMN:年輕代(young)中初始化(最小)的大小(位元組)
NGCMX:年輕代(young)的最大容量(位元組)
NGC:年輕代(young)中當前的容量(位元組)
OGCMN:old代中初始化(最小)的大小(位元組)
OGCMX:old代的最大容量(位元組)
OGC:old代當前新生成的容量(位元組)
PGCMN:perm代中初始化(最小)的大小(位元組)
PGCMX:perm代的最大容量(位元組)
PGC:perm代當前新生成的容量(位元組)
S0:年輕代中第一個survivor(倖存區)已使用的占當前容量百分比
S1:年輕代中第二個survivor(倖存區)已使用的占當前容量百分比
E:年輕代中Eden(伊甸園)已使用的占當前容量百分比
O:old代已使用的占當前容量百分比
P:perm代已使用的占當前容量百分比
S0CMX:年輕代中第一個survivor(倖存區)的最大容量(位元組)
S1CMX:年輕代中第二個survivor(倖存區)的最大容量(位元組)
ECMX:年輕代中Eden(伊甸園)的最大容量(位元組)
DSS:當前需要survivor(倖存區)的容量(位元組)(Eden區已滿)
TT:持有次數限制
MTT:最大持有次數限制
如果定位內存泄漏問題我一般使用如下命令:
Jstat -gcutil15469 2500 70
其中深藍色的部分就為內存泄漏的部分,java的堆內存一共只有481.5M而內存泄漏的部分獨自佔有了336.2M所以本次的內存泄漏很明顯,那麼我就來看看那個方法導致的內存泄漏:
從上圖我們可以發現紅線圈著的方法佔用了堆內存的67.75%,如果能把這個測試結果交給開發,開發是不是應該很好定位呢。所以作為一名高級測試工程師,我們需要學習的東西太多。
雖然不確定一定是內存泄漏,但是可以准確的告訴開發問題出現的原因,有一定的說服力。
❷ java中內存泄露有幾種如何分析泄露原因
一、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; i<100; 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)
{
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()+" 個元素!"); //結果:總共有: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是個比較復雜的對象或者集合類型會發生什麼情況
❸ 詳解Java語言中內存泄漏及如何檢測問題 (1)
一般來說內存泄漏有兩種情況。一種情況,在堆中的分配的內存,在沒有將其釋放掉的時候,就將所有能訪問這塊內存的方式都刪掉(如指針重新賦值);另一種情況則是在內存對象明明已經不需要的時候,還仍然保留著這塊內存和它的訪問方式(引用)。第一種情況,在Java中已經由於垃圾回收機制的引入,得到了很好的解決。所以,Java中的內存泄漏,主要指的是第二種情況。
可能光說概念太抽象了,大家可以看一下這樣的例子:
1 Vector v=new Vector(10);
2 for (int i=1;i<100; 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機制,就可以達到一些我們需要的效果。