JVM 虛擬機手冊

前言

前段時間翻看本身多年以來攢下的滿滿家當 , 忽然有一種滿滿的知足感 .
可是想一想多年來找資料的艱辛 , 決定將這些文檔整理出來, 分享給你們 .
筆記華而不實 , 其中可能也有不正確的地方 , 歡迎指正. 
在此也感謝道友們的奉獻 , 文檔暫分爲幾個:

複製代碼

另外還有其餘的筆記會陸陸續續的分享處理 , 謝謝你們的支持 .javascript

一 . 基礎知識

1 . 1 常見的內存溢出

> 堆溢出
> 元空間溢出 , 元數據區的內存溢出
> 直接內存溢出
> 虛擬機棧和本地方法棧溢出
> 運行時常量池溢出
> 方法區的內存溢出
複製代碼

1 . 2 內存溢出的常見緣由

複製代碼

1 . 3 系統的線程劃分

> 串行收集器
      : 用單線程處理全部垃圾回收工做 , 效率高
      : 數據量比較小(100M左右);單處理器下而且對響應時間無要求的應用
> 並行收集器
	  : 「對吞吐量有高要求」,多CPU、對應用響應時間無要求的中、大型應用
	
> 併發處理器:
	: 對響應時間有高要求」,多CPU、對應用響應時間有較高要求的中、大型應用
複製代碼

1 .4 Java 的四種引用類型

• 強引用
• 軟引用(SoftReference)
• 弱引用(WeakReference)
• 虛引用(PhantomReference)

1)強引用
	咱們使用的大部分引用實際上都是強引用,這是使用最廣泛的引用
	
軟引用(SoftReference)
複製代碼

1 . 5 TLAB

> 指針碰撞 
	- 對象經過引用指向實際的內存空間 , 而指向的即爲對應的指針 , 在堆內存中 , 一片內存被一個指針一份爲2 , 左邊爲已經分配內存的空間,右側爲空 , 每一次有新的對象建立,指針就會向右移動一個對象size的距離。這就被稱爲指針碰撞 , 可是當多線程高併發狀況下 , 會出現指針來不及修改的狀況
    
TLAB的全稱是Thread Local Allocation Buffer,即線程本地分配緩存區,這是一個線程專用的內存分配區域。 若是設置了虛擬機參數 -XX:UseTLAB,在線程初始化時,同時也會申請一塊指定大小的內存,只給當前線程使用,這樣每一個線程都單獨擁有一個空間,若是須要分配內存,就在本身的空間上分配,這樣就不存在競爭的狀況,能夠大大提高分配效率。

TLAB空間的內存很是小,缺省狀況下僅佔有整個Eden空間的1%,也能夠經過選項-XX:TLABWasteTargetPercent設置TLAB空間所佔用Eden空間的百分比大小。

TLAB的本質實際上是三個指針管理的區域:start,top 和 end,每一個線程都會從Eden分配一塊空間,例如說100KB,做爲本身的TLAB,其中 start 和 end 是佔位用的,標識出 eden 裏被這個 TLAB 所管理的區域,卡住eden裏的一塊空間不讓其它線程來這裏分配。

TLAB只是讓每一個線程有私有的分配指針,但底下存對象的內存空間仍是給全部線程訪問的,只是其它線程沒法在這個區域分配而已。


// TLAB 的缺陷
1,TLAB空間大小是固定的,可是這時候一個大對象,我TLAB剩餘的空間已經容不下它了。(好比100kb的TLAB,來了個110KB的對象)
2,TLAB空間還剩一點點沒有用到,有點捨不得。
3,Eden空間夠的時候,你再次申請TLAB沒問題,我不夠了,Heap的Eden區要開始GC,
4,TLAB容許浪費空間,致使Eden區空間不連續,聚沙成塔。之後還要人幫忙打理。
    
複製代碼

二 . 虛擬機

2 . 1 Java 虛擬機

Java 虛擬機,是一個能夠執行 Java 字節碼的虛擬機進程 , 它容許Java 查詢在多個任意平臺使用 , 可是跨平臺的是 Java 程序(包括字節碼文件) , 而不是 JVM

> 類加載器,在 JVM 啓動時或者類運行時將須要的 class 加載到 JVM 中。 > 內存區,將內存劃分紅若干個區以模擬實際機器上的存儲、記錄和調度功能模塊,如實際機器上的各類功能的寄存器或者 PC 指針的記錄器等。 > 執行引擎,執行引擎的任務是負責執行 class 文件中包含的字節碼指令,至關於實際機器上的 CPU 。 > 本地方法調用,調用 CC++ 實現的本地方法的代碼返回結果。 // 運行時數據區 > 程序計數器: Java 線程私有,相似於操做系統裏的 PC 計數器,它能夠看作是當前線程所執行的字節碼的行號指示器。 - 若是線程正在執行的是一個 Java 方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是 Native 方法,這個計數器值則爲空(Undefined) > 虛擬機棧(棧內存):Java線程私有,虛擬機棧描述的是 Java 方法執行的內存模型 > 本地方法棧 :和 Java 虛擬機棧的做用相似,區別是該區域爲 JVM 提供使用 Native 方法的服務 > 堆內存(線程共享):全部線程共享的一塊區域,垃圾收集器管理的主要區域 - 每一個方法在執行的時候,都會建立一個棧幀用於存儲局部變量、操做數、動態連接、方法出口等信息。 - 每一個方法調用都意味着一個棧幀在虛擬機棧中入棧到出棧的過程。 > 方法區(線程共享):各個線程共享的一個區域,用於存儲虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據 - 線程共享區域,所以這是線程不安全的區域。 - 方法區也是一個可能會發生OutOfMemoryError的區域。 - 方法區存儲的是從Class文件加載進來的靜態變量、類信息、常量池以及編譯器編譯後的代碼。 複製代碼

2 . 2 內存堆細節

// 內存堆特色
- 存儲的是咱們new來的對象,不存放基本類型和對象引用。
- 因爲建立了大量的對象,垃圾回收器主要工做在這塊區域。
- 線程共享區域,所以是線程不安全的。
- 可以發生內存溢出,主要有OutOfMemoryError和StackOverflowError。
    
// 分代
Java堆區還能夠劃分爲新生代和老年代,新生代又能夠進一步劃分爲Eden區、Survivor 1區、Survivor 2// 注意比例 : 
 8:1:1 + 2:3    
複製代碼

2 . 3 內存棧細節

- 線程私有區域,每個線程都有獨享一個虛擬機棧,所以這是線程安全的區域。
- 存放基本數據類型以及對象的引用。
- 每個方法執行的時候會在虛擬機棧中建立一個相應棧幀,方法執行完畢後該棧幀就會被銷燬。
- 方法棧幀是以先進後出的方式虛擬機棧的。每個棧幀又能夠劃分爲局部變量表、操做數棧、動態連接、方法出口以及額外的附加信息。
- 這個區域可能有兩種異常:若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError異常(一般是遞歸致使的);JVM動態擴展時沒法申請到足夠內存則拋出OutOfMemoryError異常。


複製代碼

2 . 4 Java 內存堆和棧區別

棧內存用來存儲基本類型的變量和對象的引用變量;堆內存用來存儲Java中的對象,不管是成員變量,局部變量,仍是類變量,它們指向的對象都存儲在堆內存中。

棧內存歸屬於單個線程,每一個線程都會有一個棧內存,其存儲的變量只能在其所屬線程中可見,即棧內存能夠理解成線程的私有內存;堆內存中的對象對全部線程可見。堆內存中的對象能夠被全部線程訪問。

若是棧內存沒有可用的空間存儲方法調用和局部變量,JVM 會拋出 java.lang.StackOverFlowError 錯誤;若是是堆內存沒有可用的空間存儲生成的對象,JVM 會拋出 java.lang.OutOfMemoryError 錯誤。

棧的內存要遠遠小於堆內存,若是你使用遞歸的話,那麼你的棧很快就會充滿。-Xss 選項設置棧內存的大小,-Xms 選項能夠設置堆的開始時的大小。
複製代碼

2 . 6 HotSpot虛擬機

HotSpot 虛擬機將其物理上分爲了2個部分 :

> 新生代(young generation)
 : 絕大多數最新被建立的對象會被分配到這裏
 : 對象從這個區域消失的過程咱們稱之爲」minor GC「
 
 -> 新生代三空間
 	:一個伊甸園空間(Eden )
	:兩個倖存者空間(Survivor )
	建立後待伊甸園 -- 第一次GC --> 其中一個倖存者空間 -- 不斷堆積--> 飽和後移動到第二個倖存者空間 --> 清空飽和空間 --> 幾輪後剩下的放入老年代

> 老年代(old generation)
 : 對象沒有變得不可達,而且重新生代中存活下來,會被拷貝到這
 : 對象從老年代中消失的過程,咱們稱之爲」major GC「(或者」full GC「)
 
> card table
 : 存在於老年代 ,512 byte,記錄老年代對新生代的應用
 : 由一個 write barrier
 
> 持久代( permanent generation ) 又名 方法區(method area)
 : 保存類常量以及字符串常量
 
> 加快緩存分配
 : bump-the-pointer
   - 跟蹤在伊甸園空間建立的最後一個對象 ,放在頂部,下次建立查找該對象
 : TLABs(Thread-Local Allocation Buffers)
   - 該方案爲每個線程在伊甸園空間分配一塊獨享的空間,這樣每一個線程只訪問他們本身的TLAB空間,再與bump-the-pointer技術結合能夠在不加鎖的狀況下分配內存
複製代碼

三 . 垃圾清理

3 . 1 垃圾回收的原由

> 程序員沒法自動完成系統的GC ,GC 通常在如下環境被建立

大多數對象會很快變得不可達
只有不多的由老對象(建立時間較長的對象)指向新生對象的引用

複製代碼

3 . 2 垃圾回收中的概念

// stop-the-world
: Stop-the-world會在任何一種GC算法中發生
: Stop-the-world意味着 JVM 由於要執行GC而中止了應用程序的執行
: 當Stop-the-world發生時,除了GC所需的線程之外,全部線程都處於等待狀態,直到GC任務完成
: GC優化不少時候就是指減小Stop-the-world發生的時間
   
    
// 分代回收 
> 爲何垃圾回收要分代: 
  : 不一樣的對象生命週期是不同的 ,採用不一樣的收集方式,能夠提升回收率
  
> 分代的方式 :
  : 年輕代
  : 老年代
  : 持久代 

// 新生代 GC 和老年代 GC
新生代 : 一個 Eden 區 + 兩個 Survivor 區
老年代 : 默認新生代(Young)與老年代(Old)的比例的值爲 1:2 , 默認的 Eden:from:to=8:1:1
    
新生代GC(MinorGC/YoungGC):指發生在新生代的垃圾收集動做,由於 Java 對象大多都具有朝生夕滅的特性,因此 MinorGC 很是頻繁,通常回收速度也比較快。
老年代GC(MajorGC/FullGC):指發生在老年代的 GC,出現了 MajorGC,常常會伴隨至少一次的 MinorGC(但非絕對的,在 Parallel Scavenge 收集器的收集策略裏就有直接進行 MajorGC 的策略選擇過程)。MajorGC 的速度通常會比 MinorGC 慢 10 倍以上。

      
// 觸發分代回收的方式
Scavenge GC和Full GC。

Scavenge GC : 新對象生成  , 而且在 Eden 申請空間失敗 ,即觸發   
    
    
// 垃圾收集器
新生代收集器
	-Serial 收集器
	- ParNew 收集器
		?- ParNew 收集器,是 Serial 收集器的多線程版。
	- Parallel Scavenge 收集器

老年代收集器
	- Serial Old 收集器
		?- Serial Old 收集器,是 Serial 收集器的老年代版本。
	- Parallel Old 收集器
		?- Parallel Old 收集器,是 Parallel Scavenge 收集器的老年代版本。
	- CMS 收集器
新生代 + 老年代收集器
	- G1 收集器
	- ZGC 收集器    
    
    
//  G1 和 CMS 的區別
• CMS :併發標記清除。他的主要步驟有:初始收集,併發標記,從新標記,併發清除(刪除)、重置。
• G1:主要步驟:初始標記,併發標記,從新標記,複製清除(整理)
• CMS 的缺點是對 CPU 的要求比較高。G1是將內存化成了多塊,全部對內段的大小有很大的要求。
• CMS是清除,因此會存在不少的內存碎片。G1是整理,因此碎片空間較小。
• G1 和 CMS 都是響應優先把,他們的目的都是儘可能控制 STW 時間。
• G1 和 CMS 的 Full GC 都是單線程 mark sweep compact 算法,直到 JDK10 才優化爲並行的。

複製代碼
收集器 串行、並行or併發 新生代/老年代 算法 目標 適用場景
Serial 串行 新生代 複製算法 響應速度優先 單CPU環境下的Client模式
Serial Old 串行 老年代 標記-整理 響應速度優先 單CPU環境下的Client模式、CMS的後備預案
ParNew 並行 新生代 複製算法 響應速度優先 多CPU環境時在Server模式下與CMS配合
Parallel Scavenge 並行 新生代 複製算法 吞吐量優先 在後臺運算而不須要太多交互的任務
Parallel Old 並行 老年代 標記-整理 吞吐量優先 在後臺運算而不須要太多交互的任務
CMS 併發 老年代 標記-清除 響應速度優先 集中在互聯網站或B/S系統服務端上的Java應用
G1 併發 both 標記-整理+複製算法 響應速度優先 面向服務端應用,未來替換CMS

3 . 3 垃圾回收的分類

複製代碼

3 .4 常見得垃圾回收方式

// 方式一 : 調用 system gc 方法 , 開發者手動調用該命令 , 觸發 gc
System.gc()
    
// 方式二 : 調用 Runtime.getRuntime().gc() 方式 , 該方法實際上會 invoke system.gc()
 Runtime.getRuntime().gc()
    
// 方式三 : Use jmap to force GC , 經過 jmap 命令執行 gc
// 該命令不能保證萬無一失 , 若是 JVM 被佔用致使 GC 沒法執行會出現異常 
jmap -histo:live 7544

// 方式四 : 使用 Jcmd 命令執行 GC
// 經過 Java diagnostic command (JCMD) JVM 診斷命令觸發 GC 
jcmd 7544 GC.run
    
// 方式五 : Use JConsole or Java Mission Control

複製代碼

3 . 5 垃圾回收的算法

> 應用計數 
	: 對一個對象有引用/移除 。 即添加/刪除數量 , 垃圾回收會回收數量爲 0 的對象
    
> 標記清除 
	: 第一階段從引用根節點開始標記全部被引用的對象
	: 第二階段遍歷整個堆,把未標記的對象清除

> 複製(Copying)
	: 將算法的內存空間分爲相等的兩個部分,回收時,遍歷當前區域,將使用的對象複製到另外的區域

> 標記-整理(Mark-Compact):
	: 第一階段從根節點開始標記全部被引用對象
	: 第二階段遍歷整個堆,把清除未標記對象而且把存活對象「壓縮」到堆的其中一塊,按順序排放

複製代碼

3 . 6 常見的垃圾收集器

3 .6 .1 Serial 收集器

Serial 收集器是最基礎、歷史最悠久的收集器,它在進行垃圾收集的時候會暫停全部的工做線程,直到完成垃圾收集過程。下面是Serial垃圾收集器的運行示意圖:
複製代碼

3 .6 .2 ParNew 收集器

ParNew 垃圾收集器實則是Serial 垃圾收集器的多線程版本,這個多線程在於ParNew垃圾收集器可使用多條線程進行垃圾回收。
複製代碼

3 .6 .3 Parallel Scavenge 收集器

複製代碼

3. 6 .4 Serial Old 收集器

Serial Old 收集器是Serial 收集器的老年代版本。其垃圾收集器的運行原理和Serial 收集器是同樣的。
複製代碼

3 .6 .5 Parallel Old 收集器

Parallel Old 收集器一樣是Parallel Scavenge 收集器的老年代版本,支持多線程併發收集。
複製代碼

3 .6 .6 CMS 收集器

CMS 垃圾收集器的運做過程相對前面幾個垃圾收集器來講比較複雜,整個過程能夠分爲四個部分:

初始標記: 須要Stop The World,這裏僅僅標記GC Roots可以直接關聯的對象,因此速度很快。

併發標記: 從關聯對象遍歷整個GC Roots的引用鏈,這個過程耗時最長,可是卻能夠和用戶線程併發運行。

從新標記: 修正併發時間,由於用戶線程可能會致使標記產生變更,一樣須要Stop The World。

併發清除: 清除已經死亡的對象。


複製代碼

3 .6 .7 Garbage First 收集器

Garbage First(簡稱G 1)收集器是垃圾收集器發展史上里程碑式的成果,主要面向服務端應用程序。另外G 1收集器雖然還保留新生代和老年代的概念,可是新生代和老年代不在固定,它們都是一系列區域的動態集合。

• 並行與併發:G1能充分利用多CPU、多核環境下的硬件優點,使用多個CPU來縮短Stop-The-World停頓的時間,部分其餘收集器本來須要停頓Java線程執行的GC動做,G1收集器仍然能夠經過併發的方式讓Java程序繼續執行。
• 分代收集:雖然G1能夠不須要其餘收集器配合就能獨立管理整個GC堆,但它可以採用不一樣的方式去處理新建立的對象和已經存活了一段時間、熬過屢次GC的就對象以獲取更好的收集效果。
• 空間整合:G1從總體上來看是基於「標記-整理」算法實現的收集器,從局部(兩個Region之間)上來看是基於「複製」算法實現的,這意味着G1運做期間不會產生內存空間碎片,收集後能提供規整的可用內存。
• 可預測的停頓:這是G1相對於CMS的另外一大優點。
複製代碼

四 . 對象的建立

4 . 1 建立過程

1)檢測類是否被加載
當虛擬機遇到 new 指令時,首先先去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,而且檢查這個符號引用表明的類是否已被加載、解析和初始化過。若是沒有,就執行類加載過程。

2)爲對象分配內存
類加載完成之後,虛擬機就開始爲對象分配內存,此時所需內存的大小就已經肯定了。只須要在堆上分配所須要的內存便可。
具體的分配內存有兩種狀況:
    第一種狀況是內存空間絕對規整
    第二種狀況是內存空間是不連續的。
		- 對於內存絕對規整的狀況相對簡單一些,虛擬機只須要在被佔用的內存和可用空間之間移動指針便可,這種方式被稱爲「指針碰撞」。
		- 對於內存不規整的狀況稍微複雜一點,這時候虛擬機須要維護一個列表,來記錄哪些內存是可用的。分配內存的時候須要找到一個可用的內存空間,而後在列表上記錄下已被分配,這種方式成爲「空閒列表」。

    多線程併發時會出現正在給對象 A 分配內存,還沒來得及修改指針,對象 B 又用這個指針分配內存,這樣就出現問題了。解決這種問題有兩種方案:
		• 第一種,是採用同步的辦法,使用 CAS 來保證操做的原子性。
		• 另外一種,是每一個線程分配內存都在本身的空間內進行,便是每一個線程都在堆中預先分配一小塊內存,稱爲本地線程分配緩衝(Thread Local Allocation Buffer, TLAB),分配內存的時候再TLAB上分配,互不干擾。能夠經過 -XX:+/-UseTLAB 參數決定。
    
3)爲分配的內存空間初始化零值
對象的內存分配完成後,還須要將對象的內存空間都初始化爲零值,這樣能保證對象即便沒有賦初值,也能夠直接使用。
    
4)對對象進行其餘設置
分配完內存空間,初始化零值以後,虛擬機還須要對對象進行其餘必要的設置,設置的地方都在對象頭中,包括這個對象所屬的類,類的元數據信息,對象的 hashcode ,GC 分代年齡等信息。
    
5)執行 init 方法
執行完上面的步驟以後,在虛擬機裏這個對象就算建立成功了,可是對於 Java 程序來講還須要執行 init 方法纔算真正的建立完成,由於這個時候對象只是被初始化零值了,尚未真正的去根據程序中的代碼分配初始值,調用了 init 方法以後,這個對象才真正能使用。

複製代碼

4 . 2 內存佈局

對象的內存佈局包括三個部分:
	- 對象頭:對象頭包括兩部分信息。
		• 第一部分,是存儲對象自身的運行時數據,如哈希碼,GC 分代年齡,鎖狀態標誌,線程持有的鎖等等。
		• 第二部分,是類型指針,即對象指向類元數據的指針。
	- 實例數據:就是數據。
	- 對齊填充:不是必然的存在,就是爲了對齊。
複製代碼

4 . 3 對象的訪問定位

句柄定位:Java 堆會畫出一塊內存來做爲句柄池,reference 中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息。

直接指針訪問:Java 堆對象的不居中就必須考慮如何放置訪問類型數據的相關信息,而 reference 中存儲的直接就是對象地址。
複製代碼

4 . 4 對象死亡

引用計數算法: 爲對象添加一個引用計數器,每當對象在一個地方被引用,則該計數器加1;每當對象引用失效時,計數器減1。但計數器爲0的時候,就表白該對象沒有被引用。

可達性分析算法: 經過一系列被稱之爲「GC Roots」的根節點開始,沿着引用鏈進行搜索,凡是在引用鏈上的對象都不會被回收。

// GC Root 對象 : 可達性的根對象
Java虛擬機棧中被引用的對象,各個線程調用的參數、局部變量、臨時變量等。
方法區中類靜態屬性引用的對象,好比引用類型的靜態變量。
方法區中常量引用的對象。本地方法棧中所引用的對象。
Java虛擬機內部的引用,基本數據類型對應的Class對象,一些常駐的異常對象。
被同步鎖(synchronized)持有的對象。

  
複製代碼

4 . 6 類加載器

// 什麼是類加載器
類加載器(ClassLoader),用來加載 Java 類到 Java 虛擬機中 , 通常來講,Java 虛擬機使用 Java 類的方式以下:Java 源程序(.java 文件)在通過 Java 編譯器編譯以後就被轉換成 Java 字節代碼(.class 文件) , 類加載器,負責讀取 Java 字節代碼,並轉換成 java.lang.Class 類的一個實例

// 發生的時期1、遇到 new、getstatic、putstatic、invokestatic 這四條字節碼指令時,若是類還沒進行初始化,則須要先觸發其初始化。
• 2、使用 java.lang.reflect 包的方法對類進行反射調用的時候,若是類還沒進行初始化,則須要先觸發其初始化。
• 3、當初始化了一個類的時候,若是發現其父類還沒進行初始化,則須要先觸發其父類的初始化。
• 4、當虛擬機啓動時,用戶須要指定一個執行的主類,即調用其 #main(String[] args) 方法,虛擬機則會先初始化該主類。
• 5、當使用 JDK7 的動態語言支持時,若是一個 java.lang.invoke.MethodHandle 實例最後的解析結果爲 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化。
    
// 加載Class 的方式
• 第一個階段,加載(Loading),是找到 .class 文件並把這個文件包含的字節碼加載到內存中。
• 第二階段,鏈接(Linking),又能夠分爲三個步驟,分別是字節碼驗證、Class 類數據結構分析及相應的內存分配、最後的符號表的解析。
• 第三階段,Initialization(類中靜態屬性和初始化賦值),以及Using(靜態塊的執行)等。

    
    
複製代碼

4 . 7 ClassLoader 詳解

// Java 中有三個類加載器
1. Bootstrap CLassloder 
	最頂層的加載類,主要加載核心類庫,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。另外須要注意的是能夠經過啓動jvm時指定-Xbootclasspath和路徑來改變Bootstrap ClassLoader的加載目錄。好比java -Xbootclasspath/a:path被指定的文件追加到默認的bootstrap路徑中。咱們能夠打開個人電腦,在上面的目錄下查看,看看這些jar包是否是存在於這個目錄。  2. Extention ClassLoader  擴展的類加載器,加載目錄%JRE_HOME%\lib\ext目錄下的jar包和class文件。還能夠加載-D java.ext.dirs選項指定的目錄。  3. AppClassLoader 加載當前應用的classpath的全部類 // classLoad 加載流程 Java 基於 Launcher 入口應用 - Launcher初始化了ExtClassLoaderAppClassLoader - // 知識點 1 父加載器不是父類 2 Bootstrap ClassLoader是由C/C++編寫的 // 經常使用方法 - 獲取父加載器 : cl.getParent() , cl.getParent().getParent() - 經過指定的全限定類名加載class : loadClass() 複製代碼

// 雙親委派
1 首先判斷這個class是否是已經加載成功 2 當 class 未加載 , 先異常往根節點查找 , 是否上層加載器已經加載 (其中若是某個層已經加載 , 則直接返回) 3 當到 Bootstrap classloader 仍然未加載 , 則由 Bootstrap classloader 到指定的路徑查找 , 若是沒有查找到 ,則由子加載器繼續到其對應路徑查找 4 到此時仍然沒有查找到 ,則返回異常 // 流程 TODO : // 思考 : 加載對象的時候是從頂層向下 , 查找對象是由底層向上 業務中咱們是可以定義多個 Classloader , 使用雙親委派避免不知道在哪一個 classLoader 中查找 , 也避免重複加載的問題 複製代碼

4 . 8 class 文件

// Class 加載流程 
1. .java 文件編譯後 , 生成一個class文件 2. classloader經過相關的規則初次找到這個class 3. 而後會讀取class的頭文件,包括如下幾種數據 a. 0xCAFEBABE:判斷是否爲Java編譯 b. 50 , 0:判斷版本號 4. String, ArrayList分別有不一樣層次的loader加載,最頂層的叫Bootstrap Classloader , 下一次級叫Extension Classloader,最底層App Classloade 5. 接着class會被加載到方法區 , 在堆中new 出的該class類的對象來確認class是否被加載 6. 每一個class會有局部變量區,還有一個操做數棧 , 線程就會按照流程執行,例如取出局部變量區的數據,放入棧中,最後運行後變成一個數後從新放入 7. 接中從棧中取出結果,從新放入變量區 8. 而線程也不必定只有一個工做臺,也可能有多個,可是隻在最上面工做(多線程狀況),這每一個工做臺叫棧幀,而多個工做臺就是方法調用方法的結果 // Java 對象頭 GC分代信息,鎖信息,哈希碼,指向Class類元信息的指針 Hotspot 虛擬機的對象頭主要包括兩部分數據:Mark Word(標記字段)、Klass Pointer(類型指針) - Klass Point 是是對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例 - Mark Word 用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC 分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程 ID、偏向時間戳等等。Java 對象頭通常佔有兩個機器碼(在 32 位虛擬機中,1 個機器碼等於 4 字節,也就是 32 bits)。可是若是對象是數組類型,則須要三個機器碼,由於 JVM 虛擬機能夠經過 Java 對象的元數據信息肯定 Java 對象的大小,沒法從數組的元數據來確認數組的大小,因此用一塊來記錄數組長度。 // Java 對象實例數據 實例數據部分是對象真正存儲的有效信息 // Java 對象對齊填充 虛擬機規範要求對象大小必須是8字節的整數倍 複製代碼

Java 對象頭的存儲結構 32 位 TODO : 待完善html

好文推薦@ 從一個class文件深刻理解Java字節碼結構_四月葡萄的博客-CSDN博客_java字節碼java

image.png

查看字節碼的方式mysql

// 方法一 : Java 基本工具類
- 查看基本的字節碼 : javap java.lang.Object
- 查看基本成員 : javap -p
- 查看詳細信息 : javap -v
- 反彙編整個類 : javap -c 

// 方法二 : 使用 ASM 查詢
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>8.0.1</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-util</artifactId>
    <version>8.0.1</version>
</dependency>

try {
    ClassReader reader = new ClassReader("java.lang.Object");
    StringWriter sw = new StringWriter();
    TraceClassVisitor tcv = new TraceClassVisitor(new PrintWriter(System.out));
    reader.accept(tcv, 0);
} catch (IOException e) {
    e.printStackTrace();
}

// 方法三 : BCEL
<dependency>
    <groupId>org.apache.bcel</groupId>
    <artifactId>bcel</artifactId>
    <version>6.5.0</version>
</dependency>

try { 
    JavaClass objectClazz = Repository.lookupClass("java.lang.Object");
    System.out.println(objectClazz.toString());
} catch (ClassNotFoundException e) { 
    e.printStackTrace(); 
}

// 方法四 : Javassist 
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.27.0-GA</version>
</dependency>

try {
    ClassPool cp = ClassPool.getDefault();
    ClassFile cf = cp.get("java.lang.Object").getClassFile();
    cf.write(new DataOutputStream(new FileOutputStream("Object.class")));
} catch (NotFoundException e) {
    e.printStackTrace();
}

// 方法五 : Jclasslib (IDEA 插件)

複製代碼

4 . 9 對象在JVM 中的表示 -- OOP-Klass

HotSpot 經過 OOP-Klass 模型來在虛擬機中表示一個對象 , 這裏的 OOP 指的是 Ordinary Object Pointer (普通對象指針),它用來表示對象的實例信息,看起來像個指針其實是藏在指針裏的對象。而 Klass 則包含元數據和方法信息,用來描述Java類。

做用 : 
	避免讓每一個對象中都含有一個vtable(虛函數表),因此就把對象模型拆成klass和oop,其中oop中不含有任何虛函數,而Klass就含有虛函數表,能夠進行method dispatch。

Klass : Java類在HotSpot中的c++對等體,用來描述Java類 , 在加載過程當中建立
	- 實現語言層面的Java類
	- 實現Java對象的分發功能
	
OOP : 在Java程序運行過程當中new對象時建立的 , 包含如下部分
    - instanceOopDesc,也叫對象頭
        - Mark Word,主要存儲對象運行時記錄信息,如hashcode, GC分代年齡,鎖狀態標誌,線程ID,時間戳等
        - 元數據指針,即指向方法區的instanceKlass實例
    - 實例數據
複製代碼

五 . GC 監控

5 .1 什麼時 GC 監控

GC 監控是指監控 JVM 執行 GC	的過程
例如 :
	> 什麼時候一個新生代被移動到老年代,以及其中被花費的時間
	> stop the world 什麼時候發生,執行了多長時間
	
> GC 訪問的接口 : GUI / CUI 兩大類
	: cUI GC 監控方法使用的獨立的 jstat 的 CUI 應用
	: cUI 或者在啓動的時候選擇JVM 參數 verbosegc
	: GUI GC 由一個單獨的圖形化界面完成 : jconsole ,jvisualvm , Visual GC
	

jstat :
參數名稱見附錄 

-verbosegc : 啓動 Java 應用時可指定
複製代碼

5 .2 常見的 GC 監控工具

• jps :虛擬機進程情況工具
	JVM Process Status Tool ,顯示指定系統內全部的HotSpot虛擬機進程。
    -q:忽略輸出的類名、Jar名以及傳遞給main方法的參數,只輸出pid。
    -m:輸出傳遞給main方法的參數,若是是內嵌的JVM則輸出爲null。
    -l:輸出徹底的包名,應用主類名,jar的徹底路徑名
    -v:輸出傳給jvm的參數
    -V:輸出經過標記的文件傳遞給JVM的參數(.hotspotrc文件,或者是經過參數-XX:Flags=指定的文件)。
    -J 用於傳遞jvm選項到由javac調用的java加載器中,
        
• jstat :虛擬機統計信息監控工具
	JVM statistics Monitoring ,是用於監視虛擬機運行時狀態信息的命令,它能夠顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。 常見的用法包括類的加載及卸載狀況 , 查看新生代、老生代及持久代的容量及使用狀況 , 查看新生代、老生代及持久代的垃圾收集狀況,包括垃圾回收的次數及垃圾回收所佔用的時間 , 查看新生代中Eden區及Survior區中容量及分配狀況
        
• jinfo :Java 配置信息工具
	JVM Configuration info ,這個命令做用是實時查看和調整虛擬機運行參數。
        
• jmap :Java 內存映射工具
	JVM Memory Map ,命令用於生成 heap dump 文件。
        
• jhat :虛擬機堆轉儲快照分析工具
	JVM Heap Analysis Tool ,命令是與 jmap 搭配使用,用來分析 jmap 生成的 dump 文件。jhat 內置了一個微型 的HTTP/HTML 服務器,生成 dump 的分析結果後,能夠在瀏覽器中查看。
        
• jstack :Java 堆棧跟蹤工具
	Java Stack Trace ,用於生成 Java 虛擬機當前時刻的線程快照。
        
• HSDIS :JIT 生成代碼反編譯

// Java 自帶
• JConsole :Java 監視與管理控制檯
	Java Monitoring and Management Console 是從 Java5 開始,在 JDK 中自帶的 Java 監控和管理控制檯,用於對 JVM 中內存,線程和類等的監控。
• VisualVM :多合一故障處理工具
	JDK 自帶全能工具,能夠分析內存快照、線程快照、監控內存變化、GC變化等。
	特別是 BTrace 插件,動態跟蹤分析工具。

// 其餘
• MAT :內存分析工具
• [GChisto](GC 日誌分析工具 —— GChisto) :一款專業分析 GC 日誌的工具。
    
    
// JMC : Java Mission Control 
-> 完整的圖形化界面
-> 提供對象查看    

複製代碼

5.3 監控經常使用命令

// 獲取 Java 程序使用的內存
Runtime#freeMemory() 方法,返回剩餘空間的字節數。
Runtime#totalMemory() 方法,總內存的字節數。
Runtime#maxMemory() 方法,返回最大內存的字節數。
複製代碼

5.4 GC 分析方式

// --------------- jconsole 使用
	- 控制檯直接輸入 : jconsole
	- 1 選擇須要調試的本地鏈接 , 點擊鏈接
	- 2 選擇遠程鏈接 , 輸入用戶名 , 口令鏈接
        
// -------------- jvisualvm 使用
	- 找到 JDK 的安裝目錄 , 點擊運行 jvisualvm.exe
	- 右側直接選擇運行中的應用     

// --------------- jstat 使用 (命令行)
jstat <option> [-t] [-h] <pid>  <interval> <count>
  參數解釋:
    option   能夠從下面參數中選擇
        -class 顯示ClassLoad的相關信息; -compiler 顯示JIT編譯的相關信息; -gc 顯示和gc相關的堆信息; -gccapacity    顯示各個代的容量以及使用狀況; -gccause 顯示垃圾回收的相關信息(通-gcutil),同時顯示最後一次或當前正在發生的垃圾回收的誘因; -gcnew 顯示新生代信息; -gcnewcapacity 顯示新生代大小和使用狀況; -gcold 顯示老年代和永久代的信息; -gcoldcapacity 顯示老年代的大小; -gcpermcapacity 顯示永久代的大小; -gcutil   顯示垃圾收集信息; -printcompilation輸出JIT編譯的方法信息; -t 能夠在打印的列加上Timestamp列,用於顯示系統運行的時間 -h   能夠在週期性數據數據的時候,能夠在指定輸出多少行之後輸出一次表頭 interval 執行每次的間隔時間,單位爲毫秒 count 用於指定輸出多少次記錄,缺省則會一直打印 案例 :  |- : Jstat -cpmpiler pid |- 查看pid爲23814的ClassLoad相關信息,每秒鐘打印一次,總共打印5次 : jstat -gc pid 1000 5 |- 顯示各個代的容量的信息 : jstat -gccapacity pid |- 顯示最近一次GC的緣由 : jstat -gccause pid |- 顯示新生代的詳細信息 : jstat -gcnew pid: |- 輸出新生代各個區的詳細信息 : jstat -gcnewcapacity pid |- 顯示老年代GC的詳細狀況 : jstat -gcold pid |- 輸出老年代的詳細信息 : jstat -gcoldcapacitp pid |- 查看每一個代區域使用的百分比狀況 : jstat -gcutil pid // ------------------- jmap 使用 jmap [option] vmid -dump : 生成Java堆轉儲快照 -heap:顯示Java堆詳細信息 -histo:顯示堆中對象統計信息 案例 : |- 使用jmap 生成快照文件 : jmap -dump:format=b,file=jsconsole.bin 7020
|- 生成一個正常運行的jconsole的快照的實例 : jps
|- 查看堆棧信息 : jmap -heap pid
|- 使用jmap 生成快照文件 : jmap -dump:format=b,file=jsconsole.bin 7020    
    
// --------------------- jhat 使用
Step 1 : 導出棧
	jmap -dump:live,file=a.log pid
Step 2 : 分析堆文件
	jhat -J-Xmx512M a1.log
Step 3 : 查看
	http://ip:7000/
Step 4 : 使用 SQL 查詢
    select <javascript expression to select>
    [from [instanceof] <class name> <identifier>] [where <javascript boolean expression to filter>] (1)class namejava類的徹底限定名,如:java.lang.String, java.util.ArrayList, [Cchar數組, [Ljava.io.Filejava.io.File[] (2)類的徹底限定名不足以惟一的辨識一個類,由於不一樣的ClassLoader載入的相同的類,它們在jvm中是不一樣類型的 (3)instanceof表示也查詢某一個類的子類,若是不明確instanceof,則只精確查詢class name指定的類 (4)fromwhere子句都是可選的 (5)java域表示:obj.field_namejava數組表示:array[index] 案例 : (1)查詢長度大於100的字符串 select s from java.lang.String s where s.count > 100 (2)查詢長度大於256的數組 select a from [I a where a.length > 256 (3)顯示匹配某一正則表達式的字符串 select a.value.toString() from java.lang.String s where /java/(s.value.toString()) (4)顯示全部文件對象的文件路徑 select file.path.value.toString() from java.io.File file (5)顯示全部ClassLoader的類名 select classof(cl).name from instanceof java.lang.ClassLoader cl (6)經過引用查詢對象 select o from instanceof 0xd404d404 o https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html // ------------- jvm jinfo |- 查看 JVM 參數 : jinfo -flags process_id |- 查看java系統參數 : jinfo -sysprops process_id 複製代碼

5.5 壓測工具擴展

// 經常使用的壓測工具
1 LoadRunner  : 預測系統行爲和性能的負載測試工具
2 Apache JMeter : 開源壓測產品
3 NeoLoad : 負載和性能測試工具
4 WebLOAD : 來自Radview公司的負載測試工具,它可被用以測試系統性能和彈性,也可被用於正確性驗證
5 阿里雲PTS : 一個SaaS性能測試平臺,具備強大的分佈式壓測能力
6 Loadstorm : 一款針對Web應用的雲端負載測試工具,經過模擬海量點擊來測試Web應用在大負載下的性能表現
7 CloudTest : 一個集性能和功能測試於一體的綜合壓力測試雲平臺
8 Load impact : 一款服務於DevOps的性能測試工具,支持各類平臺的網站、Web應用、移動應用和API測試
    
// JMeter 使用
    
複製代碼

5.6 Jstack 使用

> Step 1 : 拿到 pid
ps -ef | grep java

> Step 2 : 查看資源進程
top -Hp 30275

printf "%x\n" 3440


> 簡單使用
jstack 30275
    
> 查看指定進程
printf "%x\n" 17880  
jstack 17880|grep 45d8 -A 30
    
// 查看 TimeWait 


// Windows 版本
netstat -ano |findstr "80" windows
netstat -an | find "TIME_WAIT" /C    
    
// jstack 統計線程數
jstack -l 28367 | grep 'java.lang.Thread.State' | wc -l    
    
    
// jstack 

Usage:
    jstack [-l] <pid>
        (to connect to running process) 鏈接活動線程
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process) 鏈接阻塞線程
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file) 鏈接dump的文件
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server) 鏈接遠程服務器

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung) -m to print both java and native frames (mixed mode) -l long listing. Prints additional information about locks -h or -help to print this help message 複製代碼

5.7 JOL 使用

JOL:查看Java 對象佈局、大小工具

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>put-the-version-here</version>
</dependency>

static Object generate() {
	Map<String, Object> map = new HashMap<>();
	map.put("a", new Integer(1));
	map.put("b", "b");
	map.put("c", new Date());
 
	for (int i = 0; i < 10; i++) {
		map.put(String.valueOf(i), String.valueOf(i));
	}
	return map;
}

查看對象內部信息: ClassLayout.parseInstance(obj).toPrintable()
查看對象外部信息:包括引用的對象:GraphLayout.parseInstance(obj).toPrintable()
查看對象佔用空間總大小:GraphLayout.parseInstance(obj).totalSize()
複製代碼

5.8 Thread Dump

Thread Dump是很是有用的診斷Java應用問題的工具。每個Java虛擬機都有及時生成全部線程在某一點狀態的thread-dump的能力,雖然各個 Java虛擬機打印的thread dump略有不一樣,可是大多都提供了當前活動線程的快照,及JVM中全部Java線程的堆棧跟蹤信息,堆棧信息通常包含完整的類名及所執行的方法,若是可能的話還有源代碼的行數。

1. 查找內存泄露,常見的是程序裏load大量的數據到緩存;
2. 發現死鎖線程;


// Linux 抓取 Dump 的方式 (20810 是 jstack 在Java 目錄下 )
jstack -l 20810 | tee -a /opt/jstack.log

// 簡單學習 : 
// 虛擬機信息
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.251-b08 mixed mode): // 線程info信息塊: // 線程名稱 - #36 - 線程類型 (daemon) - 優先級 (prio) // tid : JVM 線程ID // nid : 對應系統線程id // 線程狀態:in Object.wait(). // 起始棧地址:[0xae77d000] "Attach Listener" #36 daemon prio=9 os_prio=0 tid=0x00007f5fec001000 nid=0x6658 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None
        
// 堆棧信息
線程狀態 - java.lang.Thread.State: WAITING (parking)
線程拋出節點 -	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e4e7bdf8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
	at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:107)
	at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)

// 方案
cpu飆高,load高,響應很慢 --> 單請求 dump 屢次
查找佔用cpu最多的線程信息 --> 對對應的線程進行 dump , 先 top 查詢對應 id
cpu使用率不高可是響應很慢 --> 進行dump,查看是否有不少thread struck在了i/o、數據庫等地方,定位瓶頸緣由
請求沒法響應 --> 屢次dump,對比是否全部的runnable線程都一直在執行相同的方法

// 常見分析 
死鎖 , 熱鎖  
複製代碼

六 . GC 優化

6 . 1 GC 優化的前提

> GC 優化永遠是最後一項任務

> 原則 :
	> 將轉移到老年代的對象數量降到最少
		:調整新生代空間的大小。
	> 減小 Full GC 的執行時間
		: 你須要將老年代空間設定爲一個「合適」的值
複製代碼

6 . 2 GC 優化的方案

> 使用  StringBuilder 或者StringBuffer 來替代String
> 儘可能少的輸出日誌

GC 優化考慮的參數
複製代碼

6 . 3 GC 優化須要考慮的參數

定義 參數 描述
堆內存空間 -Xms Heap area size when starting JVM啓動JVM時的堆內存空間。
-Xmx Maximum heap area size堆內存最大限制
新生代空間 -XX:NewRatio Ratio of New area and Old area新生代和老年代的佔比
-XX:NewSize New area size新生代空間
-XX:SurvivorRatio Ratio of Eden area and Survivor area伊甸園空間和倖存者空間的佔比

6 . 4 GC類型可選參數

分類 參數 備註
Serial GC -XX:+UseSerialGC
Parallel GC -XX:+UseParallelGC -XX:ParallelGCThreads=value
Parallel Compacting GC -XX:+UseParallelOldGC
CMS GC -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=value -XX:+UseCMSInitiatingOccupancyOnly
G1 -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC 在JDK6中這兩個參數必須同時使用

6 . 5 GC 優化過程

1 > 監控 GC 狀態
2 > 分析監控結果 , 考慮是否須要GC
3 > 調整 GC 類型 , 分配存儲空間
4 > 分析結果
複製代碼

6 . 6 內存溢出的狀況及分析

> 1 堆棧溢出
	- java.lang.OutOfMemoryError: ......java heap space.....
	- 看到heap相關的時候就確定是堆棧溢出 , 適當調整 -Xmx和-Xms
	- 訪問量太多而且每一個訪問的時間太長或者數據太多,致使數據釋放不掉
        - java.lang.OutOfMemoryError:GC over head limit exceeded -- 系統處於高頻的GC狀態,並且回收的效果依然不佳
> 2 PermGen的溢出
	- java.lang.OutOfMemoryError: PermGen space
	- 系統的代碼很是多或引用的第三方包很是多、或代碼中使用了大量的常量、或經過intern注入常量、或者經過動態代碼加載等方法,致使常量池的膨脹
	-XX:PermSize和-XX:MaxPermSize的大小
        
> 3 ByteBuffer中的allocateDirect() 溢出
    - java.lang.OutOfMemoryError: Direct buffer memory
    -直接或間接使用了ByteBuffer中的allocateDirect方法的時候,而不作clear的時候就會出現相似的問題
	-XX:MaxDirectMemorySize
        
> 4 java.lang.StackOverflowError
    - java.lang.StackOverflowError
    - -Xss過小了,咱們申請不少局部調用的棧針等內容是存放在用戶當前所持有的線程中的
        
> 5 java.lang.OutOfMemoryError: unable to create new native thread
    - 說明除了heap之外的區域,沒法爲線程分配一塊內存區域了,這個要麼是內存自己就不夠,要麼heap的空間設置得太大了

> 6 java.lang.OutOfMemoryError: request {} byte for {}out of swap
    - 通常是因爲地址空間不夠而致使
複製代碼

6 . 7 Full GC 緣由分析及解決

// 緣由 : 
1 . 應用程序建立了太多沒法快速回收的對象。
2 . 當堆被分割時,即便有不少空閒空間,在老代中直接分配也可能失敗

https://blog.gceasy.io/2020/06/02/simple-effective-g1-gc-tuning-tips/
https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html
複製代碼

七 小知識點

7 . 1 Jar 相關

TODO
複製代碼

7 . 2 CPU

TODO
複製代碼

八 其餘

# 32 位 JVM 和 64 位 JVM 的最大堆內存分別是多少

理論上說上 32 位的 JVM 堆內存能夠到達 2^32,即 4GB
4 位 JVM 容許指定最大的堆內存,理論上能夠達到 2^64 
複製代碼

# 直接內存(堆外內存)

> 直接內存(Direct Memory),並非虛擬機運行時數據區的一部分,也不是 Java 虛擬機規範中農定義的內存區域
> NIO(New Input/Output) 類,引入了一種基於通道(Channel)與緩衝區(Buffer)的 I/O 方式,它可使用 native 函數庫直接分配堆外內存,而後通脫一個存儲在 Java 堆中的 DirectByteBuffer 對象做爲這塊內存的引用進行操做

// 對比
直接內存申請空間耗費更高的性能,當頻繁申請到必定量時尤其明顯
直接內存 IO 讀寫的性能要優於普通的堆內存,在屢次讀寫操做的狀況下差別明顯
複製代碼

# 其餘工具

> GCEasy : 
複製代碼

# JMC 分析流程

// Step 1 : 開啓指定應用的飛行記錄

// Step 2 : 分析模塊 , 飛行記錄提供瞭如下幾個模塊
> 通常信息 :  | 概述  | JVM 信息 | 系統屬性 | 記錄
    - CPU 佔用率 : 能夠判斷是否CPU佔滿致使的緩慢
    - 堆使用率 : 內存使用狀況會致使垃圾收集的頻率 , Redis 的使用(AOF/RDB持久化異常) ,
    - JVM 信息 : 能夠了解到當前使用的虛擬機類型(不一樣類型虛擬機會使用不一樣的回收策略 , 以及使用的JDK , 配置的 JVM 參數等)
        
> 內存 : | 概述 | 垃圾收集 | GC 時間 | GC 配置 | 分配 | 對象統計信息
    - 概述 : 
		- GC 配置 : 包含 GC 的配置信息 , 以及對應的收集器類型
		- GC 統計時間 :  
    - 垃圾收集 : 包含垃圾收集的次數和消耗的時間
        -> 垃圾收集的頻率是否正常 , 是否過於頻繁
        -> 每次消耗的時候會不會太長 ?stop-world 後會影響其餘的運行
	- GC 時間 : 該時間爲不一樣年齡代的時間
	- GC 配置 : 配置的 GC 線程數 , 堆內存等配置詳情
	- 分配 : 主要是 TLAB 的分配狀況 , 

> 代碼 : | 概述 | 熱點方法 | 調用樹 | 異常錯誤 | 編譯 | 類加載
    - 熱點方法 : 判斷代碼中對相關方法的調用是否合理(對應類的堆會不會過大 , 對象會不會過多)
    - 熱點方法 : 判斷經常使用的對象會不會有多線程風險及死鎖風險 , 是否效率太低
    - 調用樹 : 經過調用樹追溯問題的根源
    - 異常錯誤 : 判斷是否存在異常
    
> 線程 : | 概述 | 熱點線程 | 爭用 | 等待時間 | 線程轉儲 | 鎖定實例
    - 判斷死鎖
    - 判斷線程的銷燬狀況
    - 判斷是否有切換線程帶來的損失 (頻繁切換) , 熱鎖的狀況
    - 判斷線程是否合理使用了線程池等工具
    
> IO :   | 概述 | 文件讀寫 | 套接字讀寫 | 
    - 這個模塊能夠有效的分析是否爲文件讀寫時間致使的延遲或者套接字訪問致使的系統緩慢
    
> 系統信息及環境變量 , 略
    
> 事件 : 發生的事件比例 , 包括 Java Thread Park , Java Thread start , Java Thread end 等    
複製代碼

# skywalking 分析流程

skywalking 是鏈路分析工具 , 是很好的輔助工具 , 能快速的分析瓶頸點

// 使用方式(不過多簡述 , 不少) , 注意須要點擊一下刷新纔會出數據
-javaagent:D:\java\plugins\shywalking\agent82\skywalking-agent.jar    
    
// 經常使用用法: 
- 儀表盤 : 用於查看各服務器狀態    
- 拓撲圖 : 用於分析服務器的結構是否合理 (redis 服務器 , mysql 服務器 , 等等其餘的)
- 追蹤 : 能夠快速判斷慢SQL , 慢 接口    
- 性能分析 : (建立的端點是追蹤裏面的端點,點擊分析後能夠直接追蹤到對應的代碼行)    
複製代碼

九 優化的流程

9.1 壓測流程

> 對鏈接數進行優化 , 包括
	- Mysql 鏈接數 
	- redis 鏈接數
	- Mysql 鏈接池數量 (鏈接池數量不是越大月好)
	- Redis 鏈接池數量
	- tomcat 鏈接數
	- TCP 鏈接數 (包括指定端口 , 隨機端口 , 端口總數限制)
	- LDAP 鏈接數
	- OpenFile 鏈接數
	
> 對 GC 進行分析
	- 1 查看 CPU 使用狀況 : top
	- 2 查看指定進程的使用 : top -Hp [進程ID]
	- 3 分析當前進程棧 : jstack [進程ID] > jstack_01
	- 4 查看 GC 狀況 : jstat -gcutil [進程id] 1000
	- 5 查看堆內存 : jmap -histo [進程id] > jmap.txt
	- 6 打印堆內存快照 : jmap -dump:format=b,file=aa.bin 1232134
	
> 對 GC 進行優化
	// 80 時進行 GC
	- -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly
	// 保留 GC log 
	- -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log
	
>  負載均衡
	- 判斷負載的方式是輪詢仍是壓力 
	
// 優化 新生代容積
-Xmn350M -> -Xmn800M
-XX:SurvivorRatio=4 -> -XX:SurvivorRatio=8
-Xms1000m ->-Xms1800m
    
// 優化 metaspace
-Xmn350M -> -Xmn800M
-Xms1000M ->1800M
-XX:MetaspaceSize=200M
-XX:CMSInitiatingOccupancyFraction=75    	
複製代碼

附錄 :

Jstat 參數名稱

參數名稱 描述
gc 輸出每一個堆區域的當前可用空間以及已用空間(伊甸園,倖存者等等),GC執行的總次數,GC操做累計所花費的時間。
gccapactiy 輸出每一個堆區域的最小空間限制(ms)/最大空間限制(mx),當前大小,每一個區域之上執行GC的次數。(不輸出當前已用空間以及GC執行時間)。
gccause 輸出-gcutil提供的信息以及最後一次執行GC的發生緣由和當前所執行的GC的發生緣由
gcnew 輸出新生代空間的GC性能數據
gcnewcapacity 輸出新生代空間的大小的統計數據。
gcold 輸出老年代空間的GC性能數據。
gcoldcapacity 輸出老年代空間的大小的統計數據。
gcpermcapacity 輸出持久帶空間的大小的統計數據。
gcutil 輸出每一個堆區域使用佔比,以及GC執行的總次數和GC操做所花費的事件。
說明 Jstat參數
S0C 輸出Survivor0空間的大小。單位KB。 -gc -gccapacity -gcnew -gcnewcapacity
S1C 輸出Survivor1空間的大小。單位KB。 -gc -gccapacity -gcnew -gcnewcapacity
S0U 輸出Survivor0已用空間的大小。單位KB。 -gc -gcnew
S1U 輸出Survivor1已用空間的大小。單位KB。 -gc -gcnew
EC 輸出Eden空間的大小。單位KB。 -gc -gccapacity -gcnew -gcnewcapacity
EU 輸出Eden已用空間的大小。單位KB。 -gc -gcnew
OC 輸出老年代空間的大小。單位KB。 -gc -gccapacity -gcold -gcoldcapacity
OU 輸出老年代已用空間的大小。單位KB。 -gc -gcold
PC 輸出持久代空間的大小。單位KB。 -gc -gccapacity -gcold -gcoldcapacity -gcpermcapacity
PU 輸出持久代已用空間的大小。單位KB。 -gc -gcold
YGC 新生代空間GC時間發生的次數。 -gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
YGCT 新生代GC處理花費的時間。 -gc -gcnew -gcutil -gccause
FGC full GC發生的次數。 -gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
FGCT full GC操做花費的時間 -gc -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
GCT GC操做花費的總時間。 -gc -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
NGCMN 新生代最小空間容量,單位KB。 -gccapacity -gcnewcapacity
NGCMX 新生代最大空間容量,單位KB。 -gccapacity -gcnewcapacity
NGC 新生代當前空間容量,單位KB。 -gccapacity -gcnewcapacity
OGCMN 老年代最小空間容量,單位KB。 -gccapacity -gcoldcapacity
OGCMX 老年代最大空間容量,單位KB。 -gccapacity -gcoldcapacity
OGC 老年代當前空間容量制,單位KB。 -gccapacity -gcoldcapacity
PGCMN 持久代最小空間容量,單位KB。 -gccapacity -gcpermcapacity
PGCMX 持久代最大空間容量,單位KB。 -gccapacity -gcpermcapacity
PGC 持久代當前空間容量,單位KB。 -gccapacity -gcpermcapacity
PC 持久代當前空間大小,單位KB -gccapacity -gcpermcapacity
PU 持久代當前已用空間大小,單位KB -gc -gcold
LGCC 最後一次GC發生的緣由 -gccause
GCC 當前GC發生的緣由 -gccause
TT 老年化閾值。被移動到老年代以前,在新生代空存活的次數。 -gcnew
MTT 最大老年化閾值。被移動到老年代以前,在新生代空存活的次數。 -gcnew
DSS 倖存者區所需空間大小,單位KB。 -gcnew

虛擬機常見配置快查

說明 命令
開啓 GC Log (java8) -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:{file-path}
開啓 GC Log (java9) -Xlog:gc*:file={file-path}

致謝

// 此篇筆記是一個總結分析的筆記 , 時間週期較長 , 不少知識點已經難以追溯出處 , 若是此處遺漏了某位道友 ,敬請諒解

Java 技術驛站 , 一系列死磕看的至關爽
http://cmsblogs.com/?p=5140

CSDN
http://blog.csdn.net/linxdcn/article/details/72896616

芋道源碼 , 很不錯的源碼博客
http://www.iocoder.cn/

掘金老哥
https://juejin.cn/post/6844903753296920583

CSDN 
https://blog.csdn.net/briblue

以及全部對該文章有所幫助的表示感謝

複製代碼
相關文章
相關標籤/搜索