jvm的基礎知識點梳理

什麼是雙親委派機制:

當某個類加載器需要加載某個.class文件時,它首先把這個任務委託給他的上級類加載器

加載順序爲:

1、Bootstrap Class Loader (加載rt.jar)

2、Bootstrap Class Loader (加載rt.jar)

3、App Class Loader(加載ClassPath)

4、Customer ClassLoader(通過java.lang.ClassLoader的子類自定義加載class,屬於應用程序根據自身需要自定義的ClassLoader,如tomcat,jboss都會根據j2ee規範自行實現ClassLoader)

進行加載的時候會先委派根ClassLoader,然後Extension ClassLoader進行加載,如果沒找到才使用系統加載器加載,這樣做可以防止jdk的代碼被使用者篡改

 

由雙親委派機制可以得到一個結論:Object以及java.lang包下的類能直接用的原因:

jvm啓動時根ClassLoader已經加載rt.jar下的類,java.lang包在rt.jar中,所以可以直接使用

 

jvm整體架構圖:

方法區:

方法區被所有線程共享,所有字段和方法字節碼,以及一些特殊方法(如構造函數、接口代碼)在此定義,簡單說,所有定義的方法的信息都保存在該區域,此區域屬於共享區域

靜態變量+常量+類信息(構造方法/接口定義)+運行時常量池存在此方法區中

棧:

棧也叫棧內存,主管java程序的運行,是在創建線程的時候創建的,它的生命週期是跟隨線程的生命週期,線程結束時棧內存也就釋放,對於棧來說不存垃圾回收的問題,只要線程一結束就釋放,是線程私有的,8中基本類型的變量、對象的引用變量、實例方法都是在函數的棧內存中分配

棧中每個元素成爲棧幀,棧幀代表方法的執行

棧幀代表的是方法的執行,裏面存放局部變量表、操作數棧、動態鏈接、方法返回地址

heap(堆):

一個jvm實例只存在一個堆內存,堆內存的大小是可以調節的,類加載器讀取了類文件後,需要把類,方法,常變量放到堆內存中,保存所有引用類型的真實信息,堆存儲對象和數組,堆內存分爲三部分:

  • 新生去
  • 老年區
  • 永久區

本地方法棧:

本地方法(C/C++)的棧

如果在java虛擬機棧中用到了native方法(本地方法),會在本地方法棧進行壓棧,同時在java虛擬機棧中調用本地方法棧的方法會動態鏈接到本地方法棧中的相應本地方法,此時這個虛擬機棧和本地方法棧都在線程中,即該線程擁有本地方法棧和java虛擬機棧兩個棧

程序計數器:

由於線程之間會競爭cpu資源,程序計數器是用來記錄程序正在執行的地址

動態鏈接:

把類的符號引用轉變爲直引用 (如在方法中的Student s = new Student(),只有在運行時纔會調用)

方法區:

jdk1.7之前成爲PermSpace(永久代),jdk1.8叫做MetaSpace(元空間)

方法區在邏輯上是堆的一部分,但是通常上成爲非堆

 

垃圾收集機制:

  • 整體分爲老年代和新生代,新生代又分爲eden區,兩個S區
  • 一個對象如果經過15次GC還沒有被回收,就會去老年代(15次可以更改,爲默認值)
  • 一個對象如果創建,優先分配到新生代
  • 如果一個對象的大小超過了新生代的一半,那麼會會直接分配到老年代
  • 如果新生代不夠用會發生一次GC
  • 新生代空間不連續,會行程空間碎片,導致對象放不下,也會觸發一次GC,因此新生代會劃分爲Eden,S0,S1,對象現在Eden區進行分配,如果Eden區不夠用,會將存活的對象分配到一個S區,兩個S區,一定是一個爲空,另一個正在被使用,複製完後,直接清空Eden區所有存活對象,Eden區的清空實質上是整個新生代的GC,兩個S區也會觸發GC,其中一個S區會將存活對象複製到另一個S區,從而保證總是有一個S區爲空
  • 新生代(young區)的Gc稱爲 Young GC/Minor GC,Old區的GC稱爲Old GC/Major GC
  • Eden區存活的對象,如果在S區放不下,並且年齡沒有到去老年區的大小,會通過擔保機制,將對象直接存到Old區
  • Eden區,S0區,S1區的空間大小比默認爲8:1:1,也就是說新生代默認有10%的空間浪費
  • 老年代的GC:由於老年代空間較大,掃描會耗費更大的線程資源和CPU資源
  • Major GC通常會伴隨着Minor GC

 

不只是堆,運行時數據區每個區都會出現內存不足的問題

如方法區內存不足夠也會拋出oom:java.lang.OutOdMemoryError:Metaspace

設置方法區空間大小:

-XX:MetaspaceSize=30M -XX:MaxMetaspaceSize=30M

如果虛擬機棧深度不夠,也會發生內存溢出:java.lang.StackOverflowError

調整虛擬機棧大小:-Xss128k

棧的深度太小:意味着方法鏈比較短,很快會溢出

棧的深度太大:棧的深度越大,會更多的消耗cpu資源,影響線程創建的數量,另外線程也會佔用內存空間,造成oom溢出

java垃圾回收機制: 可達性分析

GC Root:根據gc root可以直接或間接到達的實例就不是垃圾,反之就是垃圾

GC Root可以是局部變量表,static成員、常量方法區、本地方法棧中的變量、類加載器、Thread

 

回收算法:

標記-清除:缺點:耗時,會產生空間碎片

複製算法(標記-複製-清除):young區算法,解決空間碎片問題,但是會造成空間浪費,存活對象比較少(朝生夕死)適合該算法

標記-整理:會把存活的對象整理成連續的一段,解決空間碎片,老年代適用於標記清除或者標記整理算法

jvm 不同的垃圾收集器實現了不同的垃圾回收算法:


目前還沒有任何收集器能在不停止用戶程序的情況下實現垃圾回收,最好的java11中的zgc停頓時間在10ms左右

jdk1.3之前爲Serial,適用於新生代,是對複製算法的實現,單線程收集

SerialOld:適用於老年代,對於標記整理算法的實現

ParNew:和Serial類似,但是是多線程

Parallel Scanvenge:複製算法的實現,吞吐量更好

Parallel Old:適用於老年代的版本,實現標記整理算法

CMS:Concurrent Mark Sweep,併發類的收集器,實現了標記-清除算法

優缺點:併發收集,低停頓(更加關注停頓時間的收集器),但是會產生空間碎片,內存不連續,只能適用於老年代

G1:篩選回收:可以根據開發者的設置,作出一個停頓時間的期望值,這樣做的話會有一些空間沒有辦法完全回收,這裏的機制如果該回收機制的名稱(Garbage First),優先回收垃圾對象多的區域,這樣可以把更多的空間釋放出來

 

針對G1GC調優指南:

1、避免設置young區和young區old區比例大小

2、停頓時間不要設置的太嚴格,否則會增加gc次數

3、調整G1併發收集百分比:InitiatingHeapOccupancyPercent,默認值45

 

一些簡單問答:

名詞解釋(內存泄漏和內存溢出)

內存泄漏:不再使用的對象無法得到及時回收,持續佔用內存空間,造成空間的浪費

內存溢出:內訓泄露容易導致內存溢出,內存溢出不一定是內存泄漏導致的,也有可能是大對象等等

 

被GC Root判定爲不可達,是都一定會被回收?

不一定,有可能因爲finalize()重新復活變爲有用的對象

finalize():當一個對象被虛擬機宣告死亡時會先調用它finalize()方法,讓此對象處理它生前的最後事情,這個對象可以趁這個時機掙脫死亡的命運

如何自我救贖:

1.對象覆寫了finalize()方法(這樣在被判死後纔會調用此方法,纔有機會做最後的救贖);

2.在finalize()方法中重新引用到"GC Roots"鏈上(如把當前對象的引用this賦值給某對象的類變量/成員變量,重新建立可達的引用)

注意:

finalize()只會在對象內存回收前被調用一次

finalize()的調用具有不確定行,只保證方法會調用,但不保證方法裏的任務會被執行完(比如一個對象手腳不夠利索,磨磨嘰嘰,還在自救的過程中,被殺死回收了)

 

方法區中的類是否會被回收?

會:

  • 該類中的所有實例都已經被回收
  • 該類的ClassLoader已經被回收
  • java.lang.Class對象沒有任何地方使用

 

cms和g1的區別:

cms只能適用於老年代,g1將堆內存分割爲一個個region,停頓時間可控

cms使用標記清除算法,會產生空間碎片,g1較少產生碎片的可能

 

 

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