《深入理解Java虛擬機:JVM高級特性與最佳實踐》讀書筆記

2021-02-21 JAVA高級架構
第一部分 走進Java一、走進Java

1、概述

  java廣泛應用於嵌入式系統、移動終端、企業伺服器、大型機等各種場合,擺脫了硬體平臺的束縛,實現了「一次編寫,到處運行」的理想

2、java技術體系結構

  按照功能來劃分

包括以下幾個組成部分:Java程序設計語言,各種硬體平臺的java虛擬機,Java API類庫,來自商業機構和開源社區的第三方Java類庫,Class文件格式

Java程序設計語言,java虛擬機,Java API類庫統稱為JDK,是用於支持java程序開發的最小環境

Java API類庫中的Java SE API子集和Java虛擬機統稱為JRE,是支持java程序運行的基本環境

  按照技術所服務的領域劃分分為4個平臺

Java Card:支持java小程序運行在java小內存設備(如智慧卡)上的平臺

Java ME:支持Java程序運行在行動裝置上的平臺

Java SE:支持面向桌面級應用的平臺

Java EE:支持使用多層架構的企業級應用的平臺

第二部分 自動內存管理機制二、內存區域和內存溢出異常1、運行時數據區

  程序計數器

  Java虛擬機棧

同程序計數器一樣,也是線程私有的。每個方法在執行的時候都會創建一個棧幀,用於存儲局部變量表、操作數棧、動態連結、方法出口等信息。

每一個方法從調用直至執行完成的過程,都對應著一個棧幀在虛擬機棧中入棧和出棧的過程。

局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。

如果請求的棧深度超過虛擬機鎖允許的深度,將拋出StackOverFlowError異常。如果拓展無法申請到足夠的內存,將拋出OutOfMemoryError異常。

  本地方法棧

  Java堆

Java堆是所有線程共享的一塊內存區域,用來存放對象實例,幾乎所有的對象實例都在這裡分配。

Java堆是垃圾回收的主要區域,採用分代收集算法。

Java堆分為新生代和老年代,新生代在細緻一點分為Eden,From Survivor,To Survivor空間。

如果堆中無法完成對象實例的內存分配,且堆也無法擴展時,將拋出OutOfMemoryError異常。

  方法區

  運行時常量池

是方法區的一部分,Class文件除了有類的版本、欄位、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載後進入方法區的運行時常量池存放。

運行時常量池相對於Class文件常量池,具有動態性,運行期間也可以將新的常量放入常量池,比如String類的intern()方法。

當運行時常量池無法申請到更多的內存時,將會拋出OutOfMemoryError異常。

  直接內存

2、HotSpot虛擬機對象探秘

  對象的創建

當遇到new指令時,先判斷這個類是否被加載、解析、初始化過,如果沒有,先執行相應類的加載過程。

類加載檢查通過後,為新生對象分配內存,如果Java堆內存是規整連續的,採用「指針碰撞」的分配方式,如果是不連續規整的,採用「空閒列表」分配方式。內存是否規整取決於垃圾收集器是否帶有壓縮整理功能。

Serial,ParNew等帶有Compact過程的收集器,採用的分配算法是「指針碰撞」。而CMS這種基於Mark-Sweep算法的收集器,通常採用「空閒列表」分配方式。

創建對象涉及到分配內存和指針指向兩個操作,不是原子性的,不是線程安全的。針對這個問題,有兩個解決辦法:1是採用CAS加上失敗重試來保證操作的原子性。2是採用TLAB(Thread Local Allocation Buffer)策略,在Java堆中預先為每一個線程分配一小塊內存,稱為TLAB(Thread Local Allocation Buffer),哪個線程要分配內存就在各自的TLAB上進行內存的分配,只有TLAB用完進行新的TLAB的分配時才需要同步鎖定,虛擬機是否使用TLAB,可以通過 -XX:+/- UseTLAB

內存分配完成後,需要對對象頭進行設置,包括這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。

最後執行init方法,把對象按照程式設計師的意願進行初始化。這樣一個真正可用的對象才算完全生產出來。

  對象的內存布局

分為三塊區域,對象頭(Header)、實例數據(Instance Data)、對齊補充(Padding)。

對象頭,存儲對象自身的運行時數據,如哈希碼、對象的GC分代年齡、鎖狀態標誌、偏向線程ID、偏向時間戳,這部分數據的長度在32位和64位虛擬機中分別為32bit和64bit。

另一個部分是類型指針,虛擬機通過這個對象來確定這個對象是哪個類的實例。

  對象的訪問定位

           

3、OutOfMemoryError異常

堆溢出

虛擬機棧和本地方法棧溢出

方法區和運行常量池溢出

本地直接內存溢出

三、垃圾收集器和內存分配策略 1、對象已死嗎?如何確定對象是否還「活著」

  引用計數器方法

  可達性分析方法

  再談引用

JDK1.2 之後把引用分為了四種:強引用、軟引用、弱引用、虛引用

強引用:只要強引用還存在,就不會被垃圾回收器回收。類似 Object o=new Object()

軟引用:指一些有用但並非必須的對象,在系統將要發生內存溢出的時候,會將這部分對象回收。SoftReference 類來實現軟引用

弱引用:被弱引用關聯的對象只能生存到下一次垃圾回收。WeakReference 類來實現弱引用

虛引用:一個對象是否有虛引用的存在,完全不會對其生存時間造車影響,也無法通過虛引用取得對象的引用。一個對象設置虛引用的唯一目的是在被垃圾回收的時候收到一個系統通知

   對象被回收的過程

當對象進行可達性分析沒有與GC Roots相連的引用鏈,將會被第一次標記,並根據是否需要執行finalize()方法進行一次篩選,對象沒有重寫finalize()或者虛擬機已經調用過finalize(),都被視為不需要執行

如果對象有必要執行finalize,會被放入到F-Queue隊列中,並在稍後由虛擬機自動創建的低優先級的Finalizer線程去觸發它,並不保證等待此方法執行結束。

如果對象在finalize()方法執行中,重新和GC Roots產生了引用鏈,則可以逃脫此次被回收的命運,但finalize()方法只能運行一次,所以並不能通過此方法逃脫下一次被回收

筆者不建議使用這個方法,建議大家完全忘掉這個方法的存在。

   回收方法區

2、垃圾回收算法

  標記-清除

       1 是效率問題,標記和清除的效率都不高。

       2 是空間問題,會產生大量不連續的內存碎片,碎片太多會都導致大對象無法找到足夠的內存,從提前觸發垃圾回收。

  複製算法

  標記-整理

  分代收集

3、HotSpot算法實現

  枚舉根節點實現

  安全點

  安全區域

4、垃圾收集器

如果兩個收集器之間有連線,說明可以搭配使用。沒有最好的收集器,也沒有萬能的收集器,只有對應具體應用最合適的收集器。

  

  Serial 收集器

  ParNew收集器

新生代收集器,Serial的多線程版本,除了Serial收集器之外,只有它能與CMS收集器配合工作。

-XX:+UseConcMarkSweepGC 選項後默認的新生代收集器,也可以使用 -XX:+UseParNewGC 選項來強制指定它

ParNew收集器在單CPU的環境中,效果不如Serial好,隨著CPU的增加,對於GC時系統資源的利用還是很有效的。

默認開啟的收集線程數和CPU數相等,可以使用 -XX:ParallelGCThreads 指定

  Parallel Scavenge收集器

新生代收集器,並行收集器,複製算法,和其他收集器不同,關注點的是吞吐量(垃圾回收時間佔總時間的比例)。提供了兩個參數用於控制吞吐量。

-XX:MaxGCPauseMillis,最大垃圾收集停頓時間,減少GC的停頓時間是以犧牲吞吐量和新生代空間來換取的,不是設置的越小越好

-XX:GCTimeRatio,設置吞吐量大小,值是大於0小於100的範圍,相當於吞吐量的倒數,比如設置成99,吞吐量就為1/(1+99)=1%。

-XX:UseAdaptiveSizePolicy ,這是一個開關參數,打開之後,就不需要設置新生代大小(-Xmn)、Eden和Survival的比例(-XX:SurvivalRatio)、 晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數,收集器會自動調節這些參數。

  Serial Old 收集器

  Palallel Old 收集器

  CMS 收集器

Concurrent Mark Sweep,是一種以獲取最短回收停頓時間為目標的收集器,尤其重視服務的響應速度。基於標記-清除算法實現。

分為四個步驟進行垃圾回收:初始標記,並發標記,重新標記,並發清除。只有初始標記和重新標記需要停頓。

初始標記只是標記一下GC Roots能直接關聯到的對象,速度很快。並發標記就是進行GC Roots的Tracing。

重新標記為了修正並發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間會比初始標記階段稍長,遠比並發時間短。

耗時最長的並發標記和並發清除過程中,處理器可以與用戶線程一起工作。

它並不是完美的,有如下三個比較明顯的缺點:

     1、垃圾回收時會佔用一部分線程,導致系統變慢,總吞吐量會降低。

     2、無法處理浮動垃圾,需要預留足夠的內存空間給用戶線程使用,可以通過 -XX:CMSInitiatingOccupancyFraction 參數控制觸發垃圾回收的閾值。

       如果預留的內存無法滿足程序需要,就會出現「Concurrent Mode Failure」失敗,這時將啟動應急預案,啟用Serial Old 進行垃圾回收,停頓時間會變長

       所以-XX:CMSInitiatingOccupancyFraction 參數的值設置的太高,會導致頻繁「Concurrent Mode Failure」失敗,性能反而降低。

     3、標記-清理,容易產生內存碎片。-XX:+UseCMSCompactAtFullColletion 開啟碎片整理功能,默認開啟,-XX:CMSFullGCsBeforeCompaction,控制多少次不壓縮的FullGC之後來一次帶壓縮的

  G1 收集器

包括新生代和老年代的垃圾回收。和其他收集器相比的優點:並行和並發,分代收集,標記-整理,可預測的停頓。垃圾回收分為以下幾個步驟:

初始標記:標記GC Roots能夠直接關聯到的對象,這階段需要停頓線程,時間很短

並發標記:進行可達性分析,這階段耗時較長,可與用戶程序並發執行

最終標記:修正發生變化的記錄,需要停頓線程,但是可並行執行

篩選回收:對各個Region的回收價值和成本進行排序,根據用戶所期望的停頓時間來執行回收計劃

5、內存分配和回收策略

對象優先在Eden分配,當新生區沒有足夠的內存是,通過分配擔保機制提前轉移到老年代中去

大對象直接進入老年代。大對象是指需要大量連續內存空間的對象,虛擬機提供了參數 -XX:PretenureSizeThreshold(只對Serial,PerNew兩個回收器起效),令大於這個值得對象直接在老年代分配,避免了Eden和兩個Survival之間發生大量的內存複製。

長期存活的對象將進入老年代。虛擬機給每個對象定義了對象年齡計數器(Age),如果對象在Eden出生,經過第一次Minor GC後依然存活,並且能被Survival容納的話,將被移動到Survival,對象年齡設為1。對象在Survival中每熬過一次Major GC,年齡就增加1,達到一定程度(默認是15),就會被晉升到老年代。對象晉升老年代的閾值,可以通過參數-XX:MaxTenuringThreShold 指定

動態對象年齡判斷。如果在Survival空間中相同年齡所有對象的大小綜合超過了Survival空間的一半,年齡大於等於這個年齡的對象都會被晉升到老年代。無需等待年齡超過MaxTenuringThreShold指定的年齡

空間分配擔保。只要老年代的連續空間大於新生代對象總和或者歷次晉升的平均大小,就進行Major GC,否則進行Full GC。

四、虛擬機性能監控與故障處理工具1、jps

命令用法: jps [options] [hostid]

功能描述: jps是用於查看有權訪問的hotspot虛擬機的進程. 當未指定hostid時,默認查看本機jvm進程

常用參數:-lmvV

詳細說明:JAVA JPS 命令詳解

2、jstat。監視JVM內存工具。

語法結構:

Usage: jstat -help|-options

jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

例子:jstat -gcutil 25444 1000 5

詳細說明:JDK之jstat的用法

3、jinfo。查看和修改JVM運行參數

java -XX:+PrintFlagsFinal -version|grep manageable 【查看JVM中哪些參數可以被jinfo動態修改】

jinfo -flag +PrintGCDetails 105704 【修改參數 PrintGCDetails 的值】

4、jmap。命令用於生成heap dump文件

如果不使用這個命令,還可以使用-XX:+HeapDumpOnOutOfMemoryError參數來讓虛擬機出現OOM的時候自動生成dump文件。

jmap不僅能生成dump文件,還可以查詢finalize執行隊列、Java堆和永久代的詳細信息,如當前使用率、當前使用的是哪種收集器等。

詳細使用:JVM調優命令-jmap

5、jstack。Java堆棧跟蹤工具

詳細使用:使用jstack精確找到異常代碼,jstack 工具使用,性能調優

注意:dead lock問題,佔用cpu時間最多的線程,頻繁GC

入手點總結:

wait on monitor entry:被阻塞的,肯定有問題,等待synchronized鎖

runnable :注意IO線程,IO阻塞的線程

in Object.wait():注意非線程池等待,調用Object.wait()的對象

五、常見JVM配置說明1、JVM配置

1.1、G1

CPU 核數:8;內存(GB):16;磁碟(GB):400

-Xms10g

-Xmx10g

-Xss512k

-XX:MetaspaceSize=256m

-XX:MaxMetaspaceSize=256m

-XX:MaxDirectMemorySize=128M
-XX:+UseG1GC

-XX:MaxGCPauseMillis=150

-XX:+ParallelRefProcEnabled

-XX:+UnlockExperimentalVMOptions

-XX:G1MaxNewSizePercent=70

-XX:ParallelGCThreads=8

-XX:ConcGCThreads=2

1.2、CMS+ParNew

CPU 核數:4;內存(GB):8;磁碟(GB):200

-Xmx4g
-Xms4g
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m
-XX:SurvivorRatio=8
-XX:NewRatio=4
-XX:+HeapDumpOnOutOfMemoryError
-XX:+DisableExplicitGC
-XX:+PrintGCDetails
-XX:+UseConcMarkSweepGC
-XX:ParallelGCThreads=4
-XX:+CMSClassUnloadingEnabled
-XX:CMSFullGCsBeforeCompaction=1
-XX:CMSInitiatingOccupancyFraction=72

1.3、參數說明

通用參數

參數

介紹

-Xms10g

-Xmx10g

-Xms10g , 初始化堆大小。通常情況和-Xmx大小設置一樣,避免虛擬機頻繁自動計算後調整堆大小。
-Xmx10g ,最大堆大小。

-XX:MaxDirectMemorySize=128M

本地直接內存大小。適合頻繁的IO操作,例如網絡並發場景(NIO)

直接內存和堆內存比較:

-XX:+PrintGCApplicationStoppedTime

列印垃圾回收期間程序暫停的時間

-XX:+PrintGC

列印GC基本日誌

-XX:+PrintGCDetails

列印GC詳細日誌

-XX:+DisableExplicitGC

關閉System.gc()

-Xss512k

每個線程堆棧大小。每一次方法的調用都對應一個入棧和出棧

-XX:PermSize=256m

-XX:MaxPermSize=256m

-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m

JDK8以前:

JDK8及以後:

-XX:+HeapDumpOnOutOfMemoryError

JVM異常自動生成堆轉儲

CMS

參數

說明

-XX:+UserConcMarkSweepGC

年老代指定CMS垃圾回收器,新生代默認用ParNew收集

-XX:ParallelGCThreads=4

設置垃圾收集線程數

-XX:CMSInitiatingOccupancyFraction=72

老年代垃圾佔比達到這個閾值開始CMS收集,設置過高容易導致並發收集失敗,會出現SerialOld收集的情況

-XX:+UseCMSCompactAtFullCollection(默認開啟,不需要設置)

在FULL GC的時候, 對年老代開啟碎片整理功能,默認開啟

-XX:CMSFullGCsBeforeCompaction=1

控制FullGC壓縮的間隔。多少次不壓縮的FullGC之後來一次帶壓縮的

-XX:+CMSClassUnloadingEnabled

CMS收集器默認不會對永久代進行垃圾回收。如果希望對永久代進行垃圾回收,可以設置標誌

-XX:+CMSParallelRemarkEnabled

為了減少第二次暫停的時間,開啟並行remark,降低標記停頓

-XX:NewRatio=4

新生代:老年代=1:4

-XX:SurvivorRatio=8

2個survivor和eden的比值。表示2:8

默認為8,也就是說Eden佔新生代的8/10,From倖存區和To倖存區各佔新生代的1/10

G1

參數

說明

-XX:+UseG1GC

使用G1垃圾回收

-XX:MaxGCPauseMillis=150

GC最大暫停時間

-XX:+ParallelRefProcEnabled

打開並行引用處理

-XX:+UnlockExperimentalVMOptions

有些時候當設置一個特定的JVM參數時,JVM會在輸出「Unrecognized VM option」後終止。如果參數輸入是正確的,並且JVM並不識別,需要設置-XX:+UnlockExperimentalVMOptions 來解鎖參數。

-XX:G1NewSizePercent

新生代最小值比例。默認5%

-XX:G1MaxNewSizePercent=70

新生代最大值比例

-XX:ParallelGCThreads=8

STW期間,並行GC線程數

-XX:ConcGCThreads=2

並發標記階段,並行執行的線程數

G1最佳實踐

在使用G1垃圾收集器的時候遵循以下實踐可以少走不少彎路:

1、不斷調優暫停時間指標

通過XX:MaxGCPauseMillis=x可以設置啟動應用程式暫停的時間,G1在運行的時候會根據這個參數選擇CSet來滿足響應時間的設置。一般情況下這個值設置到100ms或者200ms都是可以的(不同情況下會不一樣),但如果設置成50ms就不太合理。暫停時間設置的太短,就會導致出現G1跟不上垃圾產生的速度。最終退化成Full GC。所以對這個參數的調優是一個持續的過程,逐步調整到最佳狀態。

2、不要設置新生代和老年代的大小

G1收集器在運行的時候會調整新生代和老年代的大小。通過改變代的大小來調整對象晉升的速度以及晉升年齡,從而達到我們為收集器設置的暫停時間目標。設置了新生代大小相當於放棄了G1為我們做的自動調優。我們需要做的只是設置整個堆內存的大小,剩下的交給G1自己去分配各個代的大小。

3、關注Evacuation Failure

Evacuation Failure類似於CMS裡面的晉升失敗,堆空間的垃圾太多導致無法完成Region之間的拷貝,於是不得不退化成Full GC來做一次全局範圍內的垃圾收集。

六、JVM調優案例分析與實踐

1、Minor GC、Major GC和Full GC之間的區別

每次 Minor GC 會清理年輕代的內存

Major GC 是清理老年代,Full GC 是清理整個堆空間—包括年輕代和老年代。大部分時候Major GC和Full GC區分的不是很明顯

不僅僅Major GC和Full GC會stop-the-world。所有的 Minor GC 都會觸發「全世界的暫停(stop-the-world)」,停止應用程式的線程。對於大部分應用程式,停頓導致的延遲都是可以忽略不計的。其中的真相就 是,大部分 Eden 區中的對象都能被認為是垃圾,永遠也不會被複製到 Survivor 區或者老年代空間。如果正好相反,Eden 區大部分新生對象不符合 GC 條件,Minor GC 執行時暫停的時間將會長很多。

2、常用命令

查看java進程號

兩種方式都可以查看tomcat進程號
ps -ef | grep java

jps -lmvV |grep java

結果如下:2556

查看進程內線程情況
top -Hp 2556(2556為上一步查詢出來的進程號)
找到佔用cpu時間最長的線程號:3345

得到線程號的十六進位數
printf "%x\n" 3345(輸出為a05)

使用jstack定位問題
jstack 2556 | grep a05

查看內存和swap使用情況。參考:https://www.cnblogs.com/coldplayerest/archive/2010/02/20/1669949.html

free -h

 3、問題排查

內存和SWAP問題

七、虛擬機類加載機制1、虛擬機把表示類的class文件加載到內存,經過校驗、轉換解析、初始化,最終形成可以被虛擬機直接使用的java類型,這就是虛擬機的類加載機制2、類加載的時機

使用new關鍵字實例化對象的時候、讀取一個類的靜態欄位的時候、調用類的靜態方法的時候

使用java.lang.reflect包的方式對類進行反射調用的時候

初始化類,發現其父類還未初始化,需要對父類進行初始化

3、類加載的過程

加載。通過類的全限定名獲取到定義此類的二進位字節流。將字節流所代表的靜態存儲結構轉化成方法區的運行時數據結構。在方法區生成這個類的java.lang.Class對象。加載階段和連接階段的部分內容是交叉進行的。用戶可以通過自己寫的類加載器去控制字節流的獲取方式(重寫類加載器的loadClass()方法),

驗證。是連接階段的第一步。目的是確保class文件中的二進位字節流符合虛擬機的要求,不會危及虛擬機自身安全。包括文件格式驗證、元數據驗證、字節碼驗證

準備。是連接階段的第二步。是正式為類變量分配內存空間和設置初始值的階段。這個初始值和初始化階段的賦值不同,這裡指的是變量的默認初始值。另外,如果時final修飾的變量,那麼會在準備階段賦予代碼裡指定的初始值

解析。是連接階段的第三步。是虛擬機將符號引用替換為直接引用的過程

初始化。根據程序代碼去初始化類變量和其他資源

4、類加載器第五部分 高效並發十二、Java內存模型與線程 1、硬體的效率與一致性

完成計算任務,處理器必須和內存交互才能完成,比如讀取運算數據,寫入計算結果等。這個I/O操作是很難消除的。計算的處理器和存儲設備的運算速度有幾個數量級的差距。所以現代計算機加入了一層讀寫速度儘可能接近處理器的高速緩存

高速緩存解決了處理器和內存的速度矛盾,卻引入了新的問題:內存一致性。多處理器系統中,各個處理器都有自己的高速緩存,又同時共用內存。為了解決這一問題,在讀寫內存時需要遵循緩存一致性協議。

處理器會對輸入的代碼進行亂序執行優化,類似的,Java虛擬機也存在著指令重排序優化。

2、Java內存模型

Java內存模型規定,所有的變量(這個變量和java編程中的變量有區別,它包括了實例欄位、靜態欄位。不包括局部變量和方法參數,因為後者是線程私有的)都存儲在主內存,每條線程有自己的工作內存,工作內存中保存了該線程使用到的變量的拷貝副本,線程對變量的所有操作都必須在工作內存中進行,線程間變量值得傳遞需通過主內存來完成

主內存和工作內存間交互協議,8種原子操作:

volatile是java虛擬機提供的輕量級的同步機制,對於volatile變量的特殊規則:

保證了變量對所有線程的可見性,當一個線程修改了這個變量的值,修改後的值對其他線程來說是立即可見的。普通變量,需要通過把新值會寫到主內存,其他線程從主內存讀取之後才可以看到最新值

禁止指令重排序優化。

無法保證符合操作的原子性,比如i++

通過內存屏障實現的可見性和禁止重排序。不同硬體實現內存屏障的方式不同,Java內存模型屏蔽了這些差異,由JVM來為不同的平臺生成相應的機器碼來完成。X86 處理器只會對寫-讀進行指令重排序,寫volatile變量時,會加lock總線鎖,將cpu緩存寫入主存,其他cpu的讀都會被阻塞,然後其他核的緩存某些對應數據會被標記為失效,那麼其他核下次讀的時候先讀緩存發現失效了,然後去主存讀

關於long和double類型變量的特殊規則:允許虛擬機將沒有被volatile變量修飾的64位數據的讀寫操作劃分為兩次32位的操作來進行。這點就是long和double的非原子性協定

3、Java與線程

Java虛擬機實現線程,有三種方式:

(1)通過內核線程實現。jvm中的一個線程對應一個輕量級進程,一個輕量級進程對應一個內核線程。CPU通過調度器對線程進行調度。缺點:

(2)使用用戶線程實現。不需要切換回內核態,也可以支持規模更大的線程數量。部分高性能資料庫的多線程就是使用用戶線程實現的。缺點是沒有系統內核的支援,所有問題需要自己考慮,程序實現比較複雜

(3)內核線程和用戶線程結合

(4)JVM,對於Sun JDK來說,在Windows和LInux系統下,都是使用的一對一的線程模型實現的。

Java線程調度

Java線程狀態轉換

十三、線程安全與鎖優化

高效並發是從jdk1.5 到jdk1.6的一個重要改進,HotSpot虛擬機開發團隊耗費了大量的精力去實現鎖優化技術

自旋鎖與自適應自旋。同步互斥對性能最大的影響就是線程掛起、恢復需要從用戶態切換到內核態,切換的過程會造成系統消耗。往往鎖定的代碼段執行時間非常短,為了這個短的時間去掛起和恢復是不值得的。所以提出了自旋鎖的概念,當線程申請獲取一個其他線程佔用的鎖時,這個線程不會立即掛起,而是通過一定次數的循環自旋,這個過程不會釋放cpu的控制權,自適應自旋就是根據上一次自旋的結果來決定這一次自旋的次數

鎖消除。虛擬機即時編譯器在運行時會把檢測到不可能發生共享數據競爭的鎖消除

鎖粗化。一系列的操作都是對同一個對象的加鎖和解鎖,虛擬機檢測到這種情況會將鎖的範圍擴大(粗化)

輕量級鎖

偏向鎖。如果程序中大多數的鎖總是被多個線程訪問,那偏向鎖模式就是多餘的。可以使用參數 -XX:-UseBiasedLocking來禁止偏向鎖

作者:leon66666

出處:http://www.cnblogs.com/wangzhongqiu/

【整理了java程式設計師必須讀的電子書籍,掃碼領取】

備註【1】

相關焦點

  • 好書推薦|深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版)(附PDF下載)
    —文末附本書電子版下載地址— 本書簡介這是一部從工作原理和工程實踐兩個維度深入剖析第3版在第2版的基礎上做了重大修訂,內容更豐富、實戰性更強:根據新版JDK對內容進行了全方位的修訂和升級,圍繞新技術和生產實踐新增逾10萬字,包含近50%的全新內容,並對第2版中含糊、瑕疵和錯誤內容進行了修正。
  • 【JVM虛擬機系列】高級框架JVM監控內存洩漏實戰原理
    而使用Java虛擬機是實現這一特點的關鍵。一般的高級語言如果要在不同的平臺上運行,至少需要編譯成不同的目標代碼。而引入Java語言虛擬機後,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。
  • Java虛擬機(JVM)
    2.java平臺:Java平臺由Java虛擬機和Java應用程式接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:這個任務就落到java.exe身上,java.exe的任務就是找到合適的JRE來運行java程序。
  • Java jvm 類加載 反射
    文件後,能夠在不同系統的伺服器上運行;因為java語言中有虛擬機jvm,才有了跨平臺,java為了實現跨平臺,在jvm上投入了很大的研發開發資源。jvm是java的底層,本文學習探討下java的jvm及關聯的類加載和反射知識JVMJVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用於計算設備的規範,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。Java語言的一個非常重要的特點就是與平臺的無關性。
  • 了解一下,Android 10中的ART虛擬機(2)
    在我寫《深入了理解Android Java虛擬機ART》一書的時候,我最早也是先研究的dex2oat,稿子都寫了100多頁了,但數月過後就是無法拿下,所以只能放下它,轉而去研究ART Runtime。這是我寫深入理解Android 4本書來第一次碰到這種挫折。所以,書中dex2oat的字節碼到機器碼的編譯部分在第六章,但dex2oat的源碼分析卻在第九章。
  • 高級 Java 程序猿必須掌握的 17 個 JVM 參數
    各個參數介紹1.verbose:gc表示,啟動jvm的時候,輸出jvm裡面的gc信息。格式如下:•[Loaded java.lang.Object from shared objects file]•[Loaded java.io.Serializable from shared objects file]•[Loaded java.lang.Comparable from shared objects file]•[Loaded java.lang.CharSequence
  • Java類加載器深入理解
    首先回顧一下,java虛擬機載入java類的步驟:java文件經過編譯器編譯後變成字節碼文件(.class文件),類加載器(ClassLoader
  • 入侵JVM? Java Agent原理淺析和實踐
    ,也就是Java Virtual Machine Tool Interface,就是我們這裡說的JVMTI,這裡需要注意的是:使用JNI方式調用JVMTI接口訪問目標虛擬機的大體過程入下圖:jvmti.h頭文件中定義了JVMTI接口提供的方法,但是其方法的實現是由JVM提供商實現的,比如說hotspot虛擬機其實現大部分在src\share\vm\prims
  • 深入理解計算機系統系列
    本系列主要參考大學計算機系的課程安排,從數字晶片的基礎門電路說起、再到cpu的主要架構、簡單的彙編、高級語言及編譯器、作業系統的相關知識等。以上基礎文章寫完後會結合當前特別流行的虛擬概念講一下諸如jvm、kvm、docker的幾種虛擬化。目的是讓大家對計算機如何列印出「hello world」有一個貫穿軟硬體的簡單認識。
  • Java 處理 Exception 的 9 個最佳實踐!
    不僅僅初學者很難理解,即使一些有經驗的開發者也需要花費很多時間來思考如何處理異常,包括需要處理哪些異常,怎樣處理等等。這也是絕大多數開發團隊都會制定一些規則來規範對異常的處理的原因。而團隊之間的這些規範往往是截然不同的。本文給出幾個被很多團隊使用的異常處理最佳實踐。1.
  • Java 中處理 Exception 的最佳實踐
    不僅僅初學者很難理解,即使一些有經驗的開發者也需要花費很多時間來思考如何處理異常,包括需要處理哪些異常,怎樣處理等等。這也是絕大多數開發團隊都會制定一些規則來規範對異常的處理的原因。而團隊之間的這些規範往往是截然不同的。本文給出幾個被很多團隊使用的異常處理最佳實踐。 1. 在Finally塊中清理資源或者使用try-with-resource語句。
  • Java處理Exception的9個最佳實踐
    不僅僅初學者很難理解,即使一些有經驗的開發者也需要花費很多時間來思考如何處理異常,包括需要處理哪些異常,怎樣處理等等。這也是絕大多數開發團隊都會制定一些規則來規範對異常的處理的原因。而團隊之間的這些規範往往是截然不同的。本文給出幾個被很多團隊使用的異常處理最佳實踐。1.
  • 電子書┃Java程式設計師入門必讀書籍,你讀過幾本?
    《深入理解Java虛擬機》JVM高級特性與最佳實踐超級暢銷書,6個月5次印刷,Java程式設計師必備,深刻揭示JVM的工作原理注重實現,以解決實踐中的疑難問題為首要目的,包含大量經典案例和最佳實踐內容簡介:作為一位Java程式設計師,你是否也曾經想深入理解Java虛擬機,但是卻被它的複雜和深奧拒之門外?
  • Java 處理 Exception 的 9 個最佳實踐
    不僅僅初學者很難理解,即使一些有經驗的開發者也需要花費很多時間來思考如何處理異常,包括需要處理哪些異常,怎樣處理等等。這也是絕大多數開發團隊都會制定一些規則來規範對異常的處理的原因。而團隊之間的這些規範往往是截然不同的。1.
  • Java程式設計師入門必讀書籍,你讀過幾本?
    《深入理解Java虛擬機》JVM高級特性與最佳實踐超級暢銷書,6個月5次印刷,Java程式設計師必備,深刻揭示JVM的工作原理注重實現,以解決實踐中的疑難問題為首要目的,包含大量經典案例和最佳實踐內容簡介:作為一位Java程式設計師,你是否也曾經想深入理解Java虛擬機,但是卻被它的複雜和深奧拒之門外?
  • 學Java最權威、最暢銷、口碑最佳的三本書!
    無論你從事什麼工作,在什麼類型的公司,就是阿里,百度,騰訊等大廠每天都在考慮java 虛擬機。我也親自讀過,有很多案例,原理講解很清楚。學會它,你的年薪無上線,無論您是開發,測試,運維,還是寫網站等等工作。一百萬個理由推薦入手。這是一部從工作原理和工程實踐兩個維度深入剖析JVM的著作,是計算機領域公認的經典。
  • JVM、GC 大串講,面試夠用了
    JVM JRE JDK的關係JVM(java虛擬機),將 .class 文件中的字節碼指令進行識別並調用作業系統向上的 API 完成動作。JVM不僅可以運行java程序,只要是能編譯成.class的文件都能運行。JRE (Java 運行時環境),包含了jvm和core lib。JDK (Java 開發工具包),它集成了jre和一些工具。
  • Java 中 9 個處理 Exception 的最佳實踐
    不僅僅初學者很難理解,即使一些有經驗的開發者也需要花費很多時間來思考如何處理異常,包括需要處理哪些異常,怎樣處理等等。這也是絕大多數開發團隊都會制定一些規則來規範對異常的處理的原因。而團隊之間的這些規範往往是截然不同的。本文給出幾個被很多團隊使用的異常處理最佳實踐。1.
  • JVM 是由哪幾部分組成的?
    JVM(Java virtual machine)Java 虛擬機是Java程序運行的基礎。
  • 借華為方舟編譯器,談一下Android ART JVM的幾個有趣知識點
    借著華為方舟虛擬機,簡單提一下jvm裡幾個有趣的知識點。