淺談Java虛擬機JVM的內存模型以及Java的參數傳遞

一、Java的內存模型

關於Java運行時的內存模型,佈局,大部分人瞭解熟悉的就是堆和棧(這也是我們最關心的倆個區域),然而實際上,JVM的內存模型其實遠遠不止這兩塊。實際上,JVM講內存劃分爲了5大模塊:1、方法區。2、堆。3、JVM棧。4、本地方法棧。5、程序計數器(這5大區域裏面,線程私有的是3,4,5這三個模塊(即線程自己擁有的空間,其他線程不能訪問到的),而線程共享(就是所有線程都能夠訪問的數據)的則是1和2)。下面筆者就會詳細講解每個區域到底存放那些數據,又有那些特性。

1、方法區

這塊區域存放的數據就是已經被JVM加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等。GC收集器(垃圾收集器)很少光顧這裏(關於GC收集器,筆者會在下一篇文章中提及)。

2、堆

堆,可以說是非常重要的一個區域,它存放了幾乎所有程序中new出來的對象實例(new出來的對象,都在堆中分配內存,當然,在最新的jdk版本中,這已經不是絕對正確的了)。因爲幾乎所有的對象實例都在這裏分配,所以GC收集器回收的內存大部分都是從這裏回收。

可以說,我們new出來的對象的各種數據都是存放在堆裏面的,比如創建的Person類裏面的name屬性的值,你都可以在這裏找到。

3、JVM棧

JVM棧,描述的是Java方法執行時的內存模型。每一個方法在執行的同時,都會創建一個棧幀,用於存儲局部變量表等各種數據,方法中所有你用到的變量都會在這裏找到(基本數據類型,如int,long,double這種,就是保存的變量的值,而像對象,String這種引用類型的變量,它保存的並不是值,而是一個地址,這個地址就指向真實的數據)。

在這裏,可能有讀者會想到,上面說了,所有new出來的對象都在堆中分配內存,而棧又是用於存儲方法的局部變量等數據的,那如果在方法裏面,new了一個局部變量出來,這個變量到底是在堆中還是棧中?

就如上面括號中的內容所說,如果在方法裏面new了一個局部變量出來,這個變量其實還是在堆中分配的內存,但是,會在棧中存放這個變量的地址,該地址就指向堆中這個變量分配的內存地址。講到這裏,筆者就想說一下關於java參數傳遞中經常會出錯的情況,看如下代碼:

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class TestUnitl
{
    //@Autowired
    //private IUserLoginServiceImpl userLoginService;
    //
    //@Test
    //public void testForConfig()
    //{
    //    UserAccountEntity test = userLoginService.verifyLoginUser("123456","123456");
    //    System.out.println("搜索的結果:" + test);
    //}

    public static void main(String[] args)
    {
        TestUnitl testUnitl = new TestUnitl();

        String originData = "我是原始數據";

        System.out.println("原始數據originData的值是:" + originData);

        testUnitl.changeValue(originData);

        System.out.println("原始數據originData的值是:" + originData);
    }

    public void changeValue(String data)
    {
        System.out.println("傳遞給形參的值是:" + data);
        data = "我改變了";
        System.out.println("改變之後的值:" + data);
    }
}

在看答案之前,讀者可以先自己閱讀代碼,想一下你得出的控制檯輸出是哪些數據?

下面是這段代碼運行之後的結果:

看到答案,可能會有讀者感到疑惑,我不是已經把originData傳到方法裏面,對他進行重新賦值,修改了值了嗎,爲什麼最後輸出的結果還是原來的值?

這裏,就要考慮上面筆者所說的堆和棧,以及棧中保存的數據了。其實,每個方法的形參,也都相當於是一個局部變量,它也會在棧中分配一個內存用來保存它的值。當你將方法外部的實參傳遞給形參的時候,對於基本數據類型,就相當於是給形參這個變量賦值了,將他內存的數據修改爲你實參一樣的數據了,然後在方法內部對形參進行操作時,其實操作的都是這個形參的內存,並沒有操作到實參的內存,如下圖所示:

而對於引用類型,比如對象,或者String這種類型,實參和形參其實存放的是對象的內存地址(對象在堆中分配,所以這個地址指向堆中的某個位置),並沒有保存實參的數據的值。這個內存地址指向了對象的首地址。所以,當在方法體內部用“=”給形參賦值時,其實真正的操作是,修改了形參存放的內存地址,所以形參就指向了另外一個對象,但是實參,它還是指向的原來的對象,所以最後你打印出來的實參變量,他並沒有發生改變。(讀者可以參考一下上面的圖,就是把數值換成了一個內存地址,比如5,改成0x12345678這總內存地址,然後0x12345678這個內存地址的位置是在堆裏面,這個地址就是一個對象數據的首地址,存放了對象的數據,這個地址之後就是對象的其他數據,因爲筆者比較懶,不想畫圖了,畫圖工具太傷。。。。)

4、本地方法棧

其實這個區域和JVM棧功能差不多,區別就是,JVM棧存放java方法的各種數據,本地方法棧存放native方法各種數據。

5、程序計數器

這個區域唯一的作用就是,存放下一條將要運行的指令的地址。

大概就這麼多,其實最主要的就是平常說的最多的堆和棧,另外稍微解釋了一下java的參數傳遞和一個經常看到的錯誤,如果有什麼不懂的,大家可以留言,如果有什麼不對的,也希望大神指正。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章