此次JVM知識點包含以下幾個部分
1.類加載機制
2.jvm運行時數據區
3.java對象內存佈局
4.jvm內存模型
5.垃圾回收機制
6.垃圾收集器
7.問題排查
一 類加載機制
主要說的部分是這一塊
那麼如何裝載呢,這就談到了咱們的雙親委派機制,簡單來說就是類,向上遞交,向下加載,源碼和圖如下
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//由於BootstrapClassLoader是C++寫的,在java中被視爲null
c = findBootstrapClassOrNull(name);
}
那麼裝載的過程是什麼呢,如圖
裝載幹了啥呢,大白話說就是一個你把一個java文件轉換成二進制給jvm處理,官方一點的語言就是:
①通過一個類的全限定名獲取這個類的二進制字節流。
②將這個字節流的靜態存儲結構轉換爲方法區的運行時數據結構。
③在堆中生成一個這個二進制字節流的Class對象作爲訪問入口。
那麼鏈接呢,驗證:字面意思,準備:賦默認值,解析:符號引用轉換爲直接引用
二 運行時數據區
話不多說,看法寶,上圖
方法區
存放:靜態變量,常量,即時編譯的class文件,類信息。
區別:1.8之前叫Perm Space 永久代,現在叫Meta Space 元空間
虛擬機棧
存放:棧幀;即方法的調用,-Xss可以設置棧大小,默認1M;使棧溢出的使遞歸。
構成:局部變量表;方法中定義的變量與方法的參數,
操作數棧;壓棧出棧存放數據的地方,
動態鏈接;這個的作用就是知道誰調用的,比方說java中的多態,最後會知道具體是哪個類,
方法返回地址;字面意思
堆
存放:對象及數組
接下來咱們看一下這個指針指向問題
- 棧指向堆; 棧幀(方法中有一個變量)即 Object obj = new Object();
- 方法區指向堆;靜態變量,private static Object obj = new Object();
- 堆指向方法區;由於方法區中存放的類信息,所以對於多態如何識別,這一點,就說明堆中有指向方法區的指針。
三 java對象內存佈局
四 jvm內存模型
內存模型可以認爲是運行時數據區的落地,那麼當一個對象來的時候,如何分配內存空間呢
- 首先放入Eden區,看夠不夠,不夠,minorGC,再試試Survivor是否足夠,如果不夠;
- 放入老年代看看夠不夠,不夠就來一次Full GC(minorGC+MajorGC);
- 如果還不夠就OOM了。
那麼對象進入老年代的條件是什麼呢
- 新生代中年齡大於15;
- 大對象(-XX:PretenureSizeThreshold 配置這個,大於這個數的就成爲大對象)
- 動態年齡:即survivor區中 同一年齡的超過了該區一半,那麼大於等於該年齡的對象直接進入老年代
- minorGC,新生代放不下的時候
那麼這裏邊放了這麼對象,該如何回收呢
五 垃圾回收機制
什麼是垃圾,如何確定垃圾呢
引用計數法
沒有任何指針指向的就是垃圾,但是無法解決循環引用的問題
可達性分析
由GCRoot(靜態成員,Thread線程,虛擬機棧的變量表,本地方法棧中的變量,類加載器,常量)作爲頭,向下順藤摸瓜,能摸得到的就是好瓜,摸不到的就回收扔了。
既然已經確定了垃圾,那麼如何回收呢
四種垃圾回收算法
- 標記-清除:將標記的清除掉,弊端就是內存不連續,容易產生內存碎片;
- 複製:內存分兩塊,將一端複製到另一端,解決了內存不連續,弊端就是內存有效區只由一半;
- 標記-整理:將垃圾回收後壓縮整理一下,解決了內存有效區只有一半的問題;
- 分代算法:個人認爲這個屬於一種思想,即對前三種的一種總結;老年代用標記清除,標記整理,新生代用複製
算法說完了,那麼算法的落地,如何實現的呢,這就要說到垃圾收集器了
六.垃圾收集器
可以看到從剛開始的Serial到現在的G1乃至ZGC的最多10ms停頓可以看到java一直在尋找最短的停頓時間,這個也是一直優化的方向。
並行收集:多個線程一起收集
併發收集:跟用戶線程一起跑
CMS和G1的區別有哪些呢?
CMS,四個步驟爲,初始標記-併發標記-重新標記-併發清理
G1,四個步驟,初始標記-併發標記-最終標記-篩選回收(對各個Region的回收價值進行排序根據用戶期望的GC停頓時間制定回收計劃)
G1可以設置停頓時間(-XX:MaxGCPauseMillis=20),就是因爲他的Region,可以理解爲一面牆分成了多個磚頭,一些磚頭的集合稱爲老年代,一些稱爲新生代。
這些都知道了,那麼出現錯誤該如何排查呢
七 問題排查
1.頻繁FullGC
導致頻繁FullGC的原因有
- System.gc()
-
jmap -dump:format=b,fifile=heap.hprof PID
- 老年代內存不夠
步驟
- 打印FullGC前後的日誌 -XX:+HeapDumpBeforFullGC -XX:+HeapDumpAfterFullGC -XX:+HeapDumpPath=a.prof
- 使用MAT工具進行分析,可以看堆中佔用情況,以及class的新建情況。
2.線上CPU負載過高排查
- 採用TOP命令,查出佔用cpu最高的java應用
- top -Hp PID查詢出佔用cpu最高的線程
- 找出該線程ID,轉換成16進制 printf "%x\n" tid
- jstack PID > d.txt
- 打開d.txt,查詢該16進制的tid就能找到了
3.吞吐量調優
- 使用命令打印出gc.log -XX:+PrintGCDetails -Xloggc:gc.log
- 使用gcviewer來分析日誌
- 根據具體情況調整堆棧大小,停頓時間等參數,再看gcviewer分析出來的數據如何。
4.死鎖排查
- 使用 java bin目錄下自帶的visualVM工具,可遠程鏈接可本地鏈接
- 連接之後,點擊線程一欄,便會出現紅字 發現死鎖
- 點擊旁邊的Dump按鈕即可進入dump文件中,往下翻即可看到提示的哪一行出現了死鎖,然後定位到代碼