根據《JAVA虛擬機規范》的規定,JAVA虛擬機在執行JAVA程序的過程中會把內存劃分為不同的數據區域。不同類型的數據會存儲在不同的區域,理解JAVA內存區域的工作細節對理解JAVA多線程、線程安全性有著重要意義。
注意,JAVA內存區域的劃分與我們常說的java內存模型JMM(Java Memery Model)是兩個互不交叉的維度的概念,兩者沒有任何關系。JMM主要是將主內存和工作內存的關系、數據從主內存區讀取的工作內存、以及從工作內存寫回到主內存區、以及不同線程的工作內存間數據同步的問題。
本文主要討論內存區域的劃分,不涉及JMM相關內容。
從線程隔離角度,可以把這些內存區域分成兩部分:線程私有的區域、以及線程共享的區域。顧名思義,線程私有區域的數據是各線程獨享的,因此,存儲在線程私有區域的數據不會有線程安全問題。線程共享區域的數據是要在不同線程間共享的,這部分數據才有可能引起線程安全問題。
程序計數器
程序計數器(Programe Counter Register)是屬于線程私有區域的數據區。
程序計數器數據區基本是程序員最不需要關心的區域。
程序計數器是為了控制程序運行、用來記錄需要執行的字節碼的地址的,為了準確記錄多線程環境下每一條線程的執行指令地址,程序計數器數據區必須是線程私有的區域。
方法區
方法區(Method Area)是線程共享的數據區域。
方法區用來存儲虛擬機加載的類信息,包括類的元數據、常量、靜態變量等。
大家可以反過頭來想想,為什么靜態變量可以在類的不同實體對象之間共享?也正因為如此,靜態變量使用不當的話也很容易引起線程安全問題。
另外,String s=new String(“12345”),那么s是否會存儲在方法區?s.intern()呢?
本地方法棧
本地房發展(Native Method Stack)屬于線程私有的數據區。
本地方法棧是為JAVA使用到的本地(Native)方法服務的,由于《JAVA虛擬機規范》沒有對本地方法棧做硬性規定,所以,有些虛擬機比如HotSpot把本地方法棧和虛擬機棧合二為一了。
虛擬機棧
虛擬機棧(VM Stack)是線程私有的數據區。
虛擬機棧和堆是程序員打交道最多的數據區,也是程序員最需要關心的內容。
JAVA虛擬機棧用于存儲局部變量,比如java方法內定義的變量、方法返回等。每一個JAVA方法執行的時候,JAVA虛擬機都會同步創建一個棧幀(Stack Frame),JAVA方法的執行過程對應著局部變量的進棧出棧過程。
JAVA棧保存的是JAVA基本類型或對象引用(refrence),保存在虛擬機棧的局部變量表中,以變量槽(slot)來表示。
堆
堆(JAVA Heap)是線程共享的數據區。
JAVA堆是存放JAVA對象的內存區域,幾乎所有的JAVA對象都存儲在JAVA堆中。
JAVA堆也是JAVA垃圾回收器(GC)的主要工作對象,不同的垃圾回收器或者垃圾回收算法,會按照不同的方式劃分JAVA堆,比如新生代、老年代、永久代…等等,不管怎么劃分,這樣的劃分僅僅是為了垃圾回收服務的,并不是JAVA堆內存必須按照這樣的方式進一步細致劃分。
***存儲在線程共享的數據區中的數據,才有可能存在線程安全性問題。***也就是說,方法區、堆內的數據,有可能存在系統安全性。但是方法區中存儲的主要是類的元數據信息以及靜態變量,從線程安全角度來看,應用需要重點關注的是靜態變量。而內存堆中存儲的是JAVA對象,在多線程高并發應用中,是引起線程安全問題的主要數據存儲區域、因此,也是程序員最應該關注的地方。