JVM相關總結

[JVM]

雖然大部分情況下不需要我們直接對Java內存方面進行直接操作,但是了解其中的原理和調優還是比較重要的,JVM中的總結如下:

[運行數據區]

  1. 程序計數器
    每個線程都有一個獨立的程序計數器,是線程私有的。
    若是正在運行一個方法,計數器記錄正在執行虛擬機字節碼指令地址;
    執行的是native的方法,計數器為空(Undefined),不存在OutOfMemoryError

  2. 虛擬機棧
    線程私有,java方法執行的內存模型。
    每個方法運行的時候會創建一個棧幀,
    棧幀存儲著 ->
    (a)局部變量
    用于存放方法參數和方法內部定義的局部變量
    包括基本數據類型boolean,byte,char,int,short,double,long.float,對象引用類型reference->不等于對象本身,returnAddress
    (b)操作數棧
    后入先出
    (c)動態鏈接
    (d)方法返回地址
    返回方法被調用的位置
    (e)一些額外的附加信息
    規范里沒有提到的信息,例如與調試相關的信息

  3. 本地方法棧
    線程私有;
    虛擬機使用的native的方法服務。

  4. java堆
    java堆是被所以線程共享的一塊內存區域,啟動時候創建。
    所有對象實例和數據在堆上分配內存。

  5. 方法區
    各個線程共享的內存區域;
    存儲java虛擬機加載的類信息,訪問修飾符,方法描述,常量,靜態變量,編譯后的代碼等數據。
    (a)運行的常量池
    class文件->動態性
    (b)直接內存
    例如NIO基于通道和緩沖區的I/O方式調用native

[垃圾回收]

  1. 引用計數算法
    對象添加一個引用計數器,每當有一個地方引用時候,計數器加1;引用失效時,計數器值減1;任何時候計數器的值為0,對象不再使用。
  2. 可達性分析算法
    以GC Roots對象為起始點,當一個對象到達GCRoots沒有任何引用鏈的時候,證明對象不可達。
    作為GCRoots對象的包括:
(a) 虛擬機棧(棧幀中的本地變量表)中引用的對象  
(b) 方法區中類靜態屬性引用的對象  
(c) 方法區中常量引用的對象  
(d) 本地方法棧中JNI(即一般說的native方法)引用的對象
  1. 標記-清除算法
  2. 復制算法
    內存分為一塊較大的Eden和兩塊較小的Survivor空間,每次使用Eden和一塊Survivor空間;
    回收時,將Eden和Survivor存活的對象一次性復制到另外一塊Survivor空間,清理用過的Eden和Survivor空間,默認Eden和Survivor大小比例8:1
  3. 標記-整理算法

[垃圾收集器]

  1. Serial收集器
    新生代采取復制算法,暫停所有用戶線程;
    老年代采取標記整理算法,暫停用戶所以線程
  2. ParNew收集器
    Serial收集器的多線程版本
    GC多線程,新生代采取復制算法,暫停所有用戶線程;
    老年代采取標記整理算法,暫停用戶所以線程
  3. Parallel Scavenge收集器
    CMS收集器盡可能縮短垃圾收集時用戶線程的停頓時間
    Parallel Scavenge收集器目的達到一個可控制的吞吐量(吞吐量=運行代碼時間/(運行代碼時間+垃圾收集時間))
  4. Serial Old收集器
  5. Parallel Old收集器
  6. CMS收集器
    以獲取最短回收停頓時間為目標的收集器。
    采取標記-清除算法 ->
(1)初始標記
(2)并發標記
(3)重新標記
(4)并發清除
  1. G1收集器
    面向服務端應用的垃圾收集器
    步驟 ->
(a)初始標記
(b)并發標記
(c)最終標記
(d)篩選回收

[內存分配]

-Xms20M,-Xmx20M,-Xmn10M -XX:SurvivorRatio=8,-XX:+PrintGCDetails
java堆大小為20M,不可擴展,10M分配新生代,10M分配老年代
新生代的Eden區與一個Survivor區的空間比例為8:1
新生代的總可用空間(Eden區+1個Survivor區的總容量)
-XX:PermSize永久代
-XX:MaxPermSize永久代最大容量

存儲的是java的類信息,包括解析得到的方法、屬性、字段等等。永久帶基本不參與垃圾回收。
在jdk1.8之前,通過-XX:PermSize=64m -XX:MaxPermSize=128m來調整永久代大小,
在jdk1.8之后,永久代被移除,原本存儲在永久代的數據將存放在一個叫做元空間的本地內存區域,
通過 -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m來調整元空間大小

[類加載的時機]

生命周期
-> 加載

(a)通過一個類的全限定名來獲取定義此類的二進制字節流   
(b)將這個字節流所代表的靜態存儲結構轉化為方法區運行時的數據結構  
(c)在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口

-> 驗證

(a)文件格式驗證  
(b)元數據驗證  
(c)字節碼驗證  
(d)符號引用驗證

-> 準備 -> 解析 -> 初始化 -> 使用 -> 卸載

[類加載器]

  1. 啟動類加載器(Bootstrap ClassLoader)
    負責將存放<JAVA_HOME>\lib目錄中;
    被-Xbootclasspath參數指定的路徑中(僅按照文件名識別,如rt.jar)
  2. 擴展類加載器(Extension ClassLoader)
    負責加載<JAVA_HOME>\lib\ext目錄中
    或者被java.ext.dirs系統變量所指定的路徑中所有的類庫
  3. 應用類加載器(Application ClassLoader)
    負責加載用戶類路徑ClassPath上所指定的類庫
  4. 自定義類加載器 UserClassLoader

如果一個類加載器收到了類加載的請求,請求委派給父類加載器,只有當父類反饋無法完成,子類嘗試加載。

[Java內存模型]

Java內存模型的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。
主內存主要對應與java堆里的對象實例數據部分,而工作內存則對應虛擬機棧中的部分區域。
內存間的交互

  1. lock鎖定
    作用于主內存的變量,它把一個變量標識為一條線程獨占的狀態。
  2. unlock(解鎖)
    作用于主內存的變量,把一個處于鎖定狀態的變量釋放出來,釋放后才能被其他線程鎖定。
  3. read(讀取)
    作用于主內存的變量,將一個變量的值從主內存傳輸到線程的工作內存,以便隨后的load動作使用。
  4. load(載入)
    作用于工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
  5. use(使用)
    作用于工作內存的變量,它把一個變量的值傳遞給執行引擎,每當虛擬機遇到需要使用變量的值的字節碼指令時將會執行這個操作。
  6. assign(賦值)
    作用于工作內存的變量,它把執行引擎接收到的值賦給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。
  7. store
    作用于工作內存的變量,它把工作內存內存中一個變量的值傳送給主內存中,以便隨后的write操作使用。
  8. write(寫入)
    作用于主內存的變量,它把從store操作從工作內存中得到的變量的值放入主內存的變量中。

volatile

  1. 此變量對所以線程可見(等線程A寫進主內存結束,線程B才開始讀取)
  2. 主內存一致,工作內存不能保持一致
  3. 禁止指令重排序優化

內存模型特征

  1. 原子性
  2. 可見性
    變量修改后將新值同步回主內存,在變量讀取前從主內存刷新變量值這種依賴主內存作為傳遞媒介的方式來實現可見性。
    volatile的特殊規則保證了新值能立即同步到主內存,以及每次使用前立即從主內存刷新。
  3. 有序性

[線程的實現]

  1. 使用內核線程實現
    內核通過操縱調度器對線程進行調度,并將線程的任務映射到各個處理器上。
    內核線程一種高級接口--輕量級進程LWP,每個輕量級進程由一個內核線程支持。
    用戶態和內核態中來回切換。
    1:1
  2. 使用用戶線程實現
    1:N
  3. 使用用戶線程實現和使用用戶線程加輕量級進程混合實現
    N:M

[線程安全的同步方法]

  1. 互斥同步
    互斥是實現同步的一種手段,臨界區,互斥量,信號量
    阻塞同步
  2. 非阻塞同步
(a)測試并設置
(b)獲取并增加
(c)交換
(d)比較并交換
(e)加載鏈接/條件存儲
  1. 無同步方案
    不涉及共享數據,不依賴堆的數據和公共系統資源

[鎖優化]

  1. 自旋鎖和自適應自旋
    自旋--線程執行一個忙循環-XX:+UseSpinning參數來開啟
    自旋超過一定次數沒有成功獲得鎖,便使用傳統方式掛起線程,自旋次數默認是10次,用戶可以使用參數 -XX:PreBlockSpin更改
  2. 自適應的自旋
    由前一次在同一個鎖上的自旋時間時間及鎖的擁有者的狀態來決定,
    如果同一個鎖對象,自旋剛剛成功過,允許自旋更長時間,很少成功,則省略自旋這個過程。
  3. 鎖消除
    虛擬機及時編譯器運行的時候,對一些代碼要求同步,但是不可能存在數據競爭的進行消除。
  4. 鎖粗化
    虛擬機探測到一串零碎的操作對同一個對象枷鎖,會把加鎖同步到整個操作序列外部。
  5. 輕量級鎖
    無競爭的情況下使用CAS操作去消除同步使用的互斥量
  6. 偏向鎖
    無競爭的情況下把整個同步都消除掉,CAS操作不進行。
    -XX:+UseBiasedLocking
    鎖對象第一次被線程獲取,會把對象頭標志設置為“01”,即偏向模式,同時用CAS操作記錄在對象的Mark Word之中,
    如果CAS成功,以后每次持有偏向鎖的線程都不再進行任何同步操作。
    如果有另一個線程來獲取,偏向鎖結束,根據鎖對象是否處于被鎖定的狀態,撤銷偏向,后續操作和輕量級鎖執行類似。
    缺點:多個線程下訪問,并不適合。

本文參考于《深入理解Java虛擬機》推薦大家看一看,會對自己收獲很大。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容