JVM內(nèi)存模型
首先面試官會詢問你在進(jìn)行JVM調(diào)優(yōu)之前,是否了解JVM內(nèi)存模型的基礎(chǔ)知識。這是一個重要的入門問題。JVM內(nèi)存模型主要包括程序計數(shù)器、堆、本地方法棧、Java棧和方法區(qū)(1.7之后更改為元空間,并直接使用系統(tǒng)內(nèi)存)。
正常堆內(nèi)存又分為年輕代和老年代。在Java虛擬機(jī)中,年輕代用于存放新創(chuàng)建的對象,而老年代則用于存放生命周期較長的對象。具體而言,根據(jù)默認(rèn)設(shè)置,年輕代和老年代的比例通常為1:2。也就是說,年輕代占整個堆內(nèi)存的1/3,而老年代占2/3。這樣的比例設(shè)置可以更好地適應(yīng)不同類型的對象的內(nèi)存需求,提高垃圾回收效率,從而優(yōu)化程序的性能。具體默認(rèn)比例如下:
JAVA類加載的全過程是怎樣的?
類加載器:
APPClassLoader->ExtClassLoader->BooStrapClassLoader;
具體獲取類加載的代碼示例如下:
public class ClassLoaderExample {
public static void main(String[] args) {
// 獲取當(dāng)前類的類加載器(APPClassLoader)
ClassLoader currentClassLoader = ClassLoaderExample.class.getClassLoader();
System.out.println("Current ClassLoader: " + currentClassLoader);
// 獲取擴(kuò)展類加載器(ExtClassLoader)
ClassLoader extensionClassLoader = currentClassLoader.getParent();
System.out.println("Extension ClassLoader: " + extensionClassLoader);
// 獲取引導(dǎo)類加載器(Bootstrap ClassLoader)
ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
System.out.println("Bootstrap ClassLoader: " + bootstrapClassLoader);
}
}
什么是雙親委派機(jī)制及其作用
想知道雙親委派機(jī)制肯定需要對源碼有一些了解,否則只能靠背,具體源碼如下:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
簡單來說,雙親委派機(jī)制指的是當(dāng)一個類需要被加載時,它的加載請求會被委托給它的父類加載器,父類加載器會先嘗試加載這個類,如果加載成功就返回,如果加載失敗則會將加載請求再委托給它的父類加載器,直到最頂層的啟動類加載器(Bootstrap ClassLoader)。只有當(dāng)最頂層的啟動類加載器也無法加載時,才會由當(dāng)前類加載器自己來進(jìn)行加載。
使用雙親委派機(jī)制來加載類的好處是可以確保類的加載是由低層次的加載器向高層次的加載器進(jìn)行委托,從而保證了類的唯一性和安全性。這樣做可以避免出現(xiàn)java本地類被底層加載器加載的情況。
加載過程
分為三大部分: 加載 -》 連接 -》 初始化
加載(Loading)是指將類的字節(jié)碼文件加載到內(nèi)存中,并在方法區(qū)創(chuàng)建一個代表該類的Class對象。加載過程由類加載器完成。
連接(Linking)分為三個階段:驗(yàn)證(Verification)、準(zhǔn)備(Preparation)和解析(Resolution)。
-
驗(yàn)證(Verification):驗(yàn)證階段主要對類的字節(jié)碼進(jìn)行驗(yàn)證,確保字節(jié)碼的結(jié)構(gòu)和語義是合法的。這個階段主要包括以下幾個方面的驗(yàn)證:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證和符號引用驗(yàn)證。
-
準(zhǔn)備(Preparation):準(zhǔn)備階段是為類的靜態(tài)變量分配內(nèi)存空間,并設(shè)置默認(rèn)初始值。這個階段不會執(zhí)行任何Java代碼,只是簡單地分配內(nèi)存。
-
解析(Resolution):解析階段是將類的符號引用替換為直接引用的過程。符號引用指的是用一組符號來描述所引用的目標(biāo),而直接引用是直接指向目標(biāo)的指針、句柄或偏移量。解析階段主要完成虛擬機(jī)對類、接口、字段和方法的解析。
初始化(Initialization)是類加載的最后一個階段,主要是對類的靜態(tài)變量進(jìn)行賦值和執(zhí)行靜態(tài)代碼塊(實(shí)際上就是我們寫的代碼塊)。初始化階段是類加載的重要階段,只有在初始化階段才會真正執(zhí)行類中的Java代碼。初始化階段由虛擬機(jī)自動觸發(fā),主要有兩種情況:主動引用和被動引用。主動引用是指對類的主動使用,例如創(chuàng)建類的實(shí)例、訪問類的靜態(tài)變量和靜態(tài)方法等。被動引用則是指對類的被動使用,不會觸發(fā)類的初始化,例如通過子類引用父類的靜態(tài)變量。
一個對象從加載到JVM,再到被GC清除,都經(jīng)歷了什么過程?
當(dāng)一個對象從加載到JVM,再到被GC清除,它經(jīng)歷了以下過程:
-
加載:對象的類文件被加載到JVM中的方法區(qū)(也稱為永久代或元空間),并在方法區(qū)中創(chuàng)建一個代表該類的Class對象。
-
申請空間:在對象生成之前,對象在堆內(nèi)存中申請一塊空間,對象的實(shí)例變量會被賦予默認(rèn)初始值。
-
初始化:對象屬性進(jìn)行初始化。
-
連接:對象和棧中的引用建立連接,使得該對象可以被訪問。
-
年齡劃分:對象被分配到新生代的Eden區(qū),并初始年齡為1。每個對象的年齡由對象頭中的年齡標(biāo)識位(通常是4位)表示,所以一個對象的最大年齡為15。
-
Minor GC:當(dāng)新生代的Eden區(qū)空間不足時,會觸發(fā)Minor GC。在Minor GC中,存活的對象會被復(fù)制到Survivor區(qū)域(通常是from區(qū)和to區(qū)),同時年齡會增加。經(jīng)過多次復(fù)制和年齡增加后,對象會進(jìn)入老年代。
-
Full GC:當(dāng)老年代空間不足或者進(jìn)行整體內(nèi)存回收時,會觸發(fā)Full GC。Full GC會對整個堆內(nèi)存進(jìn)行回收,包括新生代和老年代。
-
對象回收:經(jīng)過GC后,不再被引用的對象會被GC清除,釋放內(nèi)存空間。
需要注意的是,當(dāng)前方法結(jié)束,棧中的指針會先移除掉,當(dāng)發(fā)生Full GC時,如果一個對象被回收,它的內(nèi)存分配將會被清除,即該對象所占用的內(nèi)存將被釋放。
怎么確定一個對象到底是不是垃圾? 什么是GC Root?
有兩種確認(rèn)方法,一是引用計數(shù)法,二是根可達(dá)性算法;
引用計數(shù)法:每當(dāng)一個對象被引用一次,它的引用計數(shù)就會加1,直到引用計數(shù)變?yōu)?時,該對象就被判定為垃圾對象。這是JDK 1.4之前使用的算法,但它存在一個明顯的問題,即當(dāng)兩個對象相互引用時,它們的引用計數(shù)永遠(yuǎn)不會變?yōu)?,導(dǎo)致無法回收這些對象,進(jìn)而可能導(dǎo)致內(nèi)存泄漏和內(nèi)存溢出問題。
根可達(dá)性算法:根可達(dá)性算法是目前主要使用的算法。它基于一個簡單的概念,即從一組稱為"GC Roots"的根對象開始,通過一系列引用關(guān)系來判斷對象是否可達(dá)。如果一個對象無法通過任何引用關(guān)系與GC Roots相連,那么該對象就被判定為垃圾對象。一旦確定了沒有連接到GC Roots的對象,垃圾收集器就會回收這些對象。
GC Roots包括類的靜態(tài)變量、常量池、class類以及方法棧中的變量。這些對象被認(rèn)為是程序的起始點(diǎn),通過它們可以追溯到所有其他對象的引用關(guān)系。
JVM有哪些垃圾回收算法
MarkSweep:標(biāo)記清除算法,目的是將垃圾標(biāo)記后,直接清楚垃圾,這樣會導(dǎo)致產(chǎn)生過多的內(nèi)存碎片,當(dāng)分配大對象時,可能會導(dǎo)致full gc,又或者直接內(nèi)存溢出。
Copying:拷貝算法,拷貝算法(Copying)犧牲了一半的內(nèi)存空間,只使用其中一半進(jìn)行分配。在標(biāo)記存活對象后,將對象整體遷移至另一半內(nèi)存空間,減少內(nèi)存碎片,但犧牲了可使用空間。
MarkCompack:標(biāo)記壓縮算法,為了解決拷貝算法的缺陷,就提出了標(biāo)記壓縮算法。這種算法在標(biāo)記階段跟標(biāo)記清除算法是一樣的,但是在完成標(biāo)記之后,不是直接清理垃圾內(nèi)存,而是將存活對象往一端移動,然后將端邊界以外的所有內(nèi)存直接清除。
JVM有哪些垃圾回收器?
Serial: 單線程垃圾回收器,使用復(fù)制算法。主要適用于小型應(yīng)用程序和單核處理器。
Serial Old: 老年代單線程垃圾回收器,使用標(biāo)記-整理算法。適用于較小的應(yīng)用程序和單核處理器,對于大型應(yīng)用程序可能會導(dǎo)致停頓時間較長。
ParNew: 年輕代多線程垃圾回收器,使用復(fù)制算法。與Serial相比,ParNew可以利用多個線程進(jìn)行垃圾回收,提高回收效率。
Parallel Scavenge: 年輕代多線程垃圾回收器,使用復(fù)制算法。目標(biāo)是盡可能地減少垃圾收集的停頓時間,適用于對系統(tǒng)吞吐量要求較高的應(yīng)用程序。
Parallel Old: 老年代多線程垃圾回收器,使用標(biāo)記整理算法。與Serial Old相比,Parallel Old可以利用多個線程進(jìn)行垃圾回收,提高回收效率。
CMS: 老年代多線程并發(fā)垃圾回收器,默認(rèn)使用標(biāo)記清除算法,可配置標(biāo)記整理算法。CMS的目標(biāo)是減少垃圾收集的停頓時間,適用于對響應(yīng)時間要求較高的應(yīng)用程序。
G1: 基于分代的垃圾回收器,已去除物理上的年輕代和老年代概念。使用region塊來保存和分配內(nèi)存,整體上使用標(biāo)記整理算法,微觀上使用復(fù)制算法。G1的目標(biāo)是在有限的時間內(nèi)獲得可控制的停頓時間,適用于大型應(yīng)用程序和對響應(yīng)時間要求較高的應(yīng)用程序。
什么是STW
STW(Stop The World)是指在垃圾回收過程中,所有應(yīng)用程序的線程都會被暫停,只有垃圾回收線程在執(zhí)行垃圾回收操作。這意味著在STW期間,應(yīng)用程序無法繼續(xù)執(zhí)行任何任務(wù),可能會導(dǎo)致一些延遲和性能問題。
減少STW時間是垃圾回收優(yōu)化的一個重要目標(biāo)。JVM的垃圾回收器會不斷進(jìn)行優(yōu)化,以減少STW時間,使應(yīng)用程序的暫停時間盡可能短。不同的垃圾回收器有不同的優(yōu)化策略和算法,以滿足不同場景下的需求。
STW都發(fā)生在那些階段
拋開單線程和多線程單一停頓時間不看,只看下CMS和G1垃圾回收器
CMS:共分為初始標(biāo)記,并發(fā)標(biāo)記,重新標(biāo)記,并發(fā)回收四個階段;其中初始標(biāo)記和重新標(biāo)記將會進(jìn)行STW,但是拉開了STW的戰(zhàn)線,所以總的停頓時間縮小了,但是由于他是在跟工作線程同時進(jìn)行回收,所以肯定會產(chǎn)生浮動垃圾;
G1:共分為初始標(biāo)記,并發(fā)標(biāo)記,重新標(biāo)記,篩選回收四個階段;和CMS邏輯相同,但是篩選回收將會進(jìn)行計算,jvm會判斷回收成本并執(zhí)行回收計劃,來優(yōu)先回收哪些對象
三色標(biāo)記
三色標(biāo)記是指將對象分為三個不同的顏色:白色、灰色和黑色。是CMS(Concurrent Mark Sweep)的標(biāo)記算法
-
白色:表示對象未被訪問過,也就是未被標(biāo)記為存活對象。
-
灰色:表示對象已經(jīng)被訪問過,但它引用的其他對象還未被標(biāo)記。
-
黑色:表示對象已經(jīng)被訪問過,并且它引用的其他對象也都被標(biāo)記。
在并行標(biāo)記階段,CMS會先將根節(jié)點(diǎn)標(biāo)記為灰色,然后并行地遍歷對象引用,將引用的對象標(biāo)記為灰色,并將其加入標(biāo)記隊(duì)列。當(dāng)標(biāo)記隊(duì)列為空時,標(biāo)記階段結(jié)束。
然而,由于并行標(biāo)記與應(yīng)用程序執(zhí)行是同時進(jìn)行的,可能會導(dǎo)致在標(biāo)記階段結(jié)束后,仍然存在引用發(fā)生變化的情況,比如引用刪除或引用轉(zhuǎn)變。為了解決這個問題,CMS需要進(jìn)行重新標(biāo)記的過程。重新標(biāo)記會遍歷所有的灰色對象,并將它們標(biāo)記為黑色。這樣可以確保所有的引用關(guān)系都被正確地標(biāo)記,并且不會錯誤地回收正在使用的對象。
如何進(jìn)行JVM調(diào)優(yōu)
JVM調(diào)優(yōu)主要就是通過定制JVM運(yùn)行參數(shù)來提高JAVA應(yīng)用程度的運(yùn)行數(shù)據(jù)
JVM參數(shù)有哪些
JVM參數(shù)大致可以分為三類:
- 標(biāo)注指令: -開頭,這些是所有的HotSpot都支持的參數(shù)。可以用java -help 打印出來。
- 非標(biāo)準(zhǔn)指令: -X開頭,這些指令通常是跟特定的HotSpot版本對應(yīng)的??梢杂胘ava -X 打印出來。
- 不穩(wěn)定參數(shù): -XX 開頭,這一類參數(shù)是跟特定HotSpot版本對應(yīng)的,并且變化非常大。詳細(xì)的文檔資料非常少。在JDK1.8版本下,有幾個常用的不穩(wěn)定指令:
- java -XX:+PrintCommandLineFlags : 查看當(dāng)前命令的不穩(wěn)定指令。
- java -XX:+PrintFlagsInitial : 查看所有不穩(wěn)定指令的默認(rèn)值。
- java -XX:+PrintFlagsFinal: 查看所有不穩(wěn)定指令最終生效的實(shí)際值
JVM調(diào)優(yōu)的開發(fā)者工具
JVM調(diào)優(yōu)通常需要借助一些開發(fā)者工具來輔助。阿里開源的Arthas就是一款非常強(qiáng)大的Java診斷工具,它可以幫助開發(fā)人員進(jìn)行實(shí)時的性能分析和問題排查。
Arthas具有豐富的功能,比如查看Java虛擬機(jī)的運(yùn)行狀態(tài)、監(jiān)控方法執(zhí)行時的參數(shù)和返回值、查看線程狀態(tài)和運(yùn)行時間、查看類加載和字節(jié)碼等。它還支持在運(yùn)行時修改類的方法體和實(shí)例狀態(tài),以及記錄方法調(diào)用堆棧等功能。
使用Arthas,開發(fā)人員可以方便地發(fā)現(xiàn)性能瓶頸和問題,并進(jìn)行針對性的優(yōu)化。它在Java開發(fā)中非常受歡迎,尤其是在分布式系統(tǒng)和微服務(wù)架構(gòu)中的性能調(diào)優(yōu)中發(fā)揮了重要作用。
當(dāng)然,除了Arthas,還有其他一些常用的JVM調(diào)優(yōu)工具,比如VisualVM、JConsole、JProfiler等,開發(fā)人員可以根據(jù)自己的需要選擇適合自己的工具來進(jìn)行JVM調(diào)優(yōu)。
官方文檔地址: https://arthas.aliyun.com/doc/
總結(jié)
JVM調(diào)優(yōu)確實(shí)不像開發(fā)中常見的可視化界面工具那樣直觀,而更多地需要基于底層的知識和經(jīng)驗(yàn)來解決問題。JVM調(diào)優(yōu)的確沒有固定的定性規(guī)則,但可以根據(jù)一些常見的性能問題和優(yōu)化思路來進(jìn)行思考和回答。
在面試時,如果遇到JVM調(diào)優(yōu)相關(guān)的問題,可以按照以下思路來回答:
-
首先,了解JVM的基本架構(gòu)和垃圾回收機(jī)制。這包括堆、棧、方法區(qū)等內(nèi)存結(jié)構(gòu),以及各種垃圾回收器的特點(diǎn)和工作原理。
-
掌握常見的性能問題和優(yōu)化手段。例如,內(nèi)存泄漏、頻繁的Full GC、長時間的STW等問題,可以結(jié)合具體情況提出相應(yīng)的解決方案。
-
熟悉一些性能監(jiān)控和分析工具。如前面提到的Arthas、VisualVM、JConsole等,可以介紹自己使用過的工具,并舉例說明如何利用這些工具進(jìn)行性能分析和問題排查。
-
強(qiáng)調(diào)實(shí)踐經(jīng)驗(yàn)和解決問題的思路。雖然沒有固定的定性規(guī)則,但可以根據(jù)自己的實(shí)踐經(jīng)驗(yàn)和理解,提出一些常見的優(yōu)化思路和原則,比如減少對象的創(chuàng)建和銷毀、合理配置內(nèi)存參數(shù)、優(yōu)化算法和數(shù)據(jù)結(jié)構(gòu)等。文章來源:http://www.zghlxwxcb.cn/news/detail-617262.html
總之,在回答JVM調(diào)優(yōu)相關(guān)的面試題時,除了記住一些常見的問題和解決方案,更重要的是展示出自己的思考和解決問題的能力。文章來源地址http://www.zghlxwxcb.cn/news/detail-617262.html
到了這里,關(guān)于JVM調(diào)優(yōu)篇:探索Java性能優(yōu)化的必備種子面試題的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!