JVM初探
jvm體系結構
-
方法區:存儲 static,final,Class,常量池。
-
本地方法棧 :用來登記native 方法,最終的執行時,通過JNI加載本地方法庫的方法(java本地接口)
-
java虛擬機棧 : 8大基本類型+對象引用+實例方法
- 棧內存:主管程序的運行,聲明週期和線程同步,線程結束棧內存釋放,不存在垃圾回收問題
雙親委派機制
類加載器:
BootstrapClassLoader
(啓動類加載器):負責加載jre/lib/rt.jar(c++編寫,開發者無法直接獲取啓動類加載器的引用)ExtClassLoader
(擴展類加載器) : 負責加載jre/lib/ext/*.jar(java編寫)AppClassLoader
(應用程序類加載器): 負責加載用戶類路徑(classpath)上指定的類庫。(java編寫)CustomClassLoader
(用戶自定義類加載器) : 加載指定路徑的class文件(java編寫)
工作流程:
- 當類加載器收到類加載的請求時,不斷委派父加載器,直到啓動類加載器
- 若啓動類加載器能加載,則加載,不能的話,就拋出異常並通知子加載器加載
- 重複步驟二;
native
在95年創造java的時候,c和c++已經很流行了,java要佔據一地,使Java也可以調用c,c++的程序,這就是native
native
標記的 代表是非java編寫的,會回去調用底層的c語言庫,進入本地方法棧。
jvm
在內存管理中開闢了一塊標記區域:本地方法棧 用來登記native 方法,最終的執行時,通過JNI加載本地方法庫的方法
new一個對象的過程
new一個對象分爲,加載並初始化和創建一個對象兩個步驟,如果第一個步驟已經走過了,那麼就會直接執行第二步;
如果沒有則會進行類的加載和初始化:
-
類的加載: 分爲加載和連接兩部分其中連接包括(驗證,準備,解析)
- 加載: jvm找到對應的class文件,以二進制字節流的形式讀取到jvm方法區中。
- 驗證:總的來說就是檢查是否符合java規範
- 是否符合Class文件規範
- 是否符合java語法(final標記的類是否有子類,final方法是否被重寫等)
- 準備:爲其靜態變量分配空間並設置一個初始值,如果是靜態常量則直接被賦值
- 解析:將符號引用轉換爲直接引用,和靜態綁定(private,final,static 的方法與類和對象關聯起來)
- 符號引用 : java類並不知道所引用類的實際地址用符號引用代替
- 直接引用 : 直接指向目標的指針
- 靜態綁定: 所有不會被重寫的方法和域都會被靜態綁定
-
初始化的過程(先父後子)
- 靜態變量賦值 和 執行靜態代碼塊
-
創建對象
- 在堆區爲其分配空間(分配的內存包括本類和父類的所有實例變量,但不包括任何靜態變量)
-
對所有實例變量賦默認值 ( 將方法區內對實例變量的定義拷貝一份到堆區,然後賦默認值)
-
執行實例初始化代碼
- 初始化順序是先初始化父類再初始化子類,初始化時先執行實例代碼塊然後是構造方法
-
-
在棧區定義引用變量,然後將堆區對象的地址賦值給它
堆
jdk1.8後,常量池,被分離出來,和元空間一起,邏輯上屬於堆,物理上卻不屬於
VM調優參數 :-Xmx2048m -Xms1024m -XX:+PrintGCDetails(壓制警告)
:-Xmx8m -Xms4m -Xlog:gc*(代替上面的gc參數)
-Xmx表示運行時最大可佔用的內存(默認1/4)
-Xms程序啓動時佔用的內存(默認1/64)
默認經歷15次GC沒有被回收,進入養老區。
GC
GC主要是對堆和方法區進行的,程序計數器,虛擬機棧和本地方法棧都是線程私有的,隨線程消失而消失不需要回收。
由上圖知:堆中分爲新生區和老年區
新生區: 伊甸園 :倖存區(to) : 倖存區(from) = 8 : 1: 1
- 伊甸園 : 絕大部分剛創建的對象就存儲在伊甸園區
- 倖存一區 : 在伊甸園空間執行第一次GC(Minor GC)之後,存活的對象被移動到其中一個倖存者空間(Survivor)。每一次gc後存活的對象就會被移到同一個倖存區(to)
- 倖存二區 : 當一個倖存者空間飽和,還在存活的對象會被移動到另一個倖存者(from)空間。然後會清空已經飽和的哪個倖存者空間。 清空的區就變成from區,from就變成了to區;
- 在以上步驟中重複N次(N = MaxTenuringThreshold(年齡閥值設定,默認15))依然存活的對象,就會被移動到老年代
垃圾回收算法
-
標記清除法
- 標記階段 : 程序會檢查每個對象是否爲活動對象,如果是活動對象,則程序會在對象頭部打上標記。
- 清除階段 : 對對象進行回收並取消標誌位,並判斷與前一個空閒分塊是否連續,連續則合併。 (回收就是,將次分塊連接到空閒鏈表中)
- 分配 : 搜索空閒鏈表,找到大於或等於新對象size的塊。
不足:
- 標記和清除過程效率都不高;
- 會產生大量不連續的內存碎片,導致無法給大對象分配內存。
-
標記整理法
- 讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。
優點 : 避免產生內存碎片
不足 : 需要大量移動對象,效率低
-
複製法
- 將內存分爲兩塊,每次使用其中的一半,使用後若該對象存活則複製到另一半空間中,並釋放剛剛使用的空間中的對象。
不足: 內存只有一半了。
-
分代收集
- 新生代使用:複製算法
- 老年代使用:標記 - 清除 或者 標記 - 整理 算法
JMM(java內存模型)
什麼是Volatile
Volatile是Java虛擬機 提供的 輕量級同步機制,是關鍵字。
- 保證可見性
- 不保證原子性
- 禁止指令重排
什麼是JMM
JMM: Java內存模型,不存在,概念,約定;
關於JMM的一些同步約定
- 線程解鎖前,必須把共享變量立刻寫回。
- 線程加鎖前,必須讀取主存中最新值到工作內存。
- 加鎖解鎖是同一把鎖。
內存交互操作有8種,虛擬機實現必須保證每一個操作都是原子的,不可在分的(對於double和long類型的變量來說,load、store、read和write操作在某些平臺上允許例外)
-
lock (鎖定):作用於主內存的變量,把一個變量標識爲線程獨佔狀態
-
unlock (解鎖):作用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定
-
read (讀取):作用於主內存變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用
-
load (載入):作用於工作內存的變量,它把read操作從主存中變量放入工作內存中
-
use (使用):作用於工作內存中的變量,它把工作內存中的變量傳輸給執行引擎,每當虛擬機遇到一個需要使用到變量的值,就會使用到這個指令
-
assign (賦值):作用於工作內存中的變量,它把一個從執行引擎中接受到的值放入工作內存的變量副本中
-
store (存儲):作用於主內存中的變量,它把一個從工作內存中一個變量的值傳送到主內存中,以便後續的write使用
-
write (寫入):作用於主內存中的變量,它把store操作從工作內存中得到的變量的值放入主內存的變量中
JMM對這八種指令的使用,制定瞭如下規則:
- 不允許read和load、store和write操作之一單獨出現。即使用了read必須load,使用了store必須write
- 不允許線程丟棄他最近的assign操作,即工作變量的數據改變了之後,必須告知主存
- 不允許一個線程將沒有assign的數據從工作內存同步回主內存
- 一個新的變量必須在主內存中誕生,不允許工作內存直接使用一個未被初始化的變量。就是懟變量實施use、store操作之前,必須經過assign和load操作
- 一個變量同一時間只有一個線程能對其進行lock。多次lock後,必須執行相同次數的unlock才能解鎖
- 如果對一個變量進行lock操作,會清空所有工作內存中此變量的值,在執行引擎使用這個變量前,必須重新load或assign操作初始化變量的值
- 如果一個變量沒有被lock,就不能對其進行unlock操作。也不能unlock一個被其他線程鎖住的變量
- 對一個變量進行unlock操作之前,必須把此變量同步回主內存