今天我們講解一下Jvm記憶體洩漏
記憶體洩漏和記憶體溢出的關系
記憶體洩露:指程式中動态配置設定記憶體給一些臨時對象,但是對象不會被GC所回收,它始終占用記憶體。即被配置設定的對象可達但已無用。
記憶體溢出:指程式運作過程中無法申請到足夠的記憶體而導緻的一種錯誤。記憶體溢出通常發生于OLD段或Perm段垃圾回收後,仍然無記憶體空間容納新的Java對象的情況。
從定義上可以看出記憶體洩露是記憶體溢出的一種誘因,但是不是唯一因素。
可以使用Runtime.getRuntime().freeMemory()進行記憶體洩漏查詢
Runtime.getRuntime().freeMemory()表示目前還有多少空閑記憶體
package com.one.util;
public class Hello {
public static void main(String[] args) {
System.out.println("free記憶體:" + Runtime.getRuntime().freeMemory() / 1024
/ 1024);
String[] aaa = new String[2000000];
for (int i = 0; i < 2000000; i++) {
aaa[i] = new String("aaa");
}
System.out.println("free記憶體:" + Runtime.getRuntime().freeMemory() / 1024 / 1024);
}
}
此時結果如下所示

記憶體洩漏的例子
如果長生命周期的對象持有短生命周期的引用,就很可能會出現記憶體洩露
比如下面的代碼,這裡的object執行個體,其實我們期望它隻作用于method1()方法中,且其他地方不會再用到它,但是,當method1()方法執行完成後,object對象所配置設定的記憶體不會馬上被認為是可以被釋放的對象,隻有在Simple類建立的對象被釋放後才會被釋放,嚴格的說,這就是一種記憶體洩露。
public class Simple {
Object object;
public void method1(){
object = new Object();
//...其他代碼
}
}
怎麼解決上面的問題呢,加上下面的藍色代碼注釋就好了
public class Simple {
Object object;
public void method1(){
object = new Object();
//...其他代碼
// 藍色代碼注釋開始
object = null;
// 藍色代碼注釋結束
}
}
集合裡面的記憶體洩漏
集合裡面的資料都設定成null,但是集合記憶體還是存在的
比如下面的代碼
因為你已經在下面的藍色代碼注釋裡面進行company=null了,是以下面的list集合裡面的資料都是無用的了,但是此時list集合裡面的所有元素都不會進行垃圾回收
package com.four;
import java.util.ArrayList;
import java.util.List;
public class Hello {
public static void main(String[] args) {
List<Company> list = new ArrayList<Company>();
int i=0;
for(int j=0;j<10;j++){
Company company = new Company();
company.setName("ali");
list.add(company);
// 藍色代碼注釋開始
company = null;
// 藍色代碼注釋結束
}
System.gc();
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("已經測試了"+(++i)+"秒");
}
}
}
class Company {
private String name;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("回收Comapny");
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
怎麼解決上面的問題呢,就是把上面的list集合變量也變成null,比如加上下面的紅色代碼注釋
package com.one.util;
import java.util.ArrayList;
import java.util.List;
public class Hello {
public static void main(String[] args) {
List<Company> list = new ArrayList<Company>();
int i = 0;
for (int j = 0; j < 10; j++) {
Company company = new Company();
company.setName("ali");
list.add(company);
// 藍色代碼注釋開始
company = null;
// 藍色代碼注釋結束
}
// 紅色代碼注釋開始
list = null;
// 紅色代碼注釋結束
System.gc();
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("已經測試了" + (++i) + "秒");
}
}
}
class Company {
private String name;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("回收Comapny");
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
此時結果如下所示,可以看出來集合裡面的Company變量都回收了
還有就是使用remove()方法進行移除元素的時候,也可能會造成記憶體洩漏
什麼意思呢,
就比如ArrayList裡面的pop(),如果是下面的寫法就會造成記憶體洩漏,因為下面的elementData[--size]這個元素移除之後,并沒有進行設定成null
public E pop(){
if(size == 0)
return null;
else
return (E) elementData[size];
}
是以上面的代碼應該變成下面這樣,此時注意下面的藍色代碼注釋裡面的size值比下面的紅色代碼注釋裡面的size小1
public E pop(){
if(size == 0)
return null;
else{
// 紅色代碼注釋開始
E e = (E) elementData[--size];
// 紅色代碼注釋結束
// 藍色代碼注釋開始
elementData[size] = null;
// 藍色代碼注釋結束
return e;
}
}
連接配接沒有關閉會洩漏
比如資料庫連接配接(dataSourse.getConnection()),網絡連接配接(socket)和io連接配接,這些連結在使用的時候,除非顯式的調用了其close()方法(或類似方法)将其連接配接關閉,否則是不會自動被GC回收的。其實原因依然是長生命周期對象持有短生命周期對象的引用。是以我們經常在網上看到在連接配接調用結束的時候要進行調用close()進行關閉,這樣可以回收不用的記憶體對象,增加可用記憶體。
能看到這裡的同學,就幫忙點個贊吧,Thanks(・ω・)ノ
原文連結