文章目錄
- 1.JVM 的各部分組成
- 2.運行時數據區
- 2.1.什么是程序計數器?
- 2.2.你能給我詳細的介紹Java堆嗎?
- 2.3.能不能解釋一下方法區?
- 2.3.1常量池
- 2.3.2.運行時常量池
- 2.4.什么是虛擬機棧?
- 2.4.1.垃圾回收是否涉及棧內存?
- 2.4.2.棧內存分配越大越好嗎?
- 2.4.3.方法內的局部變量是否線程安全?
- 2.4.4.什么情況下會導致棧內存溢出?
- 3.你聽過直接內存嗎?
- 4.什么是類加載器,類加載器有哪些?
- 4.1.什么是類加載器
- 4.2.類加載器有哪些?
- 4.3.什么是雙親委派模型?
- 4.4JVM為什么采用雙親委派機制?
- 5.說一下類裝載的執行過程?
- 6.對象什么時候可以被垃圾器回收 ?
- 6.1.引用計數法
- 6.2.可達性分析算法
- 7.JVM 垃圾回收算法有哪些?
- 7.1.標記清除算法
- 7.2.標記整理算法
- 7.3.復制算法
- 8.說一下JVM中的分代回收
- 8.1.分代收集算法
- 8.2.MinorGC、 Mixed GC 、 FullGC的區別是什么
- 9.說一下JVM有哪些垃圾回收器?
- 9.1.串行垃圾收集器
- 9.2.并行垃圾收集器
- 9.3.CMS(并發)垃圾收集器
- 9.4.詳細聊一下G1垃圾回收器
- 10.強引用、軟引用、弱引用、虛引用的區別?
- 11.JVM 調優的參數可以在哪里設置參數值
- 11.1.war包部署在tomcat中設置
- 11.2.jar包部署在啟動參數設置
- 12.什么時候用到 JVM 調優,調優哪些參數?
- 13.JVM 調優的參數都有哪些?
- 13.1.設置堆空間大小(放對象和數組)
- 13.2.虛擬機棧的設置
- 13.3.年輕代中Eden區和兩個Survivor區的大小比例
- 13.4.年輕代晉升老年代閾值
- 13.5.設置垃圾回收收集器
- 14.說一下 JVM 調優的工具?
- 15.Java內存泄露的排查思路
- 16.CPU飆高排查方案與思路
- 17.cpu 比較低,load 比較高怎么解決
1.JVM 的各部分組成
知道JVM 的好處:知道java 運行機制,排查問題的能力增加,比如內存泄漏、CPU飆高
JVM 是什么:Java Virtual Machine縮寫,Java程序的運行環境(java二進制字節碼的運行環境)
好處:
- 一次編寫,到處運行
- 自動內存管理,垃圾回收機制
從圖中可以看出 JVM 的主要組成部分
- ClassLoader(類加載器):負責將 .class 文件加載到 JVM 中,并轉換為運行時數據結構(Class 對象)
- Runtime Data Area(運行時數據區):管理程序運行時的內存分配和數據存儲
- Execution Engine(執行引擎):把字節碼翻譯為底層系統指令
- 解釋器:解釋字節碼信息
- 即時編譯器:針對代碼進行優化
- GC:主要是指運行數據區中的堆空間
- Native Method Library(本地庫接口):由C或C++實現,因為Java代碼并不能實現某些功能,需要借助系統提供的接口(本地方法接口)
重點講解下面部分:
JVM組成:重點介紹運行數據區
類加載器:主要指類加載子系統(比如:一個類的生命周期是什么、常見的類加載器有哪些)
垃圾回收:主要指的是堆中的對象垃圾回收,包含了垃圾回收算法、垃圾回收器、怎么去設置
JVM實踐:JVM 調優的一些內容,比如參數怎么設置,該設置多少?出錯了怎么辦?比如內存泄漏問題、CPU飆高問題
2.運行時數據區
管理程序運行時的內存分配和數據存儲
分為以下部分:
-
線程共享區域
- 方法區(Method Area)
- 存儲類的元數據(類名、類的結構、方法信息、字段信息)、靜態變量、常量池等。
- JDK 8 后由元空間(Metaspace)實現,直接使用本地內存。
- 堆(Heap)
- 存儲所有對象實例和數組,是垃圾回收(GC)的主要區域。
- 分為新生代(Eden、Survivor區)和老年代。
- 方法區(Method Area)
-
線程私有區域
- 虛擬機棧(JVM Stack)
- 每個方法調用對應一個棧幀(存儲局部變量表、操作數棧、動態鏈接、方法出口)。
- 棧深度超出限制時拋出 StackOverflowError。
- 本地方法棧(Native Method Stack)
- 為 Native 方法(如 C/C++ 實現的方法)服務。
- 程序計數器(Program Counter Register)
- 記錄當前線程執行的字節碼指令地址(分支、循環、異常處理依賴此區域)。
2.1.什么是程序計數器?
屬于運行數據區的一部分,是線程私有的(沒有線程安全問題),每個線程都有一份,內部保存的字節碼的行號。用于記錄正在執行的字節碼指令的地址。
javap -v xx.class 打印每個方法的詳細信息,包括字節碼指令(操作碼)、局部變量表、操作數棧等。
什么是程序計數器?
屬于運行數據區的一部分,是線程私有的(沒有線程安全問題),每個線程都有一份,內部保存的字節碼的行號。用于記錄正在執行的字節碼指令的地址。
2.2.你能給我詳細的介紹Java堆嗎?
它是線程共享的區域(有線程安全問題),主要用來保存實例,數組等,當堆中沒有內存空間可分配給實例,也無法再擴展時,則拋出OutOfMemoryError異常。
下圖是運行數據區:
S0、S1也被成為幸存者區 ,一個對象來了之后先會到Eden(伊甸)區,假如這個對象在垃圾回收之后還存活,就會被復制移動到S0或者S1,假如對象移動一定次數還存活著,就會被放到老年代中,老年代是指生命周期比較長的
- 年輕代被劃分為三部分,Eden區和兩個大小嚴格相同的Survivor區,根據JVM的策略,在經過幾次垃圾收集后,任然存活于Survivor的對象將被移動到老年代區間。
- 老年代主要保存生命周期長的對象,一般是一些老的對象
- 元空間(方法區)保存的類名、類的結構、方法信息、字段信息、靜態變量、常量池等。
java1.7和java1.8 堆的區別是什么?
在java1.8 之前,堆中有個永久代概念,和元空間的作用是一樣的。因為元空間或方法區,主要存儲的是一些類或者常量,隨著項目類加載越來越多,java 1.7的永久代不可控,如果設置小了,就會存在內存溢出,設置大了會浪費內存,java1.8做了優化,都放到本地內存,這樣堆節省空間,防止內存溢出,避免OOM。
你能給我詳細的介紹Java堆嗎?
- 線程共享的區域:主要用來保存對象實例,數組等,內存不夠則拋出OutOfMemoryError異常。
- 組成:年輕代+老年代
- 年輕代被劃分為三部分,Eden區和兩個大小嚴格相同的Survivor區
- 老年代主要保存生命周期長的對象,一般是一些老的對象
- Jdk1.7和1.8的區別
- 1.7中有有一個永久代,存儲的是類名、類的結構、方法信息、字段信息、靜態變量、常量池等
- 1.8移除了永久代,把數據存儲到了本地內存的元空間中,防止內存溢出
2.3.能不能解釋一下方法區?
屬于運行數據區的一部分
- 方法區(Method Area)是各個線程共享的內存區域
- 存儲類的元數據(類名、類的結構、方法信息、字段信息)、靜態變量、常量池等
- 虛擬機啟動的時候創建,關閉虛擬機時釋放
- 如果方法區域中的內存無法滿足分配請求,則會拋出OutOfMemoryError: Metaspace
方法區邏輯上屬于堆的一部分,但是不同廠商,存儲的位置不一樣,我們現在用的時oracle 提供的hotpot 虛擬機,在JDK8 之前,方法區是存在堆中一個叫永久代這個區域中,在JDK8 之后,把永久代給移除了,換了一個實現,這個實現叫做元空間,元空間不在堆內存中,用的是本地內存,也就是操作系統內存,為什么會挪動?避免OOM
- Class:類的信息,包括了類名、類的結構、方法、字段等
- Classloader:來加載這個類
2.3.1常量池
可以看作是一張表,虛擬機指令根據這張常量表找到要執行的類名、方法名、參數類型、字面量等信息
javap -v Application.class ,查看字節碼結構(類的基本信息、常量池、方法定義)
下圖,左側是main方法的指令信息,右側constant pool 是常量池,main方法按照指令執行的時候,需要到常量池中查表翻譯找到具體的類和方法地址去執行
2.3.2.運行時常量池
常量池是 *.class 文件中的,當該類被加載,它的常量池信息就會放入運行時常量池,并把里面的符號地址變為真實地址 (真正的內存地址)
能不能解釋一下方法區?
- 方法區(Method Area)是各個線程共享的內存區域
- 主要存儲類名、類的結構、方法信息、字段信息、靜態變量、常量池
- 虛擬機啟動的時候創建,關閉虛擬機時釋放
- 如果方法區域中的內存無法滿足分配請求,則會拋出OutOfMemoryError: Metaspace
介紹一下運行時常量池
- 常量池:可以看作是一張表,虛擬機指令根據這張常量表找到要執行的類名、方法名、參數類型、字面量等信息
- 當類被加載,它的常量池信息就會放入運行時常量池,并把里面的符號地址變為真實地址
2.4.什么是虛擬機棧?
Java Virtual machine Stacks (java 虛擬機棧)
- 每個線程運行時所需要的內存,稱為虛擬機棧(多個線程也會創建多個虛擬機棧,是線程私有、線程安全的),先進后出(什么時候創建虛擬機棧?)
- 每個棧由多個棧幀(frame)組成(方法會調用其它方法,產生多個棧幀),對應著每次方法調用時所占用的內存(每個棧幀包含方法所需要的數據,比如參數、局部變量、返回地址)
- 每個線程只能有一個活動棧幀,對應著當前正在執行的那個方法
2.4.1.垃圾回收是否涉及棧內存?
垃圾回收主要指就是堆內存,當棧幀彈棧以后,內存就會釋放
2.4.2.棧內存分配越大越好嗎?
未必,默認的棧內存通常為1024k,棧幀過大會導致線程數變少(每個線程都會創建虛擬機棧),例如,機器總內存為512m,目前能活動的線程數則為512個,如果把棧內存改為2048k,那么能活動的棧幀(或者線程)就會減半
2.4.3.方法內的局部變量是否線程安全?
- 如果方法內局部變量沒有逃離方法的作用范圍,它是線程安全的
- 如果是局部變量引用了對象(形參StringBuilder),并逃離方法的作用范圍,需要考慮線程安全
m1方法線程安全,m2 和m3 方法不安全
m1:每個線程來了之后,都會創建棧幀,每個棧幀都會有局部變量sb,每個線程都是獨有的,并沒有線程安全問題
m2:形參sb也是局部變量,在參數傳遞過程中,有可能會被其它線程操作。比如主線程中創建了一個StringBuilder對象sb,又在新線程里面把sb作為參數傳給了m2方法,主線程和新線程都在修改sb,sb是主線程和新線程共享的,線程不安全。
m3:會把局部變量返回,這個局部變量有可能會被其它線程共用,可以在mian 方法中調用m3 方法,得到局部變量,然后在mian 方法中開啟多線程同時去操作這個變量,也成了多個線程共用的變量
2.4.4.什么情況下會導致棧內存溢出?
- 棧幀過多導致棧內存溢出(java.lang.StackOverflowError),典型問題:遞歸調用
- 棧幀過大導致棧內存溢出(1M內存,不太容易出現)
public static void m4()
{m4();
}
1.什么是虛擬機棧
- 每個線程運行時所需要的內存,稱為虛擬機棧,它是先進后出的特點
- 每個棧由多個棧幀(frame)組成,對應著每次方法調用時所占用的內存
- 每個線程只能有一個活動棧幀,對應著當前正在執行的那個方法
2.垃圾回收是否涉及棧內仔?
- 垃圾回收主要指就是堆內存,當棧幀彈棧以后,內存就會釋放
3.棧內存分配越大越好嗎?
- 未必,默認的棧內存通常為1024K,棧幀過大會導致線程數變少
4.方法內的局部變量是否線程安全?
- 如果方法內局部變里沒有逃離方法的作用范圍,它是線程安全的
- 如果是局部變量引用了對象,并逃離方法的作用范圍,需要考慮線程安全
5.什么情況下會導致棧內存溢出?
- 棧幀過多導致棧內存溢出,典型問題:遞歸調用
- 棧幀過大導致棧內存溢出
6.堆和棧的區別是什么?
- 棧內存一般會用來存儲方法調用的棧幀、局部變量、操作數棧等,但堆內存是用來存儲Java對象和數組的。堆會GC垃圾回收,而棧不會。
- 棧內存是線程私有的,而堆內存是線程共有的。
- 兩者異常錯誤不同,但如果棧內存或者堆內存不足都會拋出異常。
- 棧空間不足:java.lang.StackOverFlowError。
- 堆空間不足:java.lang.OutOfMemoryError。
3.你聽過直接內存嗎?
并不屬于JVM中的內存結構,不由JVM進行管理。是虛擬機的系統內存(操作系統內存),常見于 NIO 操作時,用于數據緩沖區,它分配回收成本較高,但讀寫性能高
IO
java 本身不具有磁盤讀寫能力,要調用磁盤讀寫,必須調用操作系統提供的函數,會調用本地的方法,native 修飾的方法都是操作系統提供的方法,使用native 修飾的方法去操作磁盤文件。 這里涉及到CPU的運行狀態,會從java 的 用戶態切換到內核態,這是CPU狀態的改變,其次內存也有一系列操作,當切換到內核態之后,會由CPU 的函數讀取磁盤文件,會在操作系統中劃出一塊緩沖區,這個緩沖區我們也稱為系統緩存區,磁盤內容會讀到系統緩存區中,它不能把幾百兆的數據一次性全部讀到系統緩存區中,會利用緩沖區分批次進行讀取,讀到系統的緩存區,這個系統緩沖區,我們java 代碼是不能操作的,java 會在堆中分配一塊內存,對應代碼中 new byte[] ,會劃分java 的緩沖區,我們代碼要想訪問剛才讀到的流中數據,必須要從系統的緩存區讀到java 的緩沖區,讀到java 的緩沖區會進入java 的下一個狀態(用戶態),會調用輸出流的寫入操作,這樣反復進行讀寫,這個文件會復制到目標位置。也發現問題所在,有兩個緩沖區,有兩塊內存,一個是系統提供的系統緩存區,另一個是java 的緩沖區,讀取數據的時候,數據要存兩份,第一次讀到系統緩沖區,第二次讀到java 緩沖區,因為java 代碼訪問不了系統緩沖區,必須讀到java 緩沖區之后,java 代碼才能進行操作,這就造成不必要的數據復制,因此效率不是很高
NIO
操作系統中劃出了一塊緩沖區,這個區域叫直接內存,系統也可以訪問,java 代碼可以直接訪問。磁盤文件讀寫的時候,java 代碼操作非常方便,比IO少了一次緩沖區的復制操作,所以說速度成倍的提升
你聽過直接內存嗎?
- 并不屬于JVM中的內存結構,不由JVM進行管理。是虛擬機的系統內存
- 常見于 NIO 操作時,用于數據緩沖區,分配回收成本較高,但讀寫性能高,不受 JVM 內存回收管理
4.什么是類加載器,類加載器有哪些?
- 類加載器:負責將 .class 文件加載到 JVM 中,并轉換為運行時數據結構(Class 對象)
- 運行時數據區:管理程序運行時的內存分配和數據存儲
- 執行引擎:執行字節碼文件或本地方法,把字節碼翻譯為底層系統指令
- 垃圾回收器:用于對JVM中的垃圾內容進行回收
什么是類加載器,類加載器有哪些 ?
類加載器:JVM只會運行二進制文件,負責將 .class 文件加載到 JVM 中,并轉換為運行時數據結構(Class 對象),從而讓Java程序能夠啟動起來。