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

