一、JVM的組成
1、JVM由哪些部分組成,運行流程是什么?
回答:在JVM中共有四大部分,分別是Class Loader(類加載器)、Runtime Data Area(運行時數據區,內存分區)、Execution Engine(執行引擎)、Native Method Library(本地庫接口)。
它們的運行流程是:
第一,類加載器(ClassLoader)把Java代碼轉換為字節碼文件
第二,運行時數據區(Runtime Data Area)把字節碼加載到內存中,而字節
碼文件只是JVM的一套指令集規范,并不能直接交給底層系統去執行,而是
有執行引擎運行
第三,執行引擎(Execution Engine)將字節碼翻譯為底層系統指令,再交
由CPU執行去執行,此時需要調用其他語言的本地庫接口(Native Method
Library)來實現整個程序的功能。
2、詳細說一下 JVM 運行時數據區嗎?
運行時數據區包含了堆、方法區、棧、本地方法棧、程序計數器這幾部分,每個功能作用不一樣。
堆解決的是對象實例存儲的問題,垃圾回收器管理的主要區域。
方法區可以認為是堆的一部分,用于存儲已被虛擬機加載的信息,常量、靜態變量、即時編譯器編譯后的代碼。
棧解決的是程序運行的問題,棧里面存的是棧幀,棧幀里面存的是局部變量表、操作數棧、動態鏈接、方法出口等信息。
本地方法棧與棧功能相同,本地方法棧執行的是本地方法,一個Java調用非Java代碼的接口。
程序計數器(PC寄存器)程序計數器中存放的是當前線程所執行的字節碼的行數。JVM工作時就是通過改變這個計數器的值來選取下一個需要執行的字節碼指令。
3、你再詳細介紹一下程序計數器的作用?
java虛擬機對于多線程是通過線程輪流切換并且分配線程執行時間。在任何的一個時間點上,一個處理器只會處理執行一個線程,如果當前被執行的這個線程它所分配的執行時間用完了【掛起】。處理器會切換到另外的一個線程上來進行執行。并且這個線程的執行時間用完了,接著處理器就會又來執行被掛起的這個線程。這時候程序計數器就起到了關鍵作用,程序計數器在來回切換的線程中記錄他上一次執行的行號,然后接著繼續向下執行。
4、你能給我詳細的介紹Java堆嗎?
Java中的堆術語線程共享的區域。主要用來保存對象實例,數組等,當堆中沒有內存空間可分配給實例,也無法再擴展時,則拋出OutOfMemoryError異常。
在JAVA8中堆內會存在年輕代、老年代
1)Young區被劃分為三部分,Eden區和兩個大小嚴格相同的Survivor區,其中,Survivor區間中,某一時刻只有其中一個是被使用的,另外一個留做垃圾收集時復制對象用。在Eden區變滿的時候, GC就會將存活的對象移到空閑的Survivor區間中,根據JVM的策略,在經過幾次垃圾收集后,任然存活于Survivor的對象將被移動到Tenured區間。
2)Tenured區主要保存生命周期長的對象,一般是一些老的對象,當一些
對象在Young復制轉移一定的次數以后,對象就會被轉移到Tenured區。
5、能不能解釋一下方法區?
與虛擬機棧類似。本地方法棧是為虛擬機執行本地方法時提供服務的。不需要進行GC。本地方法一般是由其他語言編寫。
6、聽過直接內存嗎?
它又叫做堆外內存,線程共享的區域,在 Java 8 之前有個永久代的概念,實際上指的是 HotSpot 虛擬機上的永久代,它用永久代實現了 JVM 規范定義的方法區功能,主要存儲類的信息,常量,靜態變量,即時編譯器編譯后代碼等,這部分由于是在堆中實現的,受 GC 的管理,不過由于永久代有 -XX:MaxPermSize 的上限,所以如果大量動態生成類(將類信息放入永久代),很容易造成 OOM,有人說可以把永久代設置得足夠大,但很難確定一個合適的大小,受類數量,常量數量的多少影響很大。所以在 Java 8 中就把方法區的實現移到了本地內存中的元空間中,這樣方法區就不受 JVM 的控制了,也就不會進行 GC,也因此提升了性能。
7、什么是虛擬機棧
虛擬機棧是描述的是方法執行時的內存模型,是線程私有的,生命周期與線程相同,每個方法被執行的同時會創建棧楨。保存執行方法時的局部變量、動態連接信息、方法返回地址信息等等。方法開始執行的時候會進棧,方法執行完會出棧【相當于清空了數據】,所以這塊區域不需要進行 GC。
8、能說一下堆棧的區別是什么嗎?
第一,棧內存一般會用來存儲局部變量和方法調用,但堆內存是用來存儲
Java對象和數組的的。堆會GC垃圾回收,而棧不會。
第二、棧內存是線程私有的,而堆內存是線程共有的。
第三、兩者異常錯誤不同,但如果棧內存或者堆內存不足都會拋出異常。
棧空間不足:java.lang.StackOverFlowError。
堆空間不足:java.lang.OutOfMemoryError。
二、類加載器
1、什么是類加載器,類加載器有哪些?
JVM只會運行二進制文件,而類加載器(ClassLoader)的主要作用就是將字節碼文件加載到JVM中,從而讓Java程序能夠啟動起來。
常見的類加載器有4個:
第一個是啟動類加載器(BootStrap ClassLoader):其是由C++編寫實現。用于加載JAVA_HOME/jre/lib目錄下的類庫。
第二個是擴展類加載器(ExtClassLoader):該類是ClassLoader的子類,主要加載JAVA_HOME/jre/lib/ext目錄中的類庫。
第三個是應用類加載器(AppClassLoader):該類是ClassLoader的子類,主要用于加載classPath下的類,也就是加載開發者自己編寫的Java類。
第四個是自定義類加載器:開發者自定義類繼承ClassLoader,實現自定義類加載規則。
2、說一下類裝載的執行過程?
類從加載到虛擬機中開始,直到卸載為止,它的整個生命周期包括了:加載、驗證、準備、解析、初始化、使用和卸載這7個階段。其中,驗證、準備和解析這三個部分統稱為連接(linking)
1.加載:查找和導入class文件
2.驗證:保證加載類的準確性
3.準備:為類變量分配內存并設置類變量初始值
4.解析:把類中的符號引用轉換為直接引用
5.初始化:對類的靜態變量,靜態代碼塊執行初始化操作
6.使用:JVM 開始從入口方法開始執行用戶的程序代碼
7.卸載:當用戶程序代碼執行完畢后,JVM 便開始銷毀創建的 Class 對象,最后負責運行的 JVM 也退出內存
3、什么是雙親委派模型?
如果一個類加載器收到了類加載的請求,它首先不會自己嘗試加載這個類,而是把這請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳說到頂層的啟動類加載器中,只有當父類加載器返回自己無法完成這個加載請求(它的搜索返回中沒有找到所需的類)時,子類加載器才會嘗試自己去加載。
4、JVM為什么采用雙親委派機制
主要有兩個原因。
第一、通過雙親委派機制可以避免某一個類被重復加載,當父類已經加載后則無需重復加載,保證唯一性。
第二、為了安全,保證類庫API不會被修改
三、 垃圾回收
1、簡述Java垃圾回收機制?(GC是什么?為什么要GC)
為了讓程序員更專注于代碼的實現,而不用過多的考慮內存釋放的問題,所以,在Java語言中,有了自動的垃圾回收機制,也就是我們熟悉的GC(Garbage Collection)。
有了垃圾回收機制后,程序員只需要關心內存的申請即可,內存的釋放由系統自動識別完成。在進行垃圾回收時,不同的對象引用類型,GC會采用不同的回收時機。
2、強引用、軟引用、弱引用、虛引用的區別?
強引用最為普通的引用方式,表示一個對象處于有用且必須的狀態,如果一個對象具有強引用,則GC并不會回收它。即便堆中內存不足了,寧可出現OOM,也不會對其進行回收
軟引用表示一個對象處于有用且非必須狀態,如果一個對象處于軟引用,在內存空間足夠的情況下,GC機制并不會回收它,而在內存空間不足時,則會在OOM異常出現之間對其進行回收。但值得注意的是,因為GC線程優先級較低,軟引用并不會立即被回收。
弱引用表示一個對象處于可能有用且非必須的狀態。在GC線程掃描內存區域時,一旦發現弱引用,就會回收到弱引用相關聯的對象。對于弱引用的回收,無關內存區域是否足夠,一旦發現則會被回收。同樣的,因為GC線程優先級較低,所以弱引用也并不是會被立刻回收。
虛引用表示一個對象處于無用的狀態。在任何時候都有可能被垃圾回收。虛引用的使用必須和引用隊列Reference Queue聯合使用。
3、對象什么時候可以被垃圾器回收
如果一個或多個對象沒有任何的引用指向它了,那么這個對象現在就是垃圾,如果定位了垃圾,則有可能會被垃圾回收器回收。
如果要定位什么是垃圾,有兩種方式來確定,第一個是引用計數法,第二個是可達性分析算法。通常都使用可達性分析算法來確定是不是垃圾。
4、JVM 垃圾回收算法有哪些?
我記得一共有四種,分別是標記清除算法、復制算法、標記整理算法、分代回收。
5、你能詳細聊一下分代回收嗎?
關于分代回收是這樣的
在java8時,堆被分為了兩份:新生代和老年代,它們默認空間占用比例是1:2
對于新生代,內部又被分為了三個區域。Eden區,S0區,S1區默認空間占用比例是8:1:1
具體的工作機制是有些情況:
1)當創建一個對象的時候,那么這個對象會被分配在新生代的Eden區。當Eden區要滿了時候,觸發YoungGC。
2)當進行YoungGC后,此時在Eden區存活的對象被移動到S0區,并且當前對象的年齡會加1,清空Eden區。
3)當再一次觸發YoungGC的時候,會把Eden區中存活下來的對象和S0中的對象,移動到S1區中,這些對象的年齡會加1,清空Eden區和S0區。
4)當再一次觸發YoungGC的時候,會把Eden區中存活下來的對象和S1中的對象,移動到S0區中,這些對象的年齡會加1,清空Eden區和S1區。
5)對象的年齡達到了某一個限定的值(默認15歲 ),那么這個對象就會進入到老年代中。當然也有特殊情況,如果進入Eden區的是一個大對象,在觸發YoungGC的時候,會直接存放到老年代,當老年代滿了之后,觸發FullGC。FullGC同時回收新生代和老年代,當前只會存在一個FullGC的線程進行執行,其他的線程全部會被掛起。 我們在程序中要盡量避免FullGC的出現。
6、講一下新生代、老年代、永久代的區別?
新生代主要用來存放新生的對象。
老年代主要存放應用中生命周期長的內存對象。
永久代指的是永久保存區域。主要存放Class和Meta(元數據)的信息。在Java8中,永久代已經被移除,取而代之的是一個稱之為“元數據區”(元空間)的區域。元空間和永久代類似,不過元空間與永久代之間最大的區別在于:元空間并不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存的限制。
7、說一下 JVM 有哪些垃圾回收器?
在jvm中,實現了多種垃圾收集器,包括:串行垃圾收集器、并行垃圾收集器(JDK8默認)、CMS(并發)垃圾收集器、G1垃圾收集器(JDK9默認)
8、Minor GC、Major GC、Full GC是什么
它們指的是不同代之間的垃圾回收
Minor GC 發生在新生代的垃圾回收,暫停時間短
Major GC 老年代區域的垃圾回收,老年代空間不足時,會先嘗試觸發MinorGC。Minor GC之后空間還不足,則會觸發Major GC,Major GC速度較慢,暫停時間長
Full GC 新生代 + 老年代完整垃圾回收,暫停時間長,應盡力避免
四、JVM實踐調優
1、JVM 調優的參數可以在哪里設置參數值?
springboot項目可以在項目啟動的時候,java -jar中加入參數就行了
2、用的 JVM 調優的參數都有哪些?
?-Xms2g:初始化推大小為 2g;
?-Xmx2g:堆最大內存為 2g;
?-XX:NewRatio=4:設置年輕的和老年代的內存比例為 1:4;
?-XX:SurvivorRatio=8:設置新生代 Eden 和 Survivor 比例為 8:2;
?–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;
?-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;
?-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;
?-XX:+PrintGC:開啟打印 gc 信息;
?-XX:+PrintGCDetails:打印 gc 詳細信息。
3、平時調試 JVM都用了哪些工具呢?
一般都是使用jdk自帶的一些工具,比如
jps 輸出JVM中運行的進程狀態信息
jstack查看java進程內線程的堆棧信息
jmap 用于生成堆轉存快照
jstat用于JVM統計監測工具
還有一些可視化工具,像jconsole和VisualVM等
4、假如項目中產生了java內存泄露,你說一下你的排查思路?
第一呢可以通過jmap指定打印他的內存快照 dump文件,不過有的情況打印不了,我們會設置vm參數讓程序自動生成dump文件
第二,可以通過工具去分析 dump文件,jdk自帶的VisualVM就可以分析
第三,通過查看堆信息的情況,可以大概定位內存溢出是哪行代碼出了問題
第四,找到對應的代碼,通過閱讀上下文的情況,進行修復即可
5、服務器CPU持續飆高,你的排查方案與思路?
第一可以使用使用top命令查看占用cpu的情況
第二通過top命令查看后,可以查看是哪一個進程占用cpu較高,記錄這個進程id
第三可以通過ps 查看當前進程中的線程信息,看看哪個線程的cpu占用較高
第四可以jstack命令打印進行的id,找到這個線程,就可以進一步定位問題代碼的行號