JVM 看這一篇就夠了

送你們如下學習資料,文末有領取方式




初識JVM規範

從三種角度認識JVM

圖片

在這裏插入圖片描述java

JVM概述

  • JVM:Java Virtual Machine,也就是Java虛擬機
  • 所謂虛擬機是指:經過軟件模擬的具備完整硬件系統功能的、運行在一個徹底隔離環境中的計算機系統
  • JVM是經過軟件來模擬Java字節碼的指令集,是Java程序的運行環境

JVM主要功能

  • 經過 ClassLoader 尋找和裝載 class 文件
  • 解釋字節碼成爲指令並執行,提供 class 文件的運行環境
  • 進行運行期間的內存分配和垃圾回收
  • 提供與硬件交互的平臺

虛擬機是Java平臺無關的保障

圖片

在這裏插入圖片描述算法

JVM規範做用及其核心

JVM規範做用

  • Java 虛擬機規範爲不一樣的硬件提供了一種編譯Java技術代碼的規範
  • 該規範使Java軟件獨立於平臺,由於編譯時針對做爲虛擬機的「通常機器」而作
  • 這個「通常機器」可用軟件模擬並運行於各類現存的計算機系統,也可用硬件來實現

JVM規範定義的主要內容

  • 字節碼指令集
  • Class文件的格式
  • 數據類型和值
  • 運行時數據區
  • 棧幀
  • 特殊方法
  • 類庫
  • 異常
  • 虛擬機的啓動、加載、連接和初始化

Class字節碼解析

Class文件格式概述

  • Class文件是JVM的輸入,Java虛擬機規範中定義了Class文件的結構,Class文件是JVM實現平臺無關、技術無關的基礎
  1. 無符號數:基本數據類型,以u一、u二、u四、u8來表明幾個字節的無符號數
  2. 表:由多個無符號和其餘表構成的符合數據類型,一般以 "\_info"結尾
  3. Class文件是一組以8字節爲單位的字節流,各個數據項目按序緊湊排列
  4. 對於佔用空間大於8字節的數據項,按照高位在前的方式分割成多個8字節進行存儲
  5. Class文件格式裏面只有兩種類型:無符號數、表

Class文件的格式

  • javap工具生成非正式的 」虛擬機彙編語言「,格式以下:
  • [[]…]] [comment]
  • 是指令操做碼在數組中的下標,該數組以字節形式來存儲當前方法的Java虛擬機代碼;也能夠是至關於方法起始處的字節偏移量
  • 是指令的助記碼、是操做數、是行尾的註釋

Class文件格式說明

  • constant\_pool\_count:是從1開始的
  • 不一樣的常量類型,用tag來區分,它後面對應的 info 結構是不同的
  • L表示對象,[ 表示數組、V表示void
  • stack:方法執行時,操做棧的深度
  • Locals:局部變量所需的儲存空間,單位是slot
  • slot是虛擬機爲局部變量分配內存所使用的最小單位
  • args\_size:參數個數,爲1的話,因實例方法默認會傳入this,locals也會預留一個slot來存放

ASM

spring

參考博文shell

ASM概述

  • ASM是一個Java字節碼操縱框架,它能被用來動態生成類或者加強既有類的功能
  • ASM能夠直接產生二進制class文件,也能夠在類被加載入虛擬機以前動態改變類行爲,ASM從類文件中讀入信息後,可以改變類行爲,分析類信息,甚至能根據要求生成新類
  • 目前許多框架如 cglib、Hibernate、spring 都直接或間接地使用ASM操做字節碼

ASM編程模型

  • Core API:提供了基於事件形式的編程模型。該模型不須要一次性將整個類的結構讀取到內存中,所以這種方式更快,須要的內存更少,但這種編程方式難度較大
  • Tree API:提供了基於樹型的編程模型。該模型須要一次性將一個類的完整結構所有讀取到內存中,因此這種方法須要更多的內存,這種編程方式較簡單

ASM的Core API

  • ASM Core ApI 中操縱字節碼的功能基於 ClassVisitor 接口。這個接口中的每一個方法對應了 class 文件中的每一項
  • ASM 提供了三個基於 ClassVisitor 接口的類來實現 class 文件的生成和轉換
  • ClassReader:ClassReader 解析一個類的 class 字節碼
  • ClassAdapter:ClassAdapter 是 ClassVisitor 的實現類,實現要變化的功能
  • ClassWriter:ClassWriter 也是 ClassVisitro 的實現類,能夠用來輸出變化後的字節碼
  • ASM 給咱們提供了 ASMifier 工具來幫助開發,可以使用ASMifier 工具生成 ASM 結構來對比

類加載、鏈接和初始化

類加載和類加載器

  • 類被加載到 JVM 開始,到卸載出內存,整個生命週期如圖:

圖片

在這裏插入圖片描述數據庫

  • 加載:查找並加載類文件的二進制數據
  • 鏈接:就是將已經讀入內存的類的二進制數據合併到 JVM 運行時環境中去,包含如下步驟:
  • 驗證:確保被加載類的正確性
  • 準備:爲類的 靜態變量 分配內存,並初始化
  • 解析:把常量池中的符號引用轉換成直接引用
  • 初始化:爲類的靜態變量賦初始值

類加載要完成的功能

  • 經過類的全限定名來獲取該類的二進制字節流
  • 把二進制字節流轉化爲方法區的運行時數據結構
  • 在堆上建立一個 java.lang.Class 對象,用來封裝類在方法區內的數據結構,並向外提供了訪問方法區內數據結構的接口

加載類的方式

  • 最多見的方式:本地文件系統中加載、從jar等歸檔文件中加載
  • 動態的方式:將 java 源文件動態編譯成 class
  • 其餘方式:網絡下載、從專有數據庫中加載等等

類加載器

  • Java 虛擬機自帶的加載器包括如下幾種:
  • 啓動類加載器(BootstrapClassLoader)
  • 平臺類加載器(PlatformClassLoader) JDK8:擴展類加載器(ExtensionClassLoader)
  • 應用程序類加載器(AppClassLoader)
  • 用戶自定義的加載器:是 java.lang.ClassLoader 的子類,用戶能夠定製類的加載方式;只不過自定義類加載器其加載的順序是在全部系統類加載器的最後

類加載器的關係

圖片

在這裏插入圖片描述編程

類加載器使用

類加載器說明

  • 啓動類加載器:用於加載啓動的基礎模塊類,好比:java.base、java.management、java.xml等
  • 平臺類加載器:用於加載一些平臺相關的模塊,好比:java.scripting、java.compiler *、java.corba *等
  • 應用程序類加載器:用於加載應用級別的模塊,好比:jak.compiler、jdk.jartool、jdk.jshell 等等;還加載 classpath 路徑中的全部類庫
  • JDK8:啓動類加載器:負責將<JAVA\_HOME>/lib,或者 -Xbootclasspath 參數指定的路徑中的,且是虛擬機識別的類庫加載到內存中(按照名字識別,好比 rt.jar,對於不能識別的文件不予裝載)
  • JDK8:擴展類加載器:負責加載 <JRE\_HOME>/lib/ext,或者 java.ext.dirs 系統變量所指定路徑中的全部類庫
  • JDK8:應用程序類加載器:負責加載 classpath 路徑中的全部類庫
  • Java 程序不能直接引用啓動類加載器,直接設置 classLoader 爲 null,默認就使用啓動類加載器
  • 類加載器並不須要等到某個類「首次主動使用」的時候才加載它,JVM規範容許類加載器在預料到某個類將要被使用的時候就預先加載它
  • 若是在加載的時候 .class 文件缺失,會在該類首次主動使用時報告 LinkageError 錯誤,若是一直沒有被使用,就不會報錯

雙親委派模型

  • JVM中的 ClassLoader 一般採用雙親委派模型,要求除了啓動類加載器外,其他的類加載器都應該有本身的父級加載器。這裏的父子關係是組合而不是繼承,工做過程以下:
  1. 一個類加載器接收到類加載請求後,首先搜索它的內建加載器定義的全部「具名模塊」
  2. 若是找到了合適的模塊定義,將會使用該加載器來加載
  3. 若是 class 沒有在這些加載器定義的具名模塊中找到,那麼將委託給父級加載器,直到啓動類加載器
  4. 若是父級加載器反饋它不能完成加載請求,好比在它的搜索路徑下找不到這個類,那子類加載器才本身來加載
  5. 在類路徑下找到的類將成爲這些加載器的無名模塊
  • 雙親委派模型對於保證 Java 程序的穩定運做很重要,能夠避免一個類被加載屢次
  • 實現雙親委派的代碼在 java.lang.ClassLoader 的 loadClass() 方法中,若是自定義類加載器的話,推薦覆蓋實現 findClass() 方法
  • 若是有一個類加載器能加載某個類,稱爲 定義類加載器,全部能成功返回該類的 Class 的類加載器 都被稱爲初始類加載器

雙親委派模型的說明

  • 雙親委派模型對於保證 Java 程序的穩定運做很重要
  • 實現雙親委派的代碼 java.lang.ClassLoader 的 loadClass() 方法中,若是自定義類加載器的話,推薦覆蓋實現 findClass() 方法
  • 若是有一個類加載器能加載某個類,稱爲 定義類加載器,全部能成功返回該類的 Class 的類加載器 都被稱爲 初始化加載器
  • 若是沒有指定父加載器,默認就是啓動類加載器
  • 每一個類加載器都有本身的命名空間,命名空間由該類加載器及其全部父加載器所加載的類構成,不一樣的命名空間,能夠出現類的全路徑名 相同的狀況
  • 運行時包由同一個類加載器的類構成,決定兩個類是否屬於同一個運行時包,不只要看全路徑名是否同樣,還要看定義類加載器是否相同。只有屬於同一個運行時包的類才能實現相互包內可見

破壞雙親委派模型

  • 雙親委派模型有一個問題:父加載器沒法向下識別子加載器加載的資源
  • 爲了解決這個問題,引入了線程上下文類加載器,能夠經過 Thread 的 setContextClassLoader() 進行設置
  • 實現熱部署時,好比 OSGI 的模塊化熱部署,它的類加載器就再也不是嚴格按照雙親委派模型,不少可能就在平級的類加載器中執行了

類鏈接和初始化

類鏈接主要驗證的內容

  • 類文件結構檢查:按照 JVM 規範規定的類文件結構進行
  • 元數據驗證:對字節碼描述的信息進行語義分析,保證其符合 Java 語言規範要求
  • 字節碼驗證:經過對數據流和控制流進行分析,確保程序語義是合法和符合邏輯的。這裏主要對方法體進行校驗
  • 符號引用驗證:對類自身之外的信息,也就是常量池中的各類符號引用,進行匹配校驗

類鏈接中的準備

  • 爲類的 靜態變量 分配內存,並初始化

類鏈接中的解析

  • 解析就是把常量中的符號引用轉換成直接引用的過程,包括:符號引用:以一組無歧義(惟一)的符號來描述所引用的目標,與虛擬機的失效無關
  • 直接引用:直接執行目標的指針、相對偏移量、或是能間接定位到目標的句柄,是和虛擬機實現相關的
  • 主要針對:類、接口、字段、類方法、接口方法、方法類型、方法句柄、調用點限定符

類的初始化

  • 類的初始化就是爲類的靜態變量賦初始值,或者說是執行類構造器 方法的過程
  1. 初始化一個類的時候,並不會先初始化它實現的接口
  2. 初始化一個接口的時候,並不會先初始化它的父接口
  3. 只有當程序首次使用接口裏面的變量或者是調用接口方法的時候,才致使接口初始化
  4. 若是類尚未加載和鏈接,就先加載和鏈接
  5. 若是類存在父類,且父類沒有初始化,就先初始化父類
  6. 若是類中存在初始化語句,就依次執行這些初始化語句
  7. 若是是接口的話:
  8. 調用 Classloader 類的 loadClass 方法類裝載一個類,並不會初始化這個類,不是對類的主動使用

類的主動初始化

類的初始化時機

  • Java 程序對類的使用方式分紅:主動使用和被動使用,JVM 必須在每一個類或接口 」首次主動使用「 時才初始化它們;被動使用類不會致使類的初始化,主動使用的狀況:
  1. 建立類實例
  2. 訪問某個類或接口的靜態變量
  3. 調用類的靜態方法
  4. 反射某個類
  5. 初始化某個類的子類,而父類尚未初始化
  6. JVM 啓動的時候運行的主類
  7. 定義了 default 方法的接口,當接口實現類初始化時

類的卸載

  • 當表明一個類的 Class 對象再也不被引用,那麼 Class 對象的生命週期就結束了,對應的在方法區中的數據也會被卸載
  • JVM 自帶的類加載器裝載的類,是不會卸載的,由用戶自定義的類加載器的加載的類是能夠卸載的

內存分配

圖片

在這裏插入圖片描述數組

JVM的簡化架構和運行時數據區

圖片

在這裏插入圖片描述緩存

運行時數據區

  • PC 寄存器、Java虛擬機棧、Java堆、方法區、運行時常量池、本地方法棧等

PC 寄存器

  • 每一個線程都擁有一個PC寄存器,是線程私有的,用來存儲指向下一條指令的地址
  • 在建立線程的時候,建立相應的PC寄存器
  • 執行本地方法時,PC寄存器的值爲 undefined
  • 是一塊比較小的內存空間,是惟一一個在JVM規範中沒有規定 OutOfMemoryError 的內存區域

Java棧

  • 棧由一系列幀(棧幀)(Frame)組成(所以Java棧也叫作幀棧),是線程私有的
  • 棧幀用來保存一個方法的局部變量、操做數棧(Java沒有寄存器,全部參數傳遞使用操做數棧)、常量池指針、動態連接、方法返回等
  • 每一次方法調用建立一個幀,並壓棧,退出方法的時候,修改棧頂指針就能夠把棧幀中的內容銷燬
  • 局部變量表存放了編譯期可知的各類基本數據類型和引用類型,每一個 slot 存放32位的數據,long、double、佔兩個槽位
  • 棧的優勢:存取速度比堆塊,僅次於寄存器
  • 棧的缺點:存在棧中的數據大小、生存區是在編譯器決定的,缺少靈活性

Java堆

  • 用來存放應用系統建立的對象和數組,全部線程共享 Java 堆
  • GC主要管理堆空間,對分代GC來講,堆也是分代的
  • 堆的優勢:運行期動態分配內存大小,自動進行垃圾回收;
  • 堆的缺點:效率相對較慢

方法區

  • 方法區是線程共享的,一般用來保存裝載的類的結構信息
  • 一般和元空間關聯在一塊兒,但具體的跟JVM實現和版本有關
  • JVM規範把方法區描述爲堆的一個邏輯部分,但它有一個別名稱爲 Non-heap(非堆),應是爲了與 Java 堆分開

運行時常量池

  • 是Class文件中每一個類或接口的常量池表,在運行期間的表示形式,一般包括:類的版、字段、方法、接口等信息
  • 在方法區中分配
  • 一般在加載類和接口到JVM後,就建立相應的運行時常量池

本地方法棧

  • 在 JVM 中用來支持 native 方法執行的棧就是本地方法棧

棧、堆、方法區交互關係

圖片

在這裏插入圖片描述安全

Java堆內存模型和分配

Java堆內存概述

  • Java 堆用來存放應用系統建立的對象和數組,全部線程共享Java堆
  • Java堆是在運行期動態分配內存大小,自動進行垃圾回收
  • Java垃圾回收(GC)主要就是回收堆內存,對分代GC來講,堆也是分代的

Java堆的結構

圖片

在這裏插入圖片描述服務器

  • 新生代用來存放新分配的對象;新生代中通過垃圾回收,沒有回收掉的對象,被複制到老年代
  • 老年代存儲對象比新生代存儲對象的年齡大得多
  • 老年代存儲一些大對象
  • 整個堆大小 = 新生代 + 老年齡
  • 新生代 = Eden + 存活區
  • 從前的持久代,用來存放Class、Method 等元信息的區域,從 JDK8 開始去掉了,取而代之的是元空間(MetaSpace),元空間並不在虛擬機裏面,而是直接使用本地內存

對象內存佈局

  • 對象在內存中儲存的佈局(這裏以Hotspot虛擬機爲例來講明),分爲:對象頭、實例數據和對齊填充
  • 對象頭,包含兩部分:
  • Mark Word:存儲對象自身的運行數據,如:HashCode、GC分代年齡,鎖狀態標誌等
  • 類型指針:對象指向它的類元數據的指針
  • 實例數據:真正存放對象實例數據的地方
  • 對齊填充:這部分不必定存在,也沒有什麼特別含義,僅僅是佔位符。由於 HotSpot 要求對象起始地址都是8字節的整數倍,若是不是,就對齊

對象的訪問定位

  • 使用句柄:Java堆中會劃分出一塊內存來做爲句柄池,reference 中存儲句柄的地址,句柄中存儲對象的實例數據和類元數據的地址,如圖

圖片

在這裏插入圖片描述

  • 使用指針:Java堆中會存放訪問類元數據的地址,reference存儲的就直接是對象的地址,如圖:

圖片

在這裏插入圖片描述

Trace跟蹤和Java堆的參數配置

doc

Trace跟蹤參數

  • 能夠打印GC的簡要信息:-Xlog:gc
  • 打印GC詳細信息:-Xlog:gc*
  • 指定GC log的位置,以文件輸出:-Xlog:gc:garbage-collection.log
  • 每一次GC後,都打印堆信息:-xlog:gc+heap = debug

GC 日誌格式

  • GC發生的時間,也就是 JVM 從啓動以來通過的秒數
  • 日誌級別信息,和日誌類型標記
  • GC 識別號
  • GC 的類型和說明 GC 的緣由
  • 容量:GC 前容量 -> GC後容量(該區域總容量)
  • GC 持續時間,單位秒。有的收集器會有詳細的描述,好比:user表示應用程序消耗的時間,sys表示系統內核消耗的時間,real 表示操做從開始到結束的時間

Java堆的參數

  • -Xms:初始化堆大小,默認物理內存的 1/64
  • -Xmx:最大堆大小,默認物理內存的 1/4
  • -Xmn:新生代大小,默認整個堆的 3/8
  • -XX:+HeapDumpOnOutOfMemoryError:OOM時導出堆到文件
  • -XX:+HeapDumpPath:導出 OOM 的路徑
  • -XX:NewRatio:老年代與新生代的比值,若是 xms=xmx,且設置了 xmn 的狀況,該參數不用設置
  • -XX:SurvivorRatio:Eden區和Survivor區的大小比值,設置爲8,則兩個 Survivor 區與一個Eden區的比值爲 2:8,一個 Survivor 佔整個新生的 1/10
  • -XX:OnOutOfMemoryError:在OOM時,執行一個腳本
  • -Xss:一般只有幾百k,決定了函數調用的深度

元空間的參數

  • -XX:MetaspaceSize:初始空間大小
  • -XX:MaxMetaspaceSize:最大空間,默認是沒有限制的
  • -XX:MinMetaspaceFreeRatio:在GC以後,最小的Metaspace 剩餘空間容量的百分比
  • -XX:MaxMetaspaceFreeRatio:在GC以後,最大的Metaspace剩餘空間容量的百分比

字節碼執行引擎

  • JVM 的字節碼執行引擎,功能基本就是輸入字節碼文件,而後對字節碼進行解析並處理,最後輸出執行的結果
  • 實現方式可能有經過解釋器直接解釋執行字節碼,或者經過即時編譯器產生本地代碼,也就是編譯執行,固然也可能二者都有

棧幀

  • 棧幀是用於支持JVM進行方法調用和方法執行的數據結構
  • 棧幀隨着方法調用而建立,隨着方法結束而銷燬
  • 棧幀裏面存儲了方法的局部變量表、操做數棧、動態連接、方法返回地址等信息
    圖片

局部變量表

  • 局部變量表:用來存放方法參數和方法內部定義的局部變量的存儲空間
  1. 以變量槽 slot 爲單位,目前一個 slot 存放32位之內的數據類型
  2. 對於64位的數據佔2個slot
  3. 對於實例方法,第0位 slot 存放的是 this,而後從1到n,依次分配給參數列表
  4. 而後根據方法體內部定義的變量順序和做用域來分配 slot
  5. slot 是複用的,以節省棧幀的空間,這種設計可能會影響系統的垃圾收集行爲

操做數棧

  • 操做數棧:用來存放方法運行期間,各個指令操做的數據
  1. 操做數棧中元素的數據類型必須和字節碼指令的順序嚴格匹配
  2. 虛擬機在實現棧幀的時候可能會作一些優化,讓兩個棧幀出現部分重疊區域,以存放公用的數據

動態連接

  • 動態連接:每個棧幀持有一個指向運行時常量池中該棧幀所屬方法的引用,以支持方法調用過程的動態連接
  1. 靜態解析:類加載的時候,符號引用就轉化爲直接引用
  2. 動態連接:運行期間轉化爲直接引用

方法返回地址

  • 方法返回地址:方法執行後返回的地址

方法調用

  • 方法調用:就是肯定具體調用哪個方法,並不涉及方法內部的執行過程
  1. 部分方法是直接在類加載的解析階段,就肯定了直接引用關係
  2. 可是對於實例方法,也稱虛方法,由於多重和多態,須要運行期動態分派

分派

  • 靜態分派:全部依賴靜態類型來定位方法執行版本的分派方式,好比:重載方法
  • 動態分派:根據運行期的實際類型來定位方法執行版本的分派方式,好比:覆蓋方法
  • 單分派和多分派:就是按照分派思考的維度,多於一個的就算多分配,只有一個的稱爲單分派

垃圾回收

垃圾回收概述

  • 什麼是垃圾:簡單說就是內存中已經再也不被使用到的內存空間就是垃圾
  • 垃圾回收算法:
  • 可做爲GC Roots的對象包括:虛擬機棧(棧幀局部變量)中引用的對象、方法區類靜態屬性引用的對象、方法區中常量引用的對象、本地方法棧中JNI引用的對象
  • HotSpot 使用了一組叫作 OopMap 的數據結構達到準確式GC的目的
  • 在OopMap的協助下,JVM能夠很快的作完GC Roots 枚舉。可是JVM並無爲每一條指令生成一個OopMap
  • 記錄OopMap 的這些「特定位置」被稱爲安全點,即當前線程執行到安全點後才容許暫停進行GC
  • 若是一段代碼中,對象引用關係不會發生變化,這個區域中任何地方開始GC都是安全的,那麼這個區域稱爲安全區域
  • 優勢:失效簡單、效率高
  • 缺點:不能解決對象之間循環引用的問題
  • 引用計數法:給對象添加一個引用計數器,有訪問就加1,引用失效就減1
  • 根搜索算法(可達性分析法):從根(GC Roots)節點向下搜索對象節點,搜索走過的路徑稱爲引用鏈,當一個對象到根之間沒有連通的話,則該對象不可用
    圖片

垃圾回收基礎

跨代引用

  • 跨代引用:也就是一個代中的對象引用另外一個代中的對象
  • 跨代引用假說:跨代引用相對於同代引用來講只是極少數
  • 隱含推論:存在相互引用關係的兩個對象,是應該傾向於同時生存或同時消亡的

記憶集

  • 記憶集(Remembered Set):一種用於記錄從非收集區域指向收集區域的指針集合的抽象數據結構
  • 字長精度:每一個記錄精確到一個機器字長,該子包含跨代指針
  • 對象精度:每一個記錄精確到一個對象,該對象裏有字段含有跨代指針
  • 卡精度:每一個記錄精確到一塊內存區域,該區域內有對象含有跨代指針
  • 卡表(Card Table):是記憶集的一種具體實現,定義了記憶集的記錄精度和與堆內存的映射關係等
  • 卡表的每一個元素都對應着其標識的內存區域中一塊特定大小的內存塊,這個內存塊稱爲 卡頁(Card Page)

寫屏障

  • 寫屏障能夠當作是JVM對 」引用類型字段賦值「 這個動做的AOP
  • 經過寫屏障來實現當對象狀態改變後,維護卡表狀態

判斷是否垃圾的步驟

  • 跟搜索算法判斷不可用
  • 看是否有必要執行 finalize 方法
  • 兩個步驟走完後對象仍然沒有人使用,那就屬於垃圾

GC 類型

  • MinorGC / YoungGC:發生在新生代的收集動做
  • MajorGC / OldGC:發生在老年代的GC,目前只有CMS收集器會有單獨收集老年代的行爲
  • MixedGC:收集整個新生代以及部分老年代,目前只有G1收集器會有這種行爲
  • FullGC:收集整個Java堆和方法區的GC

Stop-The-World

  • STW是Java中一種全局暫停的現象,多半因爲GC引發。所謂全局停頓,就是全部Java代碼中止運行,native代碼能夠執行,但不能和JVM交互
  • 其危害是長時間服務中止,沒有響應;對於HA系統,可能引發主備切換,嚴重危害生產環境

垃圾收集類型

  • 串行收集:GC單線程內存回收、會暫停全部的用戶線程,如:Serial
  • 並行收集:多個GC線程併發工做,此時用戶線程是暫停的,如:Parallel
  • 併發收集:用戶線程和GC線程同時執行(不必定是並行,可能交替執行),不須要停頓用戶線程,如:CMS

判斷類無用的條件

  • JVM 中該類的全部實例都已經被回收
  • 加載該類的 ClassLoader 已經被回收
  • 沒有任何地方引用該類的 Class 對象
  • 沒法在任何地方經過反射訪問這個類

垃圾回收算法

標記清除算法

  • 標記清除算法(Mark-Sweep):分爲標記和清除兩個階段,先標記出要回收的對象,而後統一回收這些對象
  • 圖片

    在這裏插入圖片描述

  • 優勢:簡單
  • 缺點:
  • 效率不高,標記和清除的效率都不高
  • 產生大量不連續的內存碎片,從而致使在分配大對象時觸發GC

複製算法

  • 複製算法(Copying):把內存分紅兩塊徹底相同的區域,每次使用其中一塊,當一塊使用完了,就把這塊上還存活的對象拷貝到另一塊,而後把這塊清除掉
  • 圖片

    在這裏插入圖片描述

  • 優勢:實現簡單,運行高效,不用考慮內存碎片問題
  • 缺點:內存有些浪費
  • JVM實際實現中,是將內存分爲一塊較大的Eden區和兩塊較小的 Survivor 空間,每次使用Eden和一塊 Survivor,回收時,把存活的對象複製到另外一塊 Survivor
  • HotSpot 默認的 Eden 和 Survivor 比是 8:1,也就是每次能用 90% 的新生代空間
  • 若是 Survivor 空間不夠,就要依賴老年代進行分配擔保,把放不下的對象直接進入老年代

    分配擔保:當新生代進行垃圾回收後,新生代的存活區放置不下,那麼須要把這些對象放置到老年代去的策略,也就是老年代爲新生代的GC作空間分配擔保,步驟以下:

  1. 在發生 MinorGC 前,JVM會檢查老年代的最大可用的連續空間,是否大於新生代全部對象的總空間,若是大於,能夠確保 MinorGC 是安全的
  2. 若是小於,那麼JVM會檢查是否設置了容許擔保失敗,若是容許,則繼續檢查老年代最大可用的連續空間,是否大於歷次晉升到老年代對象的平均大小
  3. 若是大於,則嘗試進行一次 MinorGC
  4. 若是不大於,則改作一次 Full GC

標記整理算法

  • 標記整理算法(Mark-Compact):因爲複製算法在存活對象比較多的時候,效率較低,且有空間浪費,所以老年代通常不會選用複製算法,老年代多選用標記整理算法
  • 標記過程跟標記清除算法同樣,但後續不是直接清除可回收對象,而是讓全部存活對象都向一端移動,而後直接清除邊界之外的內存
  • 圖片

    在這裏插入圖片描述

垃圾收集器

  • 串行收集器、並行收集器、新生代Parallel、Scavenge收集器、CMS、G1
  • 圖片

    在這裏插入圖片描述

串行收集器

  • Serial(串行)收集器 / Serial Old 收集器,是一個單線程的收集器,在垃圾收集時,會 Stop-the-World
  • 圖片

    在這裏插入圖片描述

  • 優勢:簡單,對於單cpu,因爲沒有多線程的交互開銷,可能更高效,是默認的 Client 模式下的新生代收集器
  • 使用 -XX:+UseSerialGC 來開啓,會使用:Serial + SerialOld 的收集器組合
  • 新生代使用複製算法,老年代使用標記-整理算法

並行收集器

ParNew收集器

  • ParNew(並行)收集器:使用多線程進行垃圾回收,在垃圾收集時,會Stop-the-World
  • 圖片

    在這裏插入圖片描述

  • 在併發能力好的 CPU 環境裏,它停頓的時間要比串行收集器短;但對於單 CPU 或併發能力較弱的CPU,因爲多線程的交互開銷,可能比串行回收器更差
  • 是 Server 模式下首選的新生代收集器,且能和 CMS 收集器配合使用
  • 再也不使用 -XX:+UseParNewGC來單獨開啓
  • -XX:ParallelGCThreads:指定線程數,最好與 cpu 數量一致

新生代Parallel Scavenge 收集器

  • 新生代 Parallel Scavenge 收集器 / Parallel Old 收集器:是一個應用於新生代的,使用複製算法的、並行的收集器
  • 與 ParNew 很相似,但更關注吞吐量,能最高效率的利用 CPU,適合運行後臺應用
  • 圖片

    在這裏插入圖片描述

  • 使用 -XX:+UseParallelGC 來開啓
  • 使用 -XX:+UseParallelOldGC 來開啓老年代使用 ParallelOld收集器,使用 Parallel Scavenge + Parallel Old 的收集器組合
  • -XX:MaxGCPauseMillis:設置GC 的最大停頓時間
  • 新生代使用複製算法,老年代使用標記-整理算法

CMS收集器

  • CMS(Concurrent Mark and Sweep 併發標記清除)收集器分爲:初始標記:只標記GC Roots 能直接關聯到的對象;併發標記:進行GC Roots Tracing 的過程
  • 從新標記:修正併發標記期間,因程序運行致使標記發生變化的那一部分對象
  • 併發清除:併發回收垃圾對象
  • 圖片

    在這裏插入圖片描述

  • 在初始化標記和從新標記兩個階段仍是會發生 Stop-the-World
  • 使用標記清除算法,多線程併發收集的垃圾收集器
  • 最後的重置線程,指的是清空跟收集相關的數據並重置,爲下次收集作準備
  • 優勢:低停頓,併發執行
  • 缺點:
  • 併發執行,對 CPU 資源壓力大
  • 沒法處理 在處理過程當中 產生的垃圾(浮動垃圾),可能致使 FullGC
  • 採用的標記清除算法會致使大量碎片,從而在分配大對象可能觸發 FullGC
  • 開啓:-XX:UseConcMarkSweepGC:使用 ParNew + CMS + Serial Old 的收集器組合,Serial Old 將做爲 CMS 出錯的後備收集器
  • -XX:CMSInitiatingOccupancyFraction:設置 CMS 收集器在老年代空間被使用多少後觸發回收,默認 80%

G1收集器

  • G1(Garbage-First)收集器:是一款面向服務應用的收集器,與其餘收集器相比,具備如下特色:
  1. G1 把內存劃分紅多個獨立的區域(Region)
  2. G1 仍採用分代思想,保留了新生代和老年代,但它們再也不是物理隔離的,而是一部分Region的集合,且不須要 Region 是連續的
  • 圖片

    在這裏插入圖片描述

  • G1 能充分利用多 CPU 、多核環境硬件優點,儘可能縮短 STW
  • G1 總體上採用標記-整理算法,局部是經過複製算法,不會產生內存碎片
  • G1 的停頓可預測,能明確指定在一個時間段內,消耗在垃圾收集上的時間不能超過多長時間
  • G1 跟蹤各個 Region 裏面垃圾堆的價值大小,在後臺維護一個優先列表,每次根據容許的時間來回收價值最大的區域,從而保證在有限時間內的高效收集
  • 垃圾收集:
  • 初始標記:只標記GC Roots 能直接關聯到的對象
  • 併發標記:進行 GC Roots Tracing 的過程
  • 最終標記:修正併發標記期間,因程序運行致使標記發生變化的那一部分對象
  • 篩選回收:根據時間來進行價值最大化的回收
  • 圖片

    在這裏插入圖片描述

  • 使用和配置G1:-XX:+UseG1GC:開啓G1,默認就是G1
  • -XX:MaxGCPauseMillis = n :最大GC停頓時間,這是個軟目標,JVM將盡量(但不保證)停頓小於這個時間
  • -XX:InitiatingHeapOccupancyPercent = n:堆佔用了多少的時候就觸發GC,默認爲45
  • -XX:NewRatio = n:默認爲2
  • -XX:SurvivorRatio = n:默認爲8
  • -XX:MaxTenuringThreshold = n:新生代到老年代歲數,默認是15
  • -XX:ParallelGCThreads = n:並行GC的線程數,默認值會根據平臺不一樣而不一樣
  • -XX:ConcGCThreads = n:併發 GC 使用的線程數
  • -XX:G1ReservePercent = n:設置做爲空閒空間的預留內存百分比,以下降目標空間溢出的風險,默認值是 10%
  • -XX:G1HeapRegionSize = n:設置的 G1 區域的大小。值是2的冪,範圍是1MB到32MB,目標是根據最小的Java堆大小劃分出約2048個區域

ZGC收集器(瞭解)

  • ZGC收集器:JDK11加入的具備實驗性質的低延遲收集器
  • ZGC的設計目標是:支持TB級內存容量,暫停時間低(<10ms),對整個程序吞吐量的影響小於15%
  • ZGC裏面的新技術:着色指針 和 讀屏障
  • GC性能指標:
  • 吞吐量 = 應用代碼執行的時間 / 運行的總時間
  • GC負荷,與吞吐量相反,是 GC 時間 / 運行的總時間
  • 暫停時間,就是發生 Stop-the-World 的總時間
  • GC 頻率,就是GC在一個時間段發生的次數
  • 反應速度:就是從對象成爲垃圾開始到被回收的時間
  • 交互式應用一般但願暫停時間越少越好
  • JVM內存配置原則:
  • 新生代儘量設置大點,若是過小會致使:
  • 對於老年代,針對響應時間優先的應用:因爲老年代一般採用併發收集器,所以其大小要綜合考慮併發量和併發持續時間等參數
  • 對於老年代,針對吞吐量優先的應用:一般設置較大的新生代和較小的老年代,這樣能夠儘量回收大部分短時間對象,減小中期對象,而老年代儘可能存放長期存活的對象
  • 依據對象的存活週期進行分類,對象優先在新生代分配,長時間存活的對象進入老年代
  • 根據不一樣代的特色,選取合適的收集算法:少許對象存活,適合複製算法;大量對象存活,適合標記清除或標記整理
  1. 若是設置小了,可能會形成內存碎片,高回收頻率會致使應用暫停
  2. 若是設置大了,會須要較長的回收時間
  3. YGC 次數更加頻繁
  4. 可能致使 YGC 後的對象進入老年代,若是此時老年代滿了,會觸發FGC

高效併發

Java內存模型和內存間的交互操做

Java內存模型

  • JCP 定義了一種 Java 內存模型,之前是在 JVM 規範中,後來獨立出來成爲JSR-133(Java內存模型和線程規範修訂)
  • 內存模型:在特定的操做協議下,對特定的內存或高速緩存進行讀寫訪問的過程抽象
  • Java 內存模型主要關注 JVM 中把變量值存儲到內存和從內存中取出變量值這樣的底層細節
  • 圖片

    在這裏插入圖片描述

  • 全部變量(共享的)都存儲在主內存中,每一個線程都有本身的工做內存;工做內存中保存該線程使用到的變量的主內存副本拷貝
  • 線程對變量的全部操做(讀、寫)都應該在工做內存中完成
  • 不一樣線程不能相互訪問工做內存,交互數據要經過主內存

內存間的交互操做

  • Java內存模型規定了一些操做來實現內存間交互,JVM會保存它們是原子的
  • lock:鎖定,把變量標識爲線程獨佔,做用於主內存變量
  • unlock:解鎖,把鎖定的變量釋放,別的線程才能使用,做用於主內存變量
  • read:讀取,把變量從主內存讀取到工做內存
  • load:載入,把read讀取到的值放入工做內存的變量副本中
  • use:使用,把工做內存中一個變量的值傳遞給執行引擎
  • assign:賦值,把從執行引擎接收到的值賦給工做內存裏面的變量
  • store:存儲,把工做內存中一個變量的值傳遞到主內存中
  • wirte:寫入,把 store 進來的數據存放如主內存的變量中
  • 圖片

    在這裏插入圖片描述

內存間的交互操做的規則

  • 不容許 read 和 load 、store 和 write 操做之一單獨出現,以上兩個操做必須按照順序執行,但不保證連續執行,也就是說,read 和 load 之間、store 與 write 之間是可插入其餘指令的
  • 不容許一個線程丟棄它的最近的 assign 操做,即變量在工做內存中改變了以後必須把該變化同步回主內存
  • 不容許一個線程無緣由地(沒有發生過任何 assign 操做)把數據從線程的工做內存同步回主內存中
  • 一個新的變量只能從主內存中 」誕生「,不容許在工做內存中直接使用一個未被初始化的變量,也就是對一個變量實施 use 和 store 操做以前,必須先執行過了 assign 和 load 操做
  • 一個變量在同一個時刻只容許一條線程對其執行 lock 操做,但 lock 操做能夠被同一條線程重複執行屢次,屢次執行lock後,只有執行相同次數的 unlock 操做,變量纔會被解鎖
  • 若是對一個變量執行 lock 操做,將會清空工做內存中此變量的值,在執行引擎使用這個變量前,須要從新執行 load 或 assign 操做初始化變量的值
  • 若是一個變量沒有被 lock 操做鎖定,則不容許對它執行 unlock 操做,也不能 unlock 一個被其餘線程鎖定的變量
  • 對一個變量執行 unlock 操做以前,必須先把此變量同步回主內存(執行 store 和 write 操做)

volatile特性

多線程中的可見性

  • 可見性:就是一個線程修改了變量,其餘線程能夠知道
  • 保證可見性的常見方法:volatile、synchronized、final(一旦初始化完成,其餘線程就可見)

volatile

  • volatile 基本上是 JVM 提供的最輕量級的同步機制,用 volatile 修飾的變量,對全部的線程可見,即對 volatile 變量所作的寫操做能當即反映到其餘線程中
  • 用 volatile 修飾的變量,在多線程環境下仍然是不安全的
  • volatile 修飾的變量,是禁止指令重排優化的
  • 適合使用 valatile 的場景
  • 運算結果不依賴變量的當前值
  • 確保只有一個線程修改變量的值

指令重排

  • 指令重排:指的是 JVM 爲了優化,在條件容許的狀況下,對指令進行必定的從新排列,直接運行當前可以當即執行的後序指令,避開獲取下一條指令所需數據形成的等待
  • 線程內串行語義,不考慮多線程間的語義
  • 不是全部的指令都能重排,好比:
  • 寫後讀 a = 1;b = a;寫一個變量以後,再讀這個位置
  • 寫後寫 a = 1;a = 2;寫一個變量以後,再寫這個變量
  • 讀後寫 a = b;b = 1;讀一個變量以後,再寫這個變量
  • 以上語句不可重排,可是 a = 1;b = 2;是能夠重排的
  • 程序順序原則:一個線程內保證語義的串行性
  • volatile規則:volatile 變量的寫,先發生於讀
  • 鎖規則:解鎖(unlock)必然發生在隨後的加鎖(lock)前
  • 傳遞性:A 先於 B,B 先於 C,那麼 A 必然先於 C
  • 線程的 start 方法先於它的每個動做
  • 線程的全部操做先於線程的終結
  • 線程中斷(interrupt())先於被中斷線程的代碼
  • 對象的構造函數執行結束先於 finalize() 方法

Java線程安全的處理方法

  • 不可變是線程安全的
  • 互斥同步(阻塞同步):synchronized、java.util.concurrent.ReentrantLock。目前這兩個方法性能已經差很少了,建議優先選用 synchronized,ReentrantLock 增長了以下特性:
  • 等待可中斷:當持有鎖的線程長時間不釋放鎖,正在等待的線程能夠選擇放棄等待
  • 公平鎖:多個線程等待同一個鎖時,須嚴格按照申請鎖的時間順序來獲取鎖
  • 鎖綁定多個條件:一個 ReentrantLock 對象能夠綁定多個 condition 對象,而 synchronized 是針對一個條件的,若是要多個,就得有多個鎖
  • 非阻塞同步:是一種基於衝突檢查的樂觀鎖策略,一般是先操做,若是沒有衝突,操做就成功了,有衝突再採起其餘方式進行補償處理
  • 無同步方案:其實就是在多線程中,方法並不涉及共享數據,天然也就無需同步了

鎖優化

自旋鎖與自適應自旋

  • 自旋:若是線程能夠很快得到鎖,那麼能夠再也不 OS 層掛起線程,而是讓線程作幾個忙循環,這就是自旋
  • 自適應自旋:自旋的時間再也不固定,而是由前一次在同一個鎖上的自旋時間和鎖的擁有者狀態來決定
  • 若是鎖被佔用時間很短,自旋成功,那麼能節省線程掛起、以及切換時間,從而提高系統性能
  • 若是鎖被佔用時間很長,自旋失敗,會白白浪費處理器資源,下降系統性能

鎖消除

  • 在編譯代碼的時候,檢測到根本不存在共享數據競爭,天然也就無需同步加鎖了;經過 -XX:+EliminateLocks 來開啓
  • 同時要使用 -XX:DoEscapeAnalysis 開啓逃逸分析

    逃逸分析:

  1. 若是一個方法中定義的一個對象,可能被外部方法引用,稱爲方法逃逸
  2. 若是對象可能被其餘外部線程訪問,稱爲線程逃逸,好比賦值給類變量或者能夠在其餘線程中訪問的實例變量

鎖粗化

  • 一般咱們都要求同步塊要小,但一系列連續的操做致使一個對象反覆的加鎖和解鎖,這會致使沒必要要的性能損耗。這種狀況建議把鎖同步的範圍加大到整個操做序列

輕量級鎖

  • 輕量級是相對於傳統鎖機制而言,本意是沒有多線程競爭的狀況下,減小傳統鎖機制使用 OS 實現互斥所產生的性能損耗
  • 其實現原理很簡單,就是相似樂觀鎖的方式
  • 若是輕量級鎖失敗,表示存在競爭,升級爲重量級鎖,致使性能降低

偏向鎖

  • 偏向鎖是在無競爭狀況下,直接把整個同步消除了,連樂觀鎖都不用,從而提升性能;所謂的偏向,就是偏愛,即鎖會偏向於當前已經佔有鎖的線程
  • 只要沒有競爭,得到偏向鎖的線程,在未來進入同步塊,也不須要作同步
  • 當有其餘線程請求相同的鎖時,偏向模式結束
  • 若是程序中大多數鎖老是被多個線程訪問的時候,也就是競爭比較激烈,偏向鎖反而會下降性能
  • 使用 -XX:-UseBiasedLocking 來禁用偏向鎖,默認開啓

JVM 中獲取鎖的步驟

  • 會先嚐試偏向鎖;而後嘗試輕量級鎖
  • 再而後嘗試自旋鎖
  • 最後嘗試普通鎖,使用 OS 互斥量在操做系統層掛起

同步代碼的基本規則

  • 儘可能減小持有鎖的時間
  • 儘可能減小鎖的粒度

性能監控與故障處理工具

命令行工具

  • 命令行工具:jps、jinfo、jstack、jmap、jstat、jstatd、jcmd
  • 圖形化工具:jconsole、jmc、visualvm
  • 兩種鏈接方式:JMX、jstatd

JVM 檢測工具的做用

  • 對 jvm 運行期間的內部狀況進行監控,好比:對 jvm 參數、CPU、內存、堆等信息的查看
  • 輔助進行性能調優
  • 輔助解決應用運行時的一些問題,好比:OutOfMemoryError、內存泄漏、線程死鎖、鎖爭用、Java進程消耗 CPU 太高 等等

jps

  • jps(JVM Process Status Tool):主要用來輸出 JVM 中運行的進程狀態信息,語法格式以下:jps [options] [hostid]
  • hostid 字符串的語法與 URI 的語法基本一致:[protocol:] [ [ // ] hostname] [ :port ] [/servername],若是不指定hostid,默認爲當前主機或服務器

jinfo

  • 打印給定進程或核心或遠程調試服務器的配置信息。語法格式:jinfo [option] pid #指定進程號(pid)的進程
  • jinfo [ option ] <executable #指定核心文件
  • jinfo [option] [server-id@] #指定遠程調試服務器

jstack

  • jstack 主要用來查看某個 Java 進程內的線程堆棧信息。語法格式以下:jstack [option] pid
  • jstack [option] executable core
  • jstack [option] [server-id@] remorte-hostname-or-ip

jmap

  • jmap 用來查看堆內存使用狀況,語法格式以下:jmap [option] pid
  • jmap [option] executable core
  • jmap [option] [server-id@] remote-hostname-or-ip

jstat

  • JVM 統計監測工具,查看各個區域內存和 GC 的狀況
  • 語法格式以下:jstat [generalOption | outputOptions vmid [interval[s|ms] [count]]]

jstated

  • 虛擬機的 jstat 守護進行,主要用於監控 JVM 的建立與終止,並提供一個接口,以有序遠程監視工具附加到本地系統上運行的 JVM、
  • 語法格式:jstatd [ options ]

jcmd

  • JVM 診斷工具,將診斷命令請求發送到正在運行的額 Java 虛擬機,好比能夠用來導出堆,查看 java 進程,導出線程信息,執行 GC 等

圖形化工具

jconsole

  • 一個用於監視 Java 虛擬機的符合 JMX的圖形工具。它能夠監視本地和遠程 JVM,還能夠監視和管理應用程序

jmc

  • jmc(JDK Mission Control)Java 任務控制(JMC)客戶端包括用於監視和管理 Java 應用程序的工具,而不是引入一般與這些類型的工具相關聯的性能開銷

VisualVM

  • 一個圖形工具,它提供有關在 Java 虛擬機中運行的基於 Java 技術的應用程序的詳細信息
  • Java VisualVM 提供內存和 CPU 分析,堆轉儲分析,內存泄漏檢測,訪問 MBean 和垃圾回收

遠程鏈接

  • JMX 鏈接能夠查看:系統信息、CPU使用狀況、線程多少、手動執行垃圾回收等比較偏於系統層面的信息
  • jstatd 鏈接方式能夠提供:JVM 內存分佈詳細信息、垃圾回收分佈圖、線程詳細信息,甚至能夠看到某個對象使用內存的大小
    s|ms] [count]]]

相關文章
相關標籤/搜索