深入聊聊Java內(nèi)存泄露問題
Java內(nèi)存泄露問題
所謂內(nèi)存泄露就是指一個不再被程序便用的對象或變量一直被占據(jù)在內(nèi)存中。
Java 中有垃圾回收機(jī)制,它可以保證一對象不再被引用的時候,即對象變成了孤兒的時候,對象將自動被垃圾回收器從內(nèi)存中清除掉。
既然java有垃圾回收機(jī)制,為什么還會存在內(nèi)存泄漏的問題呢?
無非,就是有些對象,無法被垃圾回收器處理,導(dǎo)致這些對象一直占用JVM內(nèi)存,那不就導(dǎo)致內(nèi)存泄漏了嘛。
由于 Java 使用有向圖的方式進(jìn)行垃圾回收管理,可以消除引用循環(huán)的問題 ,例如有兩個對象 ,相互引用, 只要它們和根進(jìn)程不可達(dá)的,那么GC也是可以回收它們的,例如下面的代碼可以看到這種情況的內(nèi)存回收。
import java. io.IOException;
public class GarbageTest {
public static void main(String[] args) throws IOException {
try {
// TODO Auto-generated method stub
gcTest();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("has exited gcTest!");
System.in.read();
System.in.read();
System.out.println("out begin gc!");
for (int i = 0; i < 100; i++) {
System.gc();
System.in.read();
System.in.read();
}
}
private static void gcTest() throws IOException {
System.in.read();
System.in.read();
Person p1 = new Person();
System.in.read();
System.in.read();
Person p2 = new Person();
p1.setMate(p2);
p2.setMate(p1);
System.out.println("before exit gctest!");
System.in.read();
System.in.read();
System.gc();
System.out.println("exit gctest!");
}
private static class Person {
byte[] data = new byte[20000000];
Person mate = null;
public void setMate(Person other) {
mate = other;
}
}
}Java 中的內(nèi)存泄露的情況: 長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內(nèi)存泄露,盡管短室命周期對象已經(jīng)不再需要,但是因為長生命周期對象持有它的引用而導(dǎo)致不能被回收,這就是 Java 中內(nèi)存泄露的發(fā)室場景 ,通俗地說,就是程序員可能創(chuàng)建了一個對象,以后一直不再使用這個對象,這個對象卻一直被引用,即這個對象無用但是卻無法被垃圾回收器回收的,這就是 java 中可能出現(xiàn)內(nèi)存泄露的情況。
例如,緩存系統(tǒng),我們加載了一個對象放在緩存中 (例如放在一個全局map對象中),然后一直不再使用它,這個對象一值被緩存引用, 但卻不再被便用。
檢查 Java 中的內(nèi)存泄露,一定要讓程序?qū)⒏鞣N分支情況都完整執(zhí)行到程序結(jié)束,然后看某個對象是否被使用過,如果沒有,則才能判定這個對象屬于內(nèi)存泄露。
如果一個外部類的實例對象的方法返回了一個內(nèi)部類的實例對象,這個內(nèi)部類對象被長期引用了,即使那個外部類實例對象不再被使用,但由于內(nèi)部類持久外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會造成內(nèi)存泄露.
下面內(nèi)容來自于網(wǎng)上 (主要特點就是清空堆棧中的某個元素,并不是徹底把它從數(shù)組中拿掉,而是把存儲的總數(shù)減少,本人寫得可以比這個好,在拿掉某個元素時,順便也讓它從數(shù)組中消失,將那個元素所在的位置的值設(shè)置為 null 即可)
我實在想不到比那個堆棧更經(jīng)典的例子了,以致于我還要引用別人的例子,下面的例子不是我想到的,是書上看到的 ,當(dāng)然如果沒有在書上看到,可能過一段時間我自己也想的到,可是那時我說是我自己想到的也沒有人相信的。
public class Stack {
private Object[] elements = new Object[10];
private int size = 0;
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity() {
if (elements.length == size) {
Object[] oldElements = elements;
elements = new Object[2 * elements.length + 1];
System.arraycopy(oldElements, 0, elements, 0, size);
}
}
}上面的原理應(yīng)該很簡單,假如堆錢加了10 個元素 ,然后全部彈出來 ,雖然堆錢是空的,沒有我們要的東西,但是這是個對象是無法回收的,這個才符合了內(nèi)存泄露的兩個條件 無用,無法回收。 但是就是存在這樣的東西也不一定會導(dǎo)致什么樣的后果 ,如果這個堆錢用的比較少,也就浪費了幾個 K 內(nèi)存而己,反正我們 的內(nèi)存都上 G 了,哪里會有什么影響,再說這個東西很快就會被回收的,有什么關(guān)系。 下面看兩個例子。
class Bad {
public static Stack s = new Stack();
static {
s.push(new Object());
s.pop(); //這里有一個對象發(fā)生內(nèi)存泄露
s.push(new Object());//上面的對象可以被回收了,等于是自愈了
}
}因為是 static ,就一直存在 到程序退出,但是我們也可以看到它有自愈功能,就是說如果你的 Stack 最多有 100 個對象,那么最多也就只有 100 個對象無法 被回收, 其實這個應(yīng)該很容易理解,Stack 內(nèi)部持有 100 個引用,最壞的情況就是 他們都是無用的,因為我們一旦放新的進(jìn)取,以前的引用自然消失!
內(nèi)存泄露的另外一種情況: 當(dāng)一個對象被存儲進(jìn) HashSet 集合中以后,就不能修改這個對象中的那些參與計算哈希值的字段了,否則,對象修改后的晗希值與 最初存儲進(jìn) HashSet 集合中時的哈希值就不同了,在這種情況下,即使在 contains 方法使用該對象的當(dāng)前引用作為的參數(shù)去 HashSet 集合中檢索對象, 也將返回找不到對象的結(jié)果,這也會導(dǎo)致無法從 HashSet 集合中單獨刪除當(dāng)前對象,造成內(nèi)存泄露。
附:內(nèi)存泄露的典型情況
(1) 數(shù)據(jù)結(jié)構(gòu)造成的短暫內(nèi)存泄露問題,看下面的代碼
public class Stack{
private Object[] element=new Object[10];
private int size=0;
public void push(Object ele){
ensureCapacity();
element[size++]=ele;
}
public Object pop(){
if(size==0) throw new EmptyStackException();
return element[--size]; //短暫造成內(nèi)存泄露
}
private void ensureCapacity(){
if(element.length==size){
Object[] oldElement=element;
element=new Object[size*2+1];
System.arraycopy(oldElement,0,element,0,size);
}
}
}
上面的代碼每一次pop()的時候,Stack都會彈出一個元素,在沒有加入新元素之前,實際上仍然有一個引用element[x]指向了這個已經(jīng)彈出的對象,因此GC是不會對其進(jìn)行垃圾回收的。只有push()新元素的時候使得element[x]=newObject,才會使得以前創(chuàng)建的對象有可能被回收。應(yīng)該把上面的pop()方法改成下面的代碼就安全多了:
public Object pop(){
if(element.length==size) throws EmptyStackException();
Object o=element[--size];
elements[size]=null; //使得GC有機(jī)會回收這個對象
return o;
} 總結(jié)
到此這篇關(guān)于Java內(nèi)存泄露問題的文章就介紹到這了,更多相關(guān)Java內(nèi)存泄露問題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中數(shù)學(xué)相關(guān)類的使用教程
Java是一種廣泛使用的編程語言,它提供了許多數(shù)學(xué)運算的函數(shù)和方法,使得開發(fā)者可以輕松地進(jìn)行各種數(shù)學(xué)計算,下面這篇文章主要給大家介紹了關(guān)于Java中數(shù)學(xué)相關(guān)類使用的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
java對接微信小程序詳細(xì)流程(登錄&獲取用戶信息)
這篇文章主要給大家介紹了關(guān)于java對接微信小程序(登錄&獲取用戶信息)的相關(guān)資料,我們在開發(fā)微信小程序時經(jīng)常需要獲取用戶微信用戶名以及頭像信息,微信提供了專門的接口API用于返回這些信息,需要的朋友可以參考下2023-08-08
Jenkins一鍵打包部署SpringBoot應(yīng)用
本文主要介紹了Jenkins一鍵打包部署SpringBoot應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
RabbitMQ?延遲隊列實現(xiàn)訂單支付結(jié)果異步階梯性通知(實例代碼)
這篇文章主要介紹了RabbitMQ?延遲隊列實現(xiàn)訂單支付結(jié)果異步階梯性通知,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-02-02

