帶你揭開JVM的神祕面紗

JVM學習文檔

引言

1.什麼是JVM?
2.學習JVM有什麼用呢?
3.常見的JVM有哪些?
4.學習線路

JVM介紹

​ 定義:Java Virtual Machine = java 程序的運行環境 ( java二進制字節碼的運行環境 )。 JVM是一套規範,由不同的廠商實現。

​ 好處:

    1.使得java程序可以一次編寫,到處運行,Java虛擬機從軟件層面屏蔽了不同操作系統之間的底層差異。

    2.提供了自動內存管理機制,垃圾回收功能。

    3.數組下標越界檢查。

    4.多態機制的實現。

​ 比較:

​ 面試題:jvm、jdk、jre 三者之間的關係? 包含關係。

	jdk = jvm + 基礎類庫+編譯工具
	jre = jvm + 基礎類庫

​ 學習JVM有什麼用?

    1.面試。
    2.理解底層的實現原理。(更好理解:自動拆裝箱、動態代理的原理。)
    3.中高級程序員的必備既能!

​ 常見的JVM:

​ 此次學習以HotSpot vm 爲準。

學習線路圖

在這裏插入圖片描述

一、JVM內存結構

1.程序計數器

1.1 作用

​ 記住下一條JVM指令的執行地址。

在這裏插入圖片描述

1.2 特點

    1.是線程私有的。每一個線程都會有一個程序計數器。
    2.唯一一個不會存在內存溢出結構。

2.虛擬機棧

2.1 定義

​ 每一條線程運行時需要的總內存空間,稱爲虛擬機棧。也稱爲 線程棧。

2.2 特點

​ 一個線程棧由多個【棧幀】組成。當需要調用方法時,線程棧會爲該方法開闢一塊棧幀空間,方法執行完後棧幀彈棧並釋放棧幀內存。

    棧幀:每個方法運行時需要的內存。

    活動棧幀:指的就是正在執行的那個方法。

2.3 問題辨析

1.垃圾回收是否涉及棧內存?
	不涉及,線程棧內存僅涉及到方法的一次次的調用與釋放。
2.棧內存分配越大越好嗎? 
	棧內存分配越大,線程數就會變少。
3.方法內的局部變量是否線程安全?
    如果方法內的局部變量沒有逃離方法的作用範圍,是線程安全的。
    如果是局部變量引用了對象,並逃離方法的作用範圍,需要考慮線程安全問題。

2.4 棧內存溢出問題

棧內存溢出異常:java.lang.StackOverflowError

原因:
	1.棧幀過多導致棧內存溢出。如:遞歸調用。
	2.棧幀過大導致棧內存溢出。

2.5 線程運行診斷

案例1: CPU佔用過多
定位:(Linux環境下)
1.用top定位那個進程對cpu的佔用過高
2.ps H -eo pid,tid,%cpu | grep +進程id (用ps命令進一步定位是那個線程引起的cpu佔用過高)
3.使用jdk自帶工具 jstack + 進程id
可以根據線程id找到有問題的線程,進一步定位到問題代碼的源代碼行號。
案例2: 遲遲得不到結果。 考慮 線程死鎖

3.本地方法棧

3.1 定義

​ jvm在調用本地方法時爲本地方法提供的內存空間。

​ 指的就是那些不是由Java代碼編寫的方法。底層與操作系統打交道的是 c/c++ 如:clone()、notify()方法等。

4.堆內存

4.1 定義

​ 通過new關鍵字創建的對象,都會使用堆內存。

4.2 特點

	1.是線程共享的內存區域,堆中的對象都要考慮線程安全的問題。
	2.有垃圾回收機制。 

4.3 堆內存溢出

​ java.lang.OutOfMemoryError Java heap space

4.4 堆內存診斷

    1.jps 工具
        查看當前系統中存在哪些Java進程
    2.jmap 工具  使用:jmap + -heap + 進程id
        查看當前線程堆內存佔用情況。
    3.jconsole 工具
        圖形界面的,多功能的檢測工具,可以連續監測。

案例:
垃圾回收後,內存佔用仍然很高,怎麼查看並排除?【有實際價值】

解決方法:
	使用更實用的jdk官方工具: jvirsualvm 內存佔用可視化工具--使用 堆dump 將佔用情況抓取下來分析。

5.方法區

5.1 定義

	方法區是線程共享的一塊區域,存儲的是與類結構相關的如常量池等,還包括在類、實例、接口初始化時用到的特殊方法。在虛擬機啓動時被創建,邏輯上是屬於堆內存的一部分,但是在具體實現上不同虛擬機的廠商不一定遵從jvm邏輯規範,實現方式有所不同。

方法區的內存結構變化圖

在這裏插入圖片描述

5.2 特點

    1.線程共享
    2.方法區中存儲的是與類相關的一些信息,如:成員變量,成員方法,構造方法、運行時常量池等。
    3.由虛擬機啓動時創建,邏輯上是堆內存的一部分。
    4.jdk 1.8之前 被稱爲永久代,屬於堆內存的一部分。1.8之後,被稱爲元空間,使用的是本地內存(使用操作系統的內存)。

5.3 方法區的內存溢出

	實際場景:框架中使用Cglib來動態生成並加載代理類,很容易產生永久代內存溢出。如: spring、 Mybatis 

5.4 常量池

5.4.1 定義
	常量池是 *.class中的概念,本質就是一張表,虛擬機指令根據符號地址從這張常量表找到要執行的類名、方法名、參數類型、字面值常量等信息。
5.4.2 作用

​ 給JVM指令提供一些常量符號,虛擬機執行引擎中的解釋器會根據符號找到對應的信息。

5.4.3 常量池與StringTable的關係

在這裏插入圖片描述

	在程序運行時,常量池中的信息,都會被加載到運行時常量池中,字符串對象在未使用時僅僅是常量池中的一個符號,當執行到 String s = "a" 時,纔會創建字符串對象,此時,會把 a 作爲 key值從 StringTable 查找,如果有則直接用,如果沒有則把 符號 "a"變爲字符串對象,放入StringTable中。

5.5 運行時常量池

5.5.1定義

​ 當類被加載進內存時,他的常量池信息就會被放入到運行時常量池(在方法區中),並將常量池中的符號地址變爲真實的內存地址。

5.5.2 StringTable的內存位置

在這裏插入圖片描述

5.5.3 StringTable特性
    1. 常量池中的字符創僅是符號,第一次使用時纔會變成對象。
    2. 利用串池的機制,可以避免重複創建字符串對象。
    3. 字符串變量拼接的底層原理是使用StringBuiler對象拼接。(jdk1.8)
    4. 字符串常量拼接的原理是編譯期的優化。
    5. 可以使用intern方法,主動將串池中還沒有的字符串對象放入串池。

* intern()方法在jdk1.6和jdk1.8 底層實現的區別?

	jdk1.8中,將內存中的字符串對象嘗試放入串池,如果串池中有則不會放入,如果沒有則放入串池,並把放入串池中的對象返回。

	jdk1.6中,將內存中的字符串對象嘗試放入串池,如果串池中有則不會放入,如果沒有則【會把該字符串對象複製一份】放入串池,並把放入串池中的對象返回。
案例1

在這裏插入圖片描述

案例2

在這裏插入圖片描述

5.5.4 StringTable的性能調優

​ pass

6.直接內存

6.1 特點
    1.屬於操作系統內存。
    2.常見於NIO操作時,用於數據緩衝區。
    3.分配回收成本較高,但讀寫性能高。
    4.不受JVM內存回收管理。
  1. Java文件讀寫過程之BIO

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NDfzW3E1-1591013381172)(.\images\Java文件讀寫過程_BIO_01.png)]

  1. Java文件讀寫過程之NIO 提高了文件讀寫的效率。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QZpb9Anx-1591013381173)(.\images\Java文件讀寫過程_NIO_02.png)]

6.2 直接內存分配和回收的原理
	1.使用了Java最底層的一個 Unsafe 對象完成了直接內存的分配回收,並且通過主動調用 freeMemory 方法來回收直接內存。 
	2.ByteBuffer 的實現類內部,使用了Cleaner(虛引用) 來監測ByteBuffer對象,一旦 ByteBuffer對象被垃圾回收,那麼就會由ReferenceHandler線程
	3.通過Cleaner的clean()方法調用 freeMemory來釋放直接內存。

二、垃圾回收機制

內容介紹

	1.如何判斷對象可以回收
    2.垃圾回收算法
    3.分代垃圾回收算法
    4.垃圾回收器
    5.垃圾回收調優

1. 如何判斷對象可以回收?

1.1 兩個對象回收算法
引用計數法:指一個對象被其他變量引用,就讓引用計數加1,當被兩個對象引用時就加2,如果沒有變量引用時,計數爲 0.
			   
	 【缺點】:
			1.循環引用問題。 A對象引用B,B引用A,AB均無其他對象引用。由於AB對象的引用計數均爲1,
		所以AB對象不會被當作垃圾回收,從而造成了內存泄漏問題。
		
			2.早期Python虛擬機在進行垃圾回收時就採用了引用計數算法。

可達性分析算法(JAVA虛擬機採用)
			
		根對象:肯定不能當成垃圾被回收的對象就是根對象。(例子:一串葡萄,掉在盤子裏的就可被回收。)
	
	特點:
		1. java虛擬機中的垃圾回收機器採用可達性分析來探索所有存活的對象。
		2. 掃描堆中的對象,看是否能夠沿着GC Root對象爲起點的引用鏈找到該對象,找不到,表示可以回收。
		
    問:哪些對象可以作爲GC Root?
				
				a.虛擬機棧(棧楨中的本地變量表)中的引用的對象 
				b.方法區中的類靜態屬性引用的對象 
				c.方法區中的常量引用的對象 
				d.本地方法棧中JNI的引用的對象
1.2對象的四種引用

在這裏插入圖片描述

面試題: 請你說一下Java虛擬中的四種引用? 

		常見的是五種,分別是強引用、弱引用、虛引用、軟飲用、終結器引用。
		
1.2.1 強引用
定義:
	我們平常用的對象都是強引用。如 Student s = new Student();通過【賦值號】s就【強引用】了學生對象。
	
特點:

	* 只要沿着GC Root根對象的引用鏈能夠找到該對象,就不會被回收。只有所有的GC Root對象都不通過【強引用】引用該對象,該對象才能被垃圾回收。
1.2.2軟引用
特點:

	* 只有沒有被直接的強引用而引用,當垃圾回收發生後,內存仍然不足時,就會回收軟引用對象。 
	* 可以配合引用隊列來釋放軟引用自身。
1.2.3 弱引用
特點:

	* 只要沒有被直接的強引用而引用,只要垃圾回收動作發生,不管內存是否充足,都會回收弱引用對象。
	* 可以配合引用隊列來釋放弱引用自身。
1.2.4 虛引用
特點:

	* 虛引用必須配合引用隊列使用,主要配合 ByteBuffer使用,被引用對象回收時,會將虛引用入隊,由
Reference Handler線程調用虛引用相關方法釋放直接內存。
1.2.5終結器引用
特點:
	無需手動編碼,但是內部配合引用隊列使用,在垃圾回收時,終結器引用入隊(被引用對象暫時沒有被回收),再由
Finalizer 線程通過終結器引用找到被引用的對象並調用它的finalize()方法,第二次GC 時才能被回收。

2.垃圾回收算法

2.1 標記清除算法

在這裏插入圖片描述

2.2 標記整理算法

在這裏插入圖片描述

2.3 複製算法

在這裏插入圖片描述

3.分代垃圾回收機制

3.1 垃圾回收執行流程

在這裏插入圖片描述

3.2 與GC 相關 vm參數

在這裏插入圖片描述

4. 垃圾回收器的分類

4.1.串行
特點:
    * 單線程
    * 適用於 堆內存較小,適合個人電腦。

在這裏插入圖片描述

4.2.吞吐量優先
特點:
    * 多線程
    * 堆內存較大,多核cpu
    * 讓單位時間內,STW的時間最短 
    
    吞吐量: 垃圾回收時間佔程序運行時間的比重,垃圾回收時間越短,吞吐量越大。

在這裏插入圖片描述

4.3.響應時間優先
特點:
    * 多線程
    * 堆內存較大,多核cpu
    * 儘可能讓單次STW的時間最短

在這裏插入圖片描述

4. 4G1 垃圾回收器
4.4.1 定義:

​ Garbage First

4.4.2 適用場景/特點
1、同時注重吞吐量和低延遲、默認的暫停目標是 200ms.
2、超大堆內存,會將堆內存劃分爲多個大小相等的Region. 
		注意:每個區域都可以獨立作爲伊甸園區、倖存區、和老年代區。
3、整體上是標記+清除算法,兩個區域之間採用的是複製算法.

相關的jvm參數:

    -xx:+UseG1Gc
    -xx:G1HeapRegionSize = size
    -xx:MaxGCPauseMillis = time 
4.4.3 G1垃圾回收階段

在這裏插入圖片描述

4.4.4 Young COllection 新生代垃圾回收
4.4.5 Young Collection + CM 垃圾回收階段

在這裏插入圖片描述

4.4.6 Mixed Collection垃圾回收階段

在這裏插入圖片描述

垃圾回收階段

未完待續…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章