一、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的參數傳遞和一個經常看到的錯誤,如果有什麼不懂的,大家可以留言,如果有什麼不對的,也希望大神指正。