java內(nèi)存管理關(guān)系及內(nèi)存泄露的原理分析
java內(nèi)存管理關(guān)系及內(nèi)存泄露原理
這可能是最近寫的博客中最接近底層的了。閑言少敘,進(jìn)入正題。
java對象和內(nèi)存的關(guān)系
首先,我們要知道下面幾條真理(自己總結(jié)的)
- 一個完整的建立對象流程是 1聲明對象,2開辟內(nèi)存空間,3將對象和內(nèi)存空間建立聯(lián)系。
- 一個對象只能對應(yīng)一個內(nèi)存空間,一個內(nèi)存空間可以對應(yīng)很多對象
- 回收一個內(nèi)存空間 。如果,這個內(nèi)存空間沒有任何一個對象和他有聯(lián)系。就可以被回收,或者好幾個對象環(huán)形引用,也會被回收
- 對一個對象進(jìn)行操作的時候,是先通過 對象 找到 內(nèi)存空間,然后 對內(nèi)存空間進(jìn)行操作(讀,寫 改 刪)
這是本人總結(jié)出來的四條經(jīng)驗。
特別重要的是,一定要有這種認(rèn)知。不管任何語言,最終都是要物理內(nèi)存上面反映的,對象 和 內(nèi)存空間 是兩個不同的個體。 如果 沒有的話,那么你會發(fā)現(xiàn) 下面將的都是什么??!
創(chuàng)建對象
Stu one; //只聲明 one對象 但是沒有分配內(nèi)存空間
//用new 開辟新的內(nèi)存空間 oneMemory ,調(diào)用構(gòu)造函數(shù)賦值,并將內(nèi)存空間 oneMemory 與 one對象建立聯(lián)系。
one = new Stu("one");
//聲明 two對象 并開辟內(nèi)存 twoMemory 調(diào)用構(gòu)造函數(shù)賦值,并將內(nèi)存空間 twoMemory與 two對象建立聯(lián)系
Stu two = new Stu("two");
//聲明 three對象, 并找到one 對象聯(lián)系的內(nèi)存空間 oneMemory。并將 oneMemory與 three 對象建立聯(lián)系
Stu three = one;
//此時 內(nèi)存空間 oneMemory 與兩個對象有聯(lián)系。一個是 one對象,一個是three對象
System.out.println("three 和 one 是否相等" + (three == one) + " one的哈希值" + one.hashCode() + " three的哈希值" + three.hashCode());
運行結(jié)果

我們可以發(fā)現(xiàn),three對象 和one對象 指向的是同一個內(nèi)存空間oneMemory。這個不就是符合上面所說的第二個真理 如果,我們對one對象進(jìn)行操作,那么產(chǎn)生的影響,也會反映到three對象上。
因為,**他們指向的是同一個內(nèi)存空間,對one對象操作,就是做內(nèi)存空間oneMemory進(jìn)行操作。而three對象指向的也是oneMemory。這個符合上面第四條真理。**例子如下
System.out.println("three 對象的值" + three.getName() + three.hashCode());
//修改one的值,第一步 找到one對象聯(lián)系的內(nèi)存空間 oneMemory , 將內(nèi)存空間oneMemory 中的name值改變
one.setName("change");
//讀取three對象值時候,先找到three對象聯(lián)系的內(nèi)存空間oneMemory,讀取其中的name值
System.out.println("three 對象的值" + three.getName() + three.hashCode());
null的作用
長久以來,我只知道,將一個值復(fù)制成null,那么他就是空的了。但是 完全不知道,為啥。
還是接著上面的例子,看一段代碼;
//讀取three對象值時候,先找到three對象聯(lián)系的內(nèi)存空間oneMemory,讀取其中的name值
System.out.println("three 對象的值 before" + three.getName() + three.hashCode());
/*
此時 如果 我們把one 對象 設(shè)置為null的。 對內(nèi)存空間 oneMemory 是沒有影響的
=null的作用是 將one對象自己本身 對內(nèi)存空間的聯(lián)系去除,并不會影響到內(nèi)存空間和其他對象的聯(lián)系
*/
one = null;
System.out.println("three 對象的值 after" + three.getName() + three.hashCode());
運行結(jié)果

我們會發(fā)現(xiàn) 將one對象賦值為空后,three對象還是和先前一樣。以前一直認(rèn)為null,是將對象和他的內(nèi)存空間清楚。但現(xiàn)在不是!。代碼注釋里面寫的很清楚了。null 并不是清除內(nèi)存空間,他只是把對象自己本身和內(nèi)存空間的聯(lián)系切斷了
內(nèi)存泄露
如果,你明白了上面。那么內(nèi)存泄露對你來說也很簡單了。
再來學(xué)習(xí)一個新概念

上面這么多呢。在本篇文章里面,你只需要記住 被static關(guān)鍵詞修飾的變量,類,方法的生命周期是伴隨整個程序的。就是程序活多久,他們就活多久
接下來看代碼
首先,我們定義一個靜態(tài)集合 staticList ,他是程序一運行,就會被創(chuàng)建好的。程序結(jié)束運行了,它才會被回收。
static List<Stu> staticList = new ArrayList<>();//開辟內(nèi)存空間 listMemory
System.out.println("three 對象的值 after" + three.getName() + three.hashCode());
/*
內(nèi)存泄露 是長生命周期的對象 對一個內(nèi)存空間有聯(lián)系,造成內(nèi)存空間沒有辦法被回收
*/
/*
將three對象添加到靜態(tài)集合里面,步驟是這樣的,
第一步 找到three對象聯(lián)系的內(nèi)存空間 oneMemory
第二步 找到 staticList集合對象聯(lián)系的內(nèi)存空間 listMemory
第三步 告訴系統(tǒng) staticList集合對象的部分成員 和內(nèi)存空間 oneMemory 建立聯(lián)系。
*/
staticList.add(three);
/*
在這里 即使three對象已經(jīng)和內(nèi)存空間 oneMemory 沒有聯(lián)系了。
oneMemory 也不會被回收,因為上面說了內(nèi)存空間和對象的關(guān)系是1對多。
而回收的條件是 一個內(nèi)存空間沒有一條和對象的聯(lián)系才可以回收。
此時 內(nèi)存空間 和staticList集合對象的部分成員 有聯(lián)系,所以 內(nèi)存空間不會被回收。
又由于staticList 集合對象聯(lián)系的內(nèi)存空間在 靜態(tài)存儲區(qū),是伴隨整個程序的。所以 在整個程序生命里面,
內(nèi)存空間 oneMemory 就得不到 回收。 就是內(nèi)存泄露了。
*/
three = null;
System.out.println(staticList.get(0).hashCode());
運行結(jié)果

可以看見。在我們將three對象賦值null切斷和內(nèi)存空間 oneMemory的聯(lián)系后。靜態(tài)集合staticList對象的部分成員依然和內(nèi)存空間 oneMemory有聯(lián)系。根據(jù)上面第三條所說,因為內(nèi)存空間 oneMemory 還是和對象有聯(lián)系的(staticList)。所以不會回收oneMemory內(nèi)存空間。又由于staticList是靜態(tài)的,生命和程序一樣長。 那么在整個程序周期里面,oneMemory內(nèi)存空間 都不會被回收。就造成了內(nèi)存泄露。
附上完整的代碼
package com.zfh.test;
import java.util.ArrayList;
import java.util.List;
public class JavaMain {
static List<Stu> staticList = new ArrayList<>();//開辟內(nèi)存空間 listMemory
public static void main(String[] args) {
Stu one; //只聲明 one對象 但是沒有分配內(nèi)存空間
//用new 開辟新的內(nèi)存空間 oneMemory ,調(diào)用構(gòu)造函數(shù)賦值,并將內(nèi)存空間 oneMemory 與 one對象建立聯(lián)系。
one = new Stu("one");
//聲明 two對象 并開辟內(nèi)存 twoMemory 調(diào)用構(gòu)造函數(shù)賦值,并將內(nèi)存空間 twoMemory與 two對象建立聯(lián)系
Stu two = new Stu("two");
//聲明 three對象, 并找到one 對象聯(lián)系的內(nèi)存空間 oneMemory。并將 oneMemory與 three 對象建立聯(lián)系
Stu three = one;
//此時 內(nèi)存空間 oneMemory 與兩個對象有聯(lián)系。一個是 one對象,一個是three對象
System.out.println("three 和 one 是否相等" + (three == one) + " one的哈希值" + one.hashCode() + " three的哈希值" + three.hashCode());
System.out.println("three 對象的值" + three.getName() + three.hashCode());
//修改one的值,第一步 找到one對象聯(lián)系的內(nèi)存空間 oneMemory , 將內(nèi)存空間oneMemory 中的name值改變
one.setName("change");
//讀取three對象值時候,先找到three對象聯(lián)系的內(nèi)存空間oneMemory,讀取其中的name值
System.out.println("three 對象的值 before" + three.getName() + three.hashCode());
/*
此時 如果 我們把one 對象 設(shè)置為null的。 對內(nèi)存空間 oneMemory 是沒有影響的
=null的作用是 將one對象自己本身 對內(nèi)存空間的聯(lián)系去除,并不會影響到內(nèi)存空間和其他對象的聯(lián)系
*/
one = null;
System.out.println("three 對象的值 after" + three.getName() + three.hashCode());
/*
內(nèi)存泄露 是長生命周期的對象 對一個內(nèi)存空間有聯(lián)系,造成內(nèi)存空間沒有辦法被回收
*/
/*
將three對象添加到靜態(tài)集合里面,步驟是這樣的,
第一步 找到three對象聯(lián)系的內(nèi)存空間 oneMemory
第二步 找到 staticList集合對象聯(lián)系的內(nèi)存空間 listMemory
第三步 告訴系統(tǒng) staticList集合對象的部分成員 和內(nèi)存空間 oneMemory 建立聯(lián)系。
*/
staticList.add(three);
/*
在這里 即使three對象已經(jīng)和內(nèi)存空間 oneMemory 沒有聯(lián)系了。
oneMemory 也不會被回收,因為上面說了內(nèi)存空間和對象的關(guān)系是1對多。
而回收的條件是 一個內(nèi)存空間沒有一條和對象的聯(lián)系才可以回收。
此時 內(nèi)存空間 和staticList集合對象的部分成員 有聯(lián)系,所以 內(nèi)存空間不會被回收。
又由于staticList 集合對象聯(lián)系的內(nèi)存空間在 靜態(tài)存儲區(qū),是伴隨整個程序的。所以 在整個程序生命里面,
內(nèi)存空間 oneMemory 就得不到 回收。 就是內(nèi)存泄露了。
*/
three = null;
System.out.println(staticList.get(0).hashCode());
}
}
bean對象 即Stu
package com.zfh.test;
public class Stu {
/* static {
System.out.println("靜態(tài)代碼塊 我只調(diào)用一次");
}*/
private String name;
/* {
System.out.println("構(gòu)造代碼塊");
}*/
public Stu(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void sout(){
System.out.println(this.name+this.hashCode());
}
public void printer() {
System.out.println(Stu.class.hashCode());
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("終結(jié)了");
}
}
檢測內(nèi)存泄露的原理
檢測內(nèi)存泄漏的關(guān)鍵是要能截獲住對分配內(nèi)存和釋放內(nèi)存的函數(shù)的調(diào)用。截獲住這兩個函數(shù),我們就能跟蹤每一塊內(nèi)存的生命周期,比如,每當(dāng)成功的分配一塊內(nèi)存后,就把它的指針加入一個全局的list中;每當(dāng)釋放一塊內(nèi)存,再把它的指針從list中刪除。這樣,當(dāng)程序結(jié)束的時候,list中剩余的指針就是指向那些沒有被釋放的內(nèi)存。這里只是簡單的描述了檢測內(nèi)存泄漏的基本原理,詳細(xì)的算法可以參見Steve Maguire的<<Writing Solid Code>>?! ?/p>
如果要檢測堆內(nèi)存的泄漏,那么需要截獲住malloc/realloc/free和new/delete就可以了(其實new/delete最終也是用malloc/free的,所以只要截獲前面一組即可)。對于其他的泄漏,可以采用類似的方法,截獲住相應(yīng)的分配和釋放函數(shù)。比如,要檢測BSTR的泄漏,就需要截獲SysAllocString/SysFreeString;要檢測HMENU的泄漏,就需要截獲CreateMenu/ DestroyMenu。(有的資源的分配函數(shù)有多個,釋放函數(shù)只有一個,比如,SysAllocStringLen也可以用來分配BSTR,這時就需要截獲多個分配函數(shù))。
在Windows平臺下,檢測內(nèi)存泄漏的工具常用的一般有三種,MS C-Runtime Library內(nèi)建的檢測功能;外掛式的檢測工具,諸如,Purify,BoundsChecker等;利用Windows NT自帶的Performance Monitor。這三種工具各有優(yōu)缺點,MS C-Runtime Library雖然功能上較之外掛式的工具要弱,但是它是免費的;Performance Monitor雖然無法標(biāo)示出發(fā)生問題的代碼,但是它能檢測出隱式的內(nèi)存泄漏的存在,這是其他兩類工具無能為力的地方。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Springboot如何同時裝配兩個相同類型數(shù)據(jù)庫
這篇文章主要介紹了Springboot如何同時裝配兩個相同類型數(shù)據(jù)庫,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
Java文件處理之使用itextpdf實現(xiàn)excel轉(zhuǎn)pdf
在文件處理中,經(jīng)常有文件類型轉(zhuǎn)換的使用場景,本文主要介紹了如何使用poi以及itextpdf完成excel轉(zhuǎn)pdf的操作,需要的小伙伴可以參考一下2024-02-02
基于 SpringBoot 實現(xiàn) MySQL 讀寫分離的問題
這篇文章主要介紹了基于 SpringBoot 實現(xiàn) MySQL 讀寫分離的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02
Java中synchronized關(guān)鍵字修飾方法同步的用法詳解
synchronized可以用來同步靜態(tài)和非靜態(tài)方法,下面就具體來看一下Java中synchronized關(guān)鍵字修飾方法同步的用法詳解:2016-06-06
淺談collection標(biāo)簽的oftype屬性能否為java.util.Map
這篇文章主要介紹了collection標(biāo)簽的oftype屬性能否為java.util.Map,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
springboot+websocket+redis搭建的實現(xiàn)
這篇文章主要介紹了springboot+websocket+redis搭建的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04

