什麼JVM優化不熟悉?回去等消息吧

jvm 內存結構

前言

對於中級程序猿或者高級程序猿,可以說jvm這塊知識是必須掌握的了吧,畢竟在跳槽找工作面試時,都會提問關於jvm的相關知識,與其死記硬背,不如靜下心來主動捅破這層窗戶,深入瞭解下關於jvm的知識點;

jvm 內存結構

jvm的內存結構主要分爲 4部分,其中主要分析的是運行時數據區。運行時數據區包含 程序計數器,堆,棧,本地方法棧以及方法區,其中堆和方法區是線程共享的,其他的3種是線程私有的。jvm的優化主要發生在 堆和方法區
jvm內存結構

程序計數器

程序計數器線程私有的,他是一塊比較小的內存空間,可以將它看成是當前線程執行字節碼的行號顯示器。更加確切的說,一個線程的執行,是通過字節碼解釋器來改變當前線程的計數器的值,來獲取到下一條需要執行字節碼的指令,從而保證線程的正常執行;

如果執行的是java程序,那麼顯示的是正在執行的虛擬機字節碼指令地址,如果執行的是Native 程序,那麼展示的是undefined ;

虛擬機棧

什麼是棧

瞭解虛擬機棧之前,首先了解下什麼是棧?
棧 是一種僅能在表頭進行插入和刪除的線性表結構,及所說的棧是一種先進後出的;
棧的數據結構
線程私有的,它的生命週期是和線程綁定在一起的,線程執行前,都會分配一個獨立的棧空間;

棧中主要存儲了什麼

的元素是棧幀,每個方法在執行時都會創建一個棧幀,棧幀裏主要存儲了局部變量表,操作數棧,方法出口以及動態連接等信息;其實每個方法在調用執行的時候,就對應了一個入棧和出棧的過程。

局部變量表

在棧幀中,是由局部變量表進行存儲數據的,那麼局部變量表主要存儲了基本數據類型的局部變量,對象的引用,但是局部變量表中並不存儲對象的內容。局部變量表在程序進行編譯時,進行內存空間的分配。

操作數棧

操作數棧的元素是java 中的任意數據類型,在編譯時,操作數棧是空的,在方法執行的過程中,通過字節碼指令完成操作數棧的入棧和出棧過程。通常在進行算術運算的時候是通過操作數棧完成的。所以可以說操作數棧是用於計算的臨時儲存區

棧的異常

棧的異常主要有兩種:

  1. StackOverflowError:棧溢出錯誤
    線程所需要的棧內存大小> 配置允許的棧內存大小,那麼java虛擬機會拋出 棧內存溢出溢出

  2. OutOfMemoryError:內存不足
    棧內存在進行擴展時,申請的內存大於剩餘的內存時,會拋出內存不足的異常;

  3. 在面試過程中,會提問到,爲什麼使用遞歸會造成棧溢出? 原因是,遞歸每次調用時,都會創建一個棧幀,這樣會導致,遞歸層數越深,所需要的棧的大小超過jvm設置的棧大小,所以會拋出StackOverflowError的異常;

什麼是堆?

堆是完全二叉樹,也就是說除了樹的最後一層節點不需要是滿的,其他的每一層從左到右都必須是滿的。
堆結構

java堆

在java中,是java 虛擬機管理最大的內存區域,它是線程共享的,主要存放了new 關鍵字創建的對象。所有對象的實例都是在堆上進行分配的,它也是發生垃圾回收的主要區域;

java 堆又分爲年輕代(Young Generation) ,年老代(Old Gereration,且年輕代和年老代的比例是1:2 的關係。在年輕代中又分爲 Eden , to Survivor ,from Survivor 空間,他們的關係是8:1:1 的關係。
堆得內存結構
年輕代中主要存儲的是“新生對象”,當年輕代滿了之後,會出發minor gc 來清理年輕代內存;

年老代中主要存儲的是長期存活的對象或者大對象,所以在新生代多次未清理掉的對象會進入年老代中。當年老代滿了之後,會出發full gc

需要注意的是,full gc不單單是隻清理年老代,而是清理整個內存空間,包含年輕代和年老代。如果full gc 清理之後,還是無法存儲對象,那麼就會發生我們常見的內存溢出;
堆內存分配

方法區

什麼是方法區

方法區是同java 的堆一樣,是線程共享的,主要用來存儲已經被加載的類的信息,常量,靜態變量,以及及時編譯器編譯後的代碼。可以更形象的說 靜態變量,常量,類信息,常量池都是存在方法區中的;

需要注意的是,在jdk1.8 之後,取消了方法區,而取而代之的是MetaSpace 元空間,元空間不是存在jvm中的,而是存在於本地內存中的;
方法區

java 垃圾回收

垃圾判斷

既然要進行垃圾回收,首先需要判斷哪些是垃圾。在java 中,判斷是否是垃圾有兩種方式,分別是 引用計數法,可達性分析

引用計數法

引用計數法很簡單,就是一個對象被引用時加一,去除引用時減一,這樣我們就可以進行判斷引用計數是否爲零 來判斷當前對象是否是垃圾了。

已用計數方法雖然簡單,但是存在一個問題,在進行循環引用時,無法判斷是否爲垃圾對象。例如:a ,b兩個對象,a引用了 b, b引用了a ,但是a ,b對象未被其他對象引用,理論上來講,a ,b 兩個對象是屬於垃圾回收範圍內的,但是他們的計數器並不爲0 ,所以無法將a ,b 兩個對象進行垃圾回收;

可達性分析

可達性分析的思路是通過 “GC Roots ” 的對象作爲起點,從這個起點向下搜索,所搜索的路徑稱爲“引用鏈” ,當一個對象沒有任何引用鏈的時候,那麼證明這個對象是不可用的,則會被垃圾回收掉;
可達性分析
在java 中,可以作爲 “GC Roots” 的對象主要有幾下幾種:

  1. 虛擬機棧中的引用對象
  2. 方法區中類靜態引用對象
  3. 方法區中常量引用對象

垃圾回收算法

垃圾回收算法主要有: 標記-清除,標記-整理,複製 ,分代收集算法。

標記-清除

標記清除算法主要包含了標記清除兩個階段。標記階段是由GC Roots 觸發的可達對象。清除階段是清除未被標記的對象。

缺點: 標記清除會產生內存碎片問題,如果內存碎片過多,會導致內存地址不連續,在後續內存分配是效率會降低;
標記清除

標記-整理

標記整理和標記清除很相像,只不過標記整理是在標記完之後,會將存活的對象向一邊移動,然後清理未標記的對象
缺點:整理時比較耗時

標記整理

複製

複製算法是每次都將內存劃分兩塊,每次只使用其中的一塊,當這塊用完時,會將存活的對象複製到另外一塊內存上,然後將已經使用過的進行一次內存清理。
缺點: 標記佔用內存,當對象存活率較高時,需要頻繁的去複製,效率變低;
複製算法

分代收集

分代收集算法是針對不同的分代,採取不同的收集方法,在“新生代” 中以爲對象存活率低,所以採用“複製算法” 進行收集,在“年老代” 中,因爲對象存活率高,所以採用“標記-清除”,“標記-整理”算法。
新生代分爲Eden 和兩個大小相同的Survivor 區,所有新創建的對象都存放下Eden區,當Eden 區滿了之後,會觸發Minor Gc,然後會將仍然存活的對象移到其中一個Survivor 區,並始終保證一個Survivor 爲空。如果在Survivor 區存放不了,那麼Gc 收集器會將這些對象直接存放在Old 區,如果在Old 區也滿了之後,就會觸發full gc 了。full gc 會進行觸發清理整個堆內存;

垃圾收集器

新生代的回收器主要有:Serial、ParNew、Parallel Scavenge(這三種算法都採用了標記-複製算法)
老年代回收器主要有:CMS、Serial Old、Parallel Old
分代收集:G1
垃圾回收器

CMS,G1

CMS收集的步驟爲:
初始標記–>併發標記–>重新標記–>併發清除
初始標記:需要停止運行程序的,初始標記僅僅只是標記一下GC Roots能關聯到的對象。
併發標記:進行GC Roots Tracing 的過程,就是追蹤與GC Roots關聯的對象是否還在使用中。
重新標記:在上一步併發標記時,程序是在運行的,會產生新的垃圾對象,對象地址也有所變動,所以需要重新標記一下,但是這樣也會導致程序暫停。
併發清除:這個階段就是清除垃圾對象,因爲是並行運行的,所以這時也會產生新的垃圾,但是這些垃圾是清理不掉的,因爲沒有被標記,所以只有等下次GC回收。
CMS清除算法有2個缺點:

  1. 因爲使用的是標記-清除算法,所以會產生內存碎片,無法回收併發清除階段產生的垃圾, 對CPU資源很敏感。
  2. 無法處理浮動垃圾,可能出現"Concurrent Mode Failure"失敗(浮動垃圾:新線程產生的垃圾)

G1回收算法(慢慢要替代CMS回收算法)

爲解決CMS算法產生空間碎片和其它一系列的問題缺陷.G1中提供了三種模式垃圾回收模式,young gc、mixed gc 和 full gc,在不同的條件下被觸發。
發生在年輕代的GC算法,一般對象(除了巨型對象)都是在eden region中分配內存,當所有eden region被耗盡無法申請內存時,就會觸發一次young gc.
當越來越多的對象晉升到老年代old region時,爲了避免堆內存被耗盡,虛擬機會觸發一個混合的垃圾收集器
缺點:G1垃圾回收器對內存的要求較高,一般建議4G以上使用

JDK 性能監控

  1. 在生產環境上多多少少會碰到過jvm 內存溢出情況,這個時候需要分析溢出原因,就需要一些工具和命令進行監控。
    在jdk 中提供的非常強大的可視化工具jvisualvm.exe
    jvisulvm
  2. 另外可以使用命令可以進行對jdk 的使用情況進行監控,以下是常用命令;

查看虛擬機進程: jps 命令

jps 命令可以列出所有的java 進程。
除此之外,還有一些參數可以自定義輸出:
-q 指定jps只輸出進程ID
-m 輸出傳遞給Java進程的參數
-l 輸出主函數的完整路徑
-v 顯示傳遞給Java虛擬機的參數

虛擬機統計信息:jstat 命令

jstat 用於觀察 Java 堆信息的詳細情況
命令: jstat -option 進程id

參數 含義
-class 監視類裝載、卸載數量、總空間以及類裝載所耗費的時間
-gc 監視Java堆狀況,包括Eden區、兩個Survivor區、老年代、永久代等的容量、已用空間、GC時間合計等信息
-gccapacity 監視內容與-gc基本相同,但輸出主要關注Java堆各個區域使用到的最大、最小空間
-gcutil 監視內容與-gc基本相同,但輸出主要關注已使用空間佔總空間的百分比
-gccause 與-gcutil功能一樣,但是會額外輸出導致上一次GC產生的原因
-gcnew 監視新生代GC狀況
-gcnewcapacity 監視內容與-gcnew基本相同,輸出主要關注使用到的最大、最小空間

例:
jstat -gcutil 2365

S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 0.00 12.05 0.00 14.71 0 0.000 0 0.00 0.00

其中每個選項的意義如下:

S0、S1 表示Survivor0、Survivor1,還未使用。
E 表示Eden區使用了12.05%的空間。
O 表示老年代還未使用。
P 表示永久代使用了14.17%的空間
YUC、YGCT 表示從程序運行以來一共發生了0次Minor GC(YGC,Young GC),總共耗時0秒。
FGC、FGCT 表示從程序運行以來一共發生了0次Full GC(FGC,Full GC),總共耗時0秒。

查看虛擬機參數:jinfo 命令

命令: jinfo -option pid

導出堆到文件:jmap 命令

jmap 是一個多功能命令,可以生成 Java 程序的 Dump 文件,也可以查看堆內對象實例的統計信息、查看 ClassLoader 的信息以及 finalizer 隊列。
命令: jmap -option pid

堆分析工具:jhat 命令

jhat 命令用於分析 Java 應用的對快照內存。Sun JDK 提供了 jhat 命令與 jmap 搭配使用,來分析 jmap 生成的堆轉儲快照。jhat 內置了一個微型的 HTTP/HTML 服務器,生成 dump 文件的分析結果後,可以在瀏覽器中查看( http://localhost:7000)

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