易燃物匯聚 YouTube 有聲書與書評
深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版) 封面

深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版)

周志明

4.3萬人在讀
49.6萬字總字數
¥31.99定價

有聲書與視頻解讀 YouTube視頻解讀 10

內容簡介

內容介紹 這是一部從工作原理和工程實踐兩個維度深入剖析JVM的著作,是計算機領域公認的經典,繁體版在臺灣也頗受歡迎。 自2011年上市以來,前兩個版本累計印刷36次,銷量超過30萬冊,兩家主要網絡書店的評論近90000條,內容上近乎零差評,是原創計算機圖書領域不可逾越的豐碑。 第3版在第2版的基礎上做了重大修訂,內容更豐富、實戰性更強:根據新版JDK對內容進行了全方位的修訂和升級,圍繞新技術和生產實踐新增逾10萬字,包含近50%的全新內容,並對第2版中含糊、瑕疵和錯誤內容進行了修正。 全書一共13章,分爲五大部分: 第壹部分(第1章)走近Java 系統介紹了Java的技術體系、發展歷程、虛擬機家族,以及動手編譯JDK,瞭解這部分內容能對學習JVM提供良好的指引。 第二部分(第2~5章)自動內存管理 詳細講解了Java的內存區域與內存溢出、垃圾收集器與內存分配策略、虛擬機性能監控與故障排除等與自動內存管理相關的內容,以及10餘個經典的性能優化案例和優化方法; 第三部分(第6~9章)虛擬機執行子系統 深入分析了虛擬機執行子系統,包括類文件結構、虛擬機類加載機制、虛擬機字節碼執行引擎,以及多個類加載及其執行子系統的實戰案例; 第四部分(第10~11章)程序編譯與代碼優化 詳細講解了程序的前、後端編譯與優化,包括前端的易用性優化措施,如泛型、主動裝箱拆箱、條件編譯等的內容的深入分析;以及後端的性能優化措施,如虛擬機的熱點探測方法、HotSpot 的即時編譯器、提前編譯器,以及各種常見的編譯期優化技術; 第五部分(第12~13章)高效併發 主要講解了Java實現高併發的原理,包括Java的內存模型、線程與協程,以及線程安全和鎖優化。 全書以實戰爲導向,通過大量與實際生產環境相結合的案例分析和展示瞭解決各種Java技術難題的方案和技巧。

讀者書評

★★★★★

JVM 調優思路 分析系統核心業務 估算每秒請求數 預估核心業務每秒處理了多少請求 結合服務器負載,估算部署了多少臺服務器 每臺服務器處理了多少請求 估算每秒佔用內存 估算每次請求耗時多久 估算每個請求佔用內存 估算每秒佔用內存 考慮到關聯對象再擴大 10-20 倍,考慮到其他業務在擴大 5-10 倍,所以每秒佔用內存再 * 20 * 10 分配 JVM 內存 結合機器配置分配內存空間,首先需要給元空間和虛擬機棧預留足夠的空間 元空間一般設置幾百兆,比如 256 M 線程棧一般設置 512 KB - 1M,例如每秒 500 次請求,那麼需要給虛擬機棧預留 500 MB 設置堆內存大小 估算 GC 頻率 年輕代 GC 頻率 = Eden 內存大小 / 每秒佔用內存 一般幾分鐘或幾十分鐘觸發一次 Young GC,一次 Young GC 耗時幾毫秒或者幾十毫秒都是正常的 估算 Young GC 後存活對象大小 根據 Survivor 空間大小與 YGC 存活對象大小估算每次進入老年代對象大小 估算 Full GC 頻率,一般 Full GC 的頻率在一天幾次、幾天一次、甚至不出現 Full GC 最好,一次 Full GC 耗時幾百毫秒都是正確的 內存分配和 Young GC 優化 保證 Survivor 區空間足夠,避免 YGC 存活對象太多不能放進 Survivor 區,避免動態年齡判斷導致對象快速進入老年代 提高 Survivor 區大小,儘量讓 YGC 後存活對象小於 Survivor 區的一半 減少 Young GC 頻率,增大內存,增大新生代大小 提升機器配置,增大堆內存,調整新生代與老年代比例,適當增大新生代大小,調整 Eden 區和 Survivor 老年代優化 調整大對象閾值,避免頻繁有大對象直接進入老年代(減少分代年齡,避免長期存活對象需要很多次複製後才能進入老年代) 減少 Full GC/OldGC 的頻率(根據實際情況適當增大老年代內存,檢查是否有內存泄漏讓老年代駐留了大量對象導致每次進入少量對象就觸發 Full GC) CMS 垃圾回收避免併發失敗觸發 Full GC(調整 CMS 觸發 Old GC 的閾值,這個一般不需要調整) 選擇合適的垃圾回收器 普通的系統一般 ParNew + CMS 組合的垃圾回收器 大內存機器選擇 G1 垃圾回收器,設置合理的停頓時間 估算瞬時高峰壓力的情況 估算瞬時高峰壓力時的內存模型 根據實際情況下將正常情況下的估算值再擴大流量,估算高峰壓力時的配置 談談 JMM 的理解? JMM 是 Java 內存模型,是一種抽象的模型,被定義出來屏蔽各種硬件和操作系統的內存訪問差異 JMM 定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存,每個線程都有一個私有的本地內存,本地內存存儲了該線程以讀/寫共享變量的副本 本地內存是 JMM 的一個抽象概念,並不真實存在,它其實涵蓋了緩存、寫緩衝區、寄存器以及其他的硬件和編譯器優化 說垃圾回收算法 複製算法的核心思想就是把內存空間分爲兩塊,每次只使用一塊,在垃圾回收時將正在使用的內存中的活躍對象複製到未被使用的內存塊中,之後清除正在使用的內存塊中的所有對象,兩塊內存交替使用,從而完成垃圾對象的回收, 複製算法沒有標記和清除過程,實現簡單,運行高效,內存從一塊空間複製到另一塊空間可以保證空間的連續性,不會出現內存碎片問題,但是複製算法需要雙倍內存空間,而且需要移動對象,這就涉及修改對象引用地址值的問題,另外,對於 G1 這種把內存劃分爲大量 region 的垃圾收集器,意味着需要維護各個 region 之間對象引用關係,在時空消耗方面不低 如果垃圾對象很少,複製算法效率低 標記清除算法: 從引用根節點開始遍歷,標記所有被引用的對象 對堆內存從頭到尾進行線性遍歷,如果發現某個對象爲不可達對象,則將其回收 優點是不需要移動對象,簡單容易實現,缺點是用戶體驗差,有內存碎片,清除對象不是把對象置空,而是把需要清除的對象地址保存在空閒的地址列表中,下次有新對象申請時,判斷某塊內存空間是否充足,如果充足,新的對象會覆蓋標記爲垃圾對象,從而實現內存的複用,但是因爲可用內存不連續問題,在大對象申請內存時,需要花費更多時間去尋找合適的位置,甚至失敗 標記壓縮算法:就地壓縮內存空間,內存利用率高,不存在內存碎片的問題,但是多次遍歷內存空間,算法的執行效率不高 遍歷內存空間,標記內存空間的活躍對象 遍歷內存空間,計算所有活躍對象壓縮後的位置 遍歷內存空間,更新所有活躍對象成員變量壓縮後的位置 遍歷內存空間,移動活躍對象到第二步計算好的位置,由於此時對象內部的成員變量已經更新完成,因此移動對象後的所有引用關係都是正確的 繼承時父子類的初始化順序是怎麼樣的? 父類---靜態變量 父類--靜態初始化塊 子類--靜態變量 子類--靜態初始化塊 父類--變量 父類--初始化塊 父類--構造器 子類--變量 子類---初始化塊 子類---構造器 說說類加載雙親委派模型 加載將網絡、某個磁盤位置下的類的 class 二進制字節流讀進來,在內存中生成一個代表這個類的 Java.lang.Class 對象放入到元空間來實現類的加載,這個階段我們程序員可以干預,我們可以自定義類加載器實現類的加載 驗證:驗證 Class 文件的字節流信息包含的信息符合 《Java 虛擬機規範》的全部約束要求,保證虛擬機的安全 準備:準備類變量賦予默認值,int 爲 0,long 爲 0L,boolean 爲 false,引用類型爲 null,常量賦予正式值 解析:把符號引用翻譯爲直接引用 初始化:當我們 new 一個類的對象,訪問一個類的靜態屬性,修改一個類的靜態屬性,調用一個類的靜態方法,用反射 API 對一個類進行調用,初始化當前類,其父類也會被初始化.......這些都會觸發類的初始化 使用:使用這個類 卸載: 該類所有的實例都已經被 GC,也就是 JVM 中不存在該 Class 的任何實例 加載該類的 ClassLoader 已經被 GC 該類的 Java.lang.class 對象沒有在任何地方被引用,如果不能在任何地方通過反射訪問該類的方法 類加載過程被分爲三個步驟,5個階段: 分爲加載、驗證、準備、解析以及初始化,加載、驗證、準備、解析這四個階段的順序是確定的,但是解析階段不一定,爲了支持 Java 語言運行時綁定特性,在某些情況下可以在初始化階段之後再開始 加載階段分爲三步: 通過完全限定名查找定位 .class 文件,並獲取其二進制字節流數據 把字節流所代表的靜態存儲結構轉換爲運行時數據結構 在堆中間中爲其創建一個Class對象,作爲程序訪問這些數據的入口 驗證階段: 文件格式驗證:驗證字節流是否符合 Class 文件格式的規範 比如 CA/FE/BA/BE 魔數驗證 主次版本號驗證 元數據驗證:堆字節碼描述的信息進行語義分析,以保證其描述的信息符合 Java 語言規範的要求 類是否有父類,除了Object之外,所有的類都應該有父類 類的父類是否繼承了不允許被繼承的類(被final修飾的類) 如果這個類不是抽象類,是否實現了其父類或接口中要求實現的所有方法 類的字段/方法是否與父類的存在衝突。例如方法參數都一樣,返回值卻不同 字節碼驗證:通過數據流和控制流分析,確定程序語義合法且符合邏輯\ 對類的方法體進行校驗分析,保證在運行時不會做出危害虛擬機的行爲 保證任意時刻操作數棧的數據類型與指令代碼序列都能配合工作,不會出現類似於在操作數棧放了一個int類型的數據,讀取時卻按照long類型加載到本地變量表中的情況 保障任何跳轉指令都不會跳轉到方法體之外的字節碼指令上 符號引用驗證:確保後續的解析動作能正確執行 通過字符串描述的全限定名是否能找到對應的類 符號引用中的類、字段、方法的可訪問性是否可被當前類訪問 準備階段主要是爲類中聲明的靜態變量分配內存空間,並將其初始化成默認值(零值) 解析階段主要是把類中對常量池內的符號引用轉換爲直接引用的過程 初始化步驟中,主要是對類的靜態變量賦予正確的初始值,也就是在聲明靜態變量時指定的初始化值以及靜態代碼塊中的賦值。本質上就是執行類構造器方法<clinit>()的過程。而觸發初始化的情況分爲六種 ①遇到new/getstatic/putstatic/invokestatic這四條字節碼指令時觸發 new:使用new關鍵字創建一個實例對象 getstatic:讀取一個靜態字段 putstatic:設置一個靜態字段 invokestatic:調用一個靜態方法 ②對類型進行反射調用,如果類型沒有經過初始化,則會觸發初始化 ③初始化一個類的時候,發現父類沒有初始化,則先觸發父類初始化 ④虛擬機啓動時,需指定一個要執行的主類(包含main方法的那個類),虛擬機會初始化這個主類(如SpringBoot的啓動類) ⑤當使用JDK7中新加入的動態語言支持時,如果一個java.lang.invoke.MethodHandler實例最後的解析結果爲REF_getStatic,REF_putStatic,REF_invokeStatic,REF_newInvokeSpecial四種類型的方法句柄,並且這個方法對應的類沒有進行初始化,則先觸發其初始化 ⑥當一個接口中定了JDK8新加入的默認方法時,如果這個接口的實現類發生了初始化,要先將接口進行初始化 當一個類完整的經過了類加載過程之後,在內存中已經生成了Class對象,同時在Java程序中已經通過它開始創建實例對象使用時,該階段被稱爲使用階段。 而當一個Class對象不再被任何一處位置引用,即不可觸及時,Class就會結束生命週期,該類加載的數據也會被卸載 JVM 存在三個默認的類加載器: BootstrapClassLoader 啓動類加載器 加載 jre\lib\rt.jar ExtClassLoader 擴展類加載器 jre\lib\ext AppClassLoader 應用程序類加載器 加載用戶類路徑下的所有類庫 AppClassLoader 的父加載器是 ExtClassLoader,ExtClassLoader 的父加載器是 BootstrapClassLoader JVM 在加載一個類時,會調用 AppClassLoader 的 loadClass 方法來加載類 不過在這個方法中,會先使用 EXTClassLoader 的 loadClass 的方法來加載類 同樣 ExtClassLoader 的 loadClass 方法中會先使用 BootstrapClassLoader 來加載類 如果 BootstrapClassLoader 加載到類就直接成功 如果 BoostrapClassLoader 沒有加載到,那麼 ExtClassLoader 就會嘗試自己加載類 如果沒有加載到,那麼則會由 AppClassLoader 來加載這個類 所以,雙親委派指的是,JVM 在加載類時,會委派給 Ext 和 Bootstrap 來進行加載,如果沒有加載到才由自己進行加載,雙親委派的好處就是 確保安全,避免 Java 核心類庫被修改 避免重複加載 保證類的唯一性,如果自己寫了 Java.lang.String 的類去運行,拋出異常。

★★★★★

# 你理解了JVM的運行機制有啥用 ## 定論 定論:這本書常讀常新,推薦作爲Java入門的枕邊書,作爲科學工程,知其然當然也要知其所以然.讀<out of gobi>時作者說中醫持續近千年,結構相對穩定,因其觀其然,而西醫近百年把醫學提高到可以去病延命的地步是其探究至分子原子的微觀原理所推動,探究原理纔是思維和認識進步的根本途徑,所謂觸類旁通.知道原理的你和不知的你是兩個人. ## 以圖記憶 讀完這本書,需要在腦海裏留存幾張圖: - JVM運行時數據區構成圖 - Java垃圾回收器線程運行圖 - Java垃圾回收器搭配圖 - 樣例Eclipse啓動和編譯調優heapdump變化圖 - class文件構成圖 - class文件加載流程圖 - 類加載的雙親委派和Tomcat加載模型 - 類編譯流程(c1&C2) - 解釋執行器與編譯執行器運行流程圖 - JMM內存模型 - Java的線程模型 - java synchronize鎖的升級流程 ## key words 1. 運行時數據區關心分配.分配注意Tlab和棧上分配,配合不同的垃圾收集器有FreeList分配和bump the poniter. 2. 運行時數據區回收,主要集中在垃圾回收器的實現,關鍵如下 - 分代收集: - 新生代 - eden+from+to - yGC的區域,表現爲短期存活的對象(未超過大對象閾值)在此處分配 - from to區域的切換 - 老年代 - 新生代超過markword中age閾值 - 創建時超過新生代最大閾值 - 跨代引用減少掃描 - memory set -> 卡表 -> 寫屏障 - 矩陣 -> regions[n][m] == 1,此種標記爲n引用m - 併發+並行收集:併發是多個GC線程,並行是與用戶線程並行 - 並行(concurrent) -> 垃圾收集期間,應用程序暫定;併發(parallel),GC和UT一起 - serial(old) -> 串行(客戶端模式下默認) - parallel(scavenge/old) -> 並行 - CMS(parNew已被內化爲其默認新生代) -> 併發(初始標記+重新標記 stw) - G1 -> 細化region+cosplay,基於成本考慮的collection set收集 - 收集算法 - 標記複製 -> 針對新生代 - 標記整理 -> 有壓縮過程,移動對象就會慢 - 標記清除 -> CMS,清除不整理產生碎片,需要一次整理 3. class文件加載與初始化 - 加載:全路徑載入成二進制流+靜態結構變成運行時數據結構+class對象在方法區生成class對象,作爲訪問該類的入口 - 處理 - 校驗 -> 格式 - 準備 -> 0值 - 鏈接 -> 符號引用替換爲直接引用 - 初始化,調用<clinit>,對已經放入方法區的class對象進行類屬性的賦值 - 使用,new對象將會調用<init>使用構造器對實例進行構造 - 卸載 -> 類加載器+class都不再使用時可卸載 4. 類的編譯和執行 - 編譯 - 前端編譯器 -> java 2 class - C1編譯器 -> 客戶端編譯器 -> 空值檢查優化 - C2編譯器 -> 服務端編譯器 -> 應用大部分優化(內聯/逃逸分析後的棧上分配/標量替換/公共字表達式消除等) - 執行 - 編譯執行 -> 快,激進 - 解釋執行 -> 加載內容少,快,作爲逃生回退方法 - 轉換閾值 - 調用計數器 - 回邊計數器 - 衰減 5. JMM - Java內存模型,與運行時數據區關係不大 - 其目的是指明對內存的操作:符合happen-before是有序的;其餘是不可知的 - happen-before原則 - 創建先於使用 - 創建先於終止 - synchronize/volatile先寫後用等 - 控制流 6. 高併發 - 線程安全的分類 - 線程安全的實現 - 互斥加鎖 - 非阻塞同步 -> CAS - 線程私有 -> threadLocal ## 回想 關鍵不在上面的骨架,很多內容摘出來還是要細細深入,咀嚼幾遍才能消化的了,另外書中的內容較全就不會樣樣深入,好在有樣例是不錯的思路.題外話,人生有很多機會走新路,我第一次拿到這本書是在18年,而今年才第一次讀完,更覺得人生有命數,說是緣分,實則是心性不夠,還未開悟.只是未來不知何往,讀書總歸有益,聊以自慰的是7年後的我好過當時的自己,也好過昨天的自己,喫新東西才能成新人,走新路才能賞新景.

★★★★★

讀了 20% 忍不住先來寫一下評價:書內容的覆蓋面和作者的知識底蘊都是毋庸置疑的。但是幾乎不對內容進行列表匯總和新老內容混寫是難以接受的。 - 不列表匯總:比如一個東西的優缺點,正常來說應該我們分離出兩個列表,讓用戶清晰看到,然後再根據金字塔理論展開講解,最後讀者只需要記錄列表標題即可。但是作者全是混在一起去講的,需要讀者從中提取。 - 新老內容混寫:已經過時的東西,前面一頓說優缺點,後面一看整個東西都已經 deprecated 或者被移除了,大到整個特性,小到小的開關。這讓讀者想記住哪些是目前最新可用的變得非常困難。我傾向這個發展過程完全已經作爲一個附錄放在最後,讓感興趣的讀者去了解纔是。而不是放在一起混淆視聽。 更新:讀完了(跳過了中間幾章以後按需再讀),被作者的 JVM 知識深度所折服,雖然由於上述原因讀起來有些乏力,但瑕不掩瑜,評價從一般改爲推薦。

★★★★★

比較優秀的一本書,作者的語言功底不錯,讀起來基本不會因爲表達含糊不清而看不懂。 本書從原理方面全鏈條講解了Java是編譯成字節碼的流程、java class文件有哪些內容、jvm的運行時內存組成、jvm是怎麼解釋class文件的、如何提高java字節碼的運行效率、垃圾回收、jvm調優等內容。 由於是原理類書籍,可能是考慮到不同jvm底層實現不同,本書並不涉及jvm是如何在C++層面處理諸如運行時常量池、堆棧這些底層數據結構的。因此,本書其實是一本偏入門的書籍:正如學習windows/Linux實現首先要學習操作系統原理,本書也爲學習虛擬機具體實現打下基礎。如果想要進一步學習jvm的讀者,需要找具體虛擬機源碼剖析類書籍進行學習。

★★★★★

囫圇吞棗地看完了,前面講述關於“Hotspot虛擬機”、“GC垃圾收集器”和“JVM檢測指標工具”模塊基本上都能有所收穫,畢竟校招時八股天天背,但後面深入到“類”以及更深入的內容時,整個人就開始犯迷糊,根本看不懂。 可能我現在這個水平還不夠,等後面造詣深之後再重新翻看。

★★★★★

去年看了第二版的,沒堅持看完,今年邊畫思維導圖邊看,終於堅持看完了,有了一個整體印象。 內存區域介紹的很細緻,垃圾收集部分詳細講了最新的幾個垃圾收集器,後續如果升級JDK版本可以試用下;新加的JVM調優的案例分析,很不錯,可以開拓大家的思路,將前面學的東西應用起來;類加載部分是第一次讀,有些喫力,後續多讀幾遍應該也是可以理解的;JAVA線程部分講的很細緻,之前學習NIO模糊的地方突然豁然開朗了;線程安全部分講的較少,但基礎知識點都有羅列,如果想詳細學習可以看一些進階的書。 總之,很不錯的一本書,但這本真的挺厚的,能堅持看完需要一些基礎和毅力。

★★★☆☆

讀完這本書其實感覺並沒有記住多少東西,只不過是對gc,類加載流程有了更好清晰的認識罷了,特別是內存屏障和卡表這塊的,實踐部分認識到了訪問者模式,也許在某一天,我會因爲某個參數某行代碼想起書中的某些內容.

★★★★★

書中兼顧理論原理與工程實踐,搭配大量案例、代碼和調優實戰,把晦澀的虛擬機底層知識講得條理清晰,是 Java 後端開發進階、面試準備、線上性能問題排查的核心參考書。

★★★★★

內容太過深奧,實屬罕見教材,鄙人拜讀,感慨萬千,閱讀了前面兩章,內容均是專業級描述,故很難想象。有實戰也有理論描述,是一本好書。

★★★☆☆

號稱重寫50的內容,看完感覺比較失望,能重寫20%就不錯了,書的內容還是不錯的,可惜實驗用例還在比較5 6 7 ,後面的優化基本還是在談6,不希望你升級到JDK11,至少升級到JDK8吧

書籍信息

作者周志明
出版社機械工業出版社
出版時間2021-05-16
ISBN9787111641247
分類計算機-編程設計
格式EPUB
完結狀態連載中
章節數295