1. 程式人生 > >java記憶體管理關係及記憶體洩露 原理

java記憶體管理關係及記憶體洩露 原理

這可能是最近寫的部落格中最接近底層的了。閒言少敘,進入正題。

java物件和記憶體的關係

首先,我們要知道下面幾條真理(自己總結的)

  1. 一個完整的建立物件流程是 1宣告物件,2開闢記憶體空間,3將物件和記憶體空間建立聯絡。
  2. 一個物件只能對應一個記憶體空間,一個記憶體空間可以對應很多物件
  3. 回收一個記憶體空間 。如果,這個記憶體空間沒有任何一個物件和他有聯絡。就可以被回收
  4. 對一個物件進行操作的時候,是先通過 物件 找到 記憶體空間,然後 對記憶體空間進行操作(讀,寫 改 刪)

這是本人總結出來的四條經驗。 特別重要的是,一定要有這種認知。不管任何語言,最終都是要實體記憶體上面反映的,物件 和 記憶體空間 是兩個不同的個體。

如果 沒有的話,那麼你會發現 下面將的都是什麼啊!

建立物件

       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("終結了");
    }
}

如果,你認為我的有錯誤,歡迎在下方評論