JVM運行時數據區
一個JVM進程也就是Java進程的內存情況。一個Java進程會有多個線程
安裝Java環境變量的時候,會有JDK JRE,而JRE也就是Java運行時環境,也就是JVM
內存分爲線程共享和線程獨佔的內存區域。
- 每個線程都可以訪問的區域稱之爲線程共享區域
- 每個線程都會有它獨立的空間,隨線程生命週期而創建與銷燬
如何開啓一個新的t1線程,會開闢一片獨佔的內存區域,這塊內存區域在邏輯上就代表了該線程。當線程啓動的時候,這塊內存區域會被分配,一旦線程銷燬這塊內存區域就會被回收。生命週期與線程相同。
1. 線程共享區域
線程共享部分有:方法區 堆內存
1.1 方法區
作用:存儲加載的類信息、常量、靜態變量、JIT編譯後的代碼數據
加載進方法區的類的類信息,是通過類對象的形式對外提供訪問方式,如開發中拿一個類對象
是通過student.class
的方式拿到Student 類
的類對象,而這個類對象實際上是一個引用,而這個引用最終指向方法區類對象。
GC:方法區存在垃圾回收,但回收效率低。主要原因是類對象很難被回收(只要有類對象的引用,該類對象就不能被回收)
1.2 堆內存
作用:唯一目的存放對象實例,幾乎所有的對象、數組都存放於此。
對於大多數應用而言,堆是JVM管理的內存中最大的一塊內存區域,也是最容易OOM的區域。
大多數JVM都將堆實現爲大小可擴展的,(通過 -Xmx 、-Xms控制)
問:堆中存儲了對象,到底存了什麼?
答:堆中只存儲了對象的實例字段的值,其他類信息 以類結構信息存在於方法區。
問:如何界定一個變量存在於棧還是堆?
答:如果是局部變量(方法內部申明的變量),存在於棧內存(並且是在方法被調用之後纔會存在於棧內存);如果是成員變量(類的成員變量),則存在於堆內存。
問:對象何時被回收?也就是如何界定對象是否 “存活”
答:當一個對象沒有被引用了時,
- 可以通過
引用計數器算法
來判斷是否被引用。存在兩個對象相互引用的問題
可達性分析算法
:主流的商用程序語言(Java、C#)都是通過可達性分析算法來判斷對象是否存活。
從GC roots
開始分析,如能夠通過引用找到該對象,那麼這個對象是可達的。如上圖中的object4
可以通過object1--> object3--> object4
找到,object2
可以通過object1--> object2
找到。而object6
無論從哪個GC roots
都無法分析,找不到,即便是object5 object6 object7
三個對象相互引用,任何一個GC roots
都無法找到他們,那麼他們皆不可達,就會被回收。
GC roots :可以理解爲非常難以被回收的東東。可以是:1.虛擬機 ; 2、方法中靜態屬性引用的對象 ; 3、方法區中常量引用的對象; 4、Native方法引用的對象
2. 線程獨佔區域
2.1 虛擬機棧
虛擬機棧:線程中方法執行的模型,每個方法執行時,就會在虛擬機棧中創建一個棧幀,每個方法從調用到執行的過程,就對應着棧幀在虛擬機棧中從入棧出棧的過程。描述方法先進後出的調用。
棧幀:代表着一個方法
有幾個線程,就會有幾個虛擬機棧。
問:一個數據存在於棧中,線程是否安全
答:線程安全,因爲一個虛擬機棧是線程獨佔的內存區域。
問:什麼是java線程?
答:需要執行的一段代碼流,先進後出
虛擬機棧,是爲了執行java方法。
2.2 本地方法棧
虛擬機棧,是爲了執行java方法;而本地方法棧是爲了執行native方法。
2.3 直接內存
並非屬於JVM內存區域,開發人員自己分配 / 回收內存。
Java程序運行過程
本地變量表,操作數棧
程序計數器
程序計數器通俗來講就是字節碼指令的地址,cpu在線程之間切換到時候,方便告訴cpu上下文,線程執行到那個指令了。
Java中的多線程是如何實現的?cpu上下文在線程之間 來回切換,執行指令。
線程安全問題
線程的內存劃分:線程共享區域,線程獨佔區域。而線程共享區域存在線程安全問題
當多個線程出現共享資源爭搶的時候,會出現線程安全問題。如:進程外的資源,如共享File文件的讀寫,DB數據的讀寫,一般指的是多個進程對文件/數據進行修改,而這些資源稱爲進程外的資源。
進程內的共享資源,也就是jvm共享區域,所有的線程都可以訪問,當多個線程訪問共享區域的時候,就會存在線程安全問題。
問:一個類的靜態字段,多個線程併發的訪問該字段,是否存在線程安全問題?
答:不安全。