java記憶體管理關係及記憶體洩露 原理
這可能是最近寫的部落格中最接近底層的了。閒言少敘,進入正題。
java物件和記憶體的關係
首先,我們要知道下面幾條真理(自己總結的)
- 一個完整的建立物件流程是 1宣告物件,2開闢記憶體空間,3將物件和記憶體空間建立聯絡。
- 一個物件只能對應一個記憶體空間,一個記憶體空間可以對應很多物件
- 回收一個記憶體空間 。如果,這個記憶體空間沒有任何一個物件和他有聯絡。就可以被回收
- 對一個物件進行操作的時候,是先通過 物件 找到 記憶體空間,然後 對記憶體空間進行操作(讀,寫 改 刪)
這是本人總結出來的四條經驗。 特別重要的是,一定要有這種認知。不管任何語言,最終都是要實體記憶體上面反映的,物件 和 記憶體空間 是兩個不同的個體。
建立物件
Stu one; //只宣告 one物件 但是沒有分配記憶體空間
//用new 開闢新的記憶體空間 oneMemory ,呼叫建構函式賦值,並將記憶體空間 oneMemory 與 one物件建立聯絡。
one = new Stu("one");
//宣告 two物件 並開闢記憶體 twoMemory 呼叫建構函式賦值,並將記憶體空間 twoMemory與 two物件建立聯絡
Stu two = new Stu("two");
//宣告 three物件, 並找到one 物件聯絡的記憶體空間 oneMemory。並將 oneMemory與 three 物件建立聯絡
Stu three = one;
//此時 記憶體空間 oneMemory 與兩個物件有聯絡。一個是 one物件,一個是three物件
System.out.println("three 和 one 是否相等" + (three == one) + " one的雜湊值" + one.hashCode() + " three的雜湊值" + three.hashCode());
執行結果 我們可以發現,three物件 和one物件 指向的是同一個記憶體空間oneMemory。這個不就是符合上面所說的第二個真理 如果,我們對one物件進行操作,那麼產生的影響,也會反映到three物件上。 因為,**他們指向的是同一個記憶體空間,對one物件操作,就是做記憶體空間oneMemory進行操作。而three物件指向的也是oneMemory。這個符合上面第四條真理。**例子如下
System.out.println("three 物件的值" + three.getName() + three.hashCode());
//修改one的值,第一步 找到one物件聯絡的記憶體空間 oneMemory , 將記憶體空間oneMemory 中的name值改變
one.setName("change");
//讀取three物件值時候,先找到three物件聯絡的記憶體空間oneMemory,讀取其中的name值
System.out.println("three 物件的值" + three.getName() + three.hashCode());
null的作用
長久以來,我只知道,將一個值複製成null,那麼他就是空的了。但是 完全不知道,為啥。 還是接著上面的例子,看一段程式碼;
//讀取three物件值時候,先找到three物件聯絡的記憶體空間oneMemory,讀取其中的name值
System.out.println("three 物件的值 before" + three.getName() + three.hashCode());
/*
此時 如果 我們把one 物件 設定為null的。 對記憶體空間 oneMemory 是沒有影響的
=null的作用是 將one物件自己本身 對記憶體空間的聯絡去除,並不會影響到記憶體空間和其他物件的聯絡
*/
one = null;
System.out.println("three 物件的值 after" + three.getName() + three.hashCode());
執行結果 我們會發現 將one物件賦值為空後,three物件還是和先前一樣。以前一直認為null,是將物件和他的記憶體空間清楚。但現在不是!。程式碼註釋裡面寫的很清楚了。null 並不是清除記憶體空間,他只是把物件自己本身和記憶體空間的聯絡切斷了
記憶體洩露
如果,你明白了上面。那麼記憶體洩露對你來說也很簡單了。 再來學習一個新概念
上面這麼多呢。在本篇文章裡面,你只需要記住 被static關鍵詞修飾的變數,類,方法的生命週期是伴隨整個程式的。就是程式活多久,他們就活多久 接下來看程式碼 首先,我們定義一個靜態集合 staticList ,他是程式一執行,就會被建立好的。程式結束運行了,它才會被回收。
static List<Stu> staticList = new ArrayList<>();//開闢記憶體空間 listMemory
System.out.println("three 物件的值 after" + three.getName() + three.hashCode());
/*
記憶體洩露 是長生命週期的物件 對一個記憶體空間有聯絡,造成記憶體空間沒有辦法被回收
*/
/*
將three物件新增到靜態集合裡面,步驟是這樣的,
第一步 找到three物件聯絡的記憶體空間 oneMemory
第二步 找到 staticList集合物件聯絡的記憶體空間 listMemory
第三步 告訴系統 staticList集合物件的部分成員 和記憶體空間 oneMemory 建立聯絡。
*/
staticList.add(three);
/*
在這裡 即使three物件已經和記憶體空間 oneMemory 沒有聯絡了。
oneMemory 也不會被回收,因為上面說了記憶體空間和物件的關係是1對多。
而回收的條件是 一個記憶體空間沒有一條和物件的聯絡才可以回收。
此時 記憶體空間 和staticList集合物件的部分成員 有聯絡,所以 記憶體空間不會被回收。
又由於staticList 集合物件聯絡的記憶體空間在 靜態儲存區,是伴隨整個程式的。所以 在整個程式生命裡面,
記憶體空間 oneMemory 就得不到 回收。 就是記憶體洩露了。
*/
three = null;
System.out.println(staticList.get(0).hashCode());
執行結果 可以看見。在我們將three物件賦值null切斷和記憶體空間 oneMemory的聯絡後。靜態集合staticList物件的部分成員依然和記憶體空間 oneMemory有聯絡。根據上面第三條所說,因為記憶體空間 oneMemory 還是和物件有聯絡的(staticList)。所以不會回收oneMemory記憶體空間。又由於staticList是靜態的,生命和程式一樣長。 那麼在整個程式週期裡面,oneMemory記憶體空間 都不會被回收。就造成了記憶體洩露。
附上完整的程式碼
package com.zfh.test;
import java.util.ArrayList;
import java.util.List;
public class JavaMain {
static List<Stu> staticList = new ArrayList<>();//開闢記憶體空間 listMemory
public static void main(String[] args) {
Stu one; //只宣告 one物件 但是沒有分配記憶體空間
//用new 開闢新的記憶體空間 oneMemory ,呼叫建構函式賦值,並將記憶體空間 oneMemory 與 one物件建立聯絡。
one = new Stu("one");
//宣告 two物件 並開闢記憶體 twoMemory 呼叫建構函式賦值,並將記憶體空間 twoMemory與 two物件建立聯絡
Stu two = new Stu("two");
//宣告 three物件, 並找到one 物件聯絡的記憶體空間 oneMemory。並將 oneMemory與 three 物件建立聯絡
Stu three = one;
//此時 記憶體空間 oneMemory 與兩個物件有聯絡。一個是 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物件聯絡的記憶體空間 oneMemory , 將記憶體空間oneMemory 中的name值改變
one.setName("change");
//讀取three物件值時候,先找到three物件聯絡的記憶體空間oneMemory,讀取其中的name值
System.out.println("three 物件的值 before" + three.getName() + three.hashCode());
/*
此時 如果 我們把one 物件 設定為null的。 對記憶體空間 oneMemory 是沒有影響的
=null的作用是 將one物件自己本身 對記憶體空間的聯絡去除,並不會影響到記憶體空間和其他物件的聯絡
*/
one = null;
System.out.println("three 物件的值 after" + three.getName() + three.hashCode());
/*
記憶體洩露 是長生命週期的物件 對一個記憶體空間有聯絡,造成記憶體空間沒有辦法被回收
*/
/*
將three物件新增到靜態集合裡面,步驟是這樣的,
第一步 找到three物件聯絡的記憶體空間 oneMemory
第二步 找到 staticList集合物件聯絡的記憶體空間 listMemory
第三步 告訴系統 staticList集合物件的部分成員 和記憶體空間 oneMemory 建立聯絡。
*/
staticList.add(three);
/*
在這裡 即使three物件已經和記憶體空間 oneMemory 沒有聯絡了。
oneMemory 也不會被回收,因為上面說了記憶體空間和物件的關係是1對多。
而回收的條件是 一個記憶體空間沒有一條和物件的聯絡才可以回收。
此時 記憶體空間 和staticList集合物件的部分成員 有聯絡,所以 記憶體空間不會被回收。
又由於staticList 集合物件聯絡的記憶體空間在 靜態儲存區,是伴隨整個程式的。所以 在整個程式生命裡面,
記憶體空間 oneMemory 就得不到 回收。 就是記憶體洩露了。
*/
three = null;
System.out.println(staticList.get(0).hashCode());
}
}
bean物件 即Stu
package com.zfh.test;
public class Stu {
/* static {
System.out.println("靜態程式碼塊 我只呼叫一次");
}*/
private String name;
/* {
System.out.println("構造程式碼塊");
}*/
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("終結了");
}
}
如果,你認為我的有錯誤,歡迎在下方評論