面試必問(wèn) | 聊聊JVM性能調(diào)優(yōu)?
經(jīng)過(guò)幾天的思考,我決定暫時(shí)放下【字節(jié)碼編程】的更新,先更新一些面試中經(jīng)常會(huì)被問(wèn)及的一些知識(shí)點(diǎn),以便能夠幫助小伙伴們系統(tǒng)的梳理面試中需要掌握的知識(shí)技能。
主要的方式是以面試的角度,深度聊聊面試中經(jīng)常被問(wèn)及的各項(xiàng)知識(shí)點(diǎn)。
對(duì)于工作3年左右的Java程序員來(lái)說(shuō),在面試大廠的過(guò)程中,面試官可能不會(huì)太關(guān)注你做了多少個(gè)項(xiàng)目、你的CRUD水平如何。更多的是關(guān)注你對(duì)某項(xiàng)技術(shù)點(diǎn)的理解深度,所以說(shuō),工作3年左右的小伙伴一定要把自己的重心放到技術(shù)的深度上來(lái)。
今天,我們先一起聊聊關(guān)于JVM性能調(diào)優(yōu)的話題,本文的主要結(jié)構(gòu)如下所示。
常見(jiàn)面試題
關(guān)于JVM,一道常見(jiàn)的面試題就是:Java中創(chuàng)建的對(duì)象是存儲(chǔ)在JVM中的哪個(gè)區(qū)域的?
例如,這里,我們簡(jiǎn)單的列舉一行代碼,如下所示。
- User user = new User();
關(guān)于上面的代碼,不少小伙伴都知道,創(chuàng)建出來(lái)的User對(duì)象是放在JVM中的堆區(qū)域的,而User對(duì)象的引用是放在棧中的。但如果你只是了解到這種程度,那面試官就會(huì)認(rèn)為你了解的太淺顯了,可能就會(huì)達(dá)不到他們的要求。其實(shí)面試官想要了解你是否對(duì)JVM有一個(gè)更深入的認(rèn)識(shí)。
站在面試官的角度來(lái)看這個(gè)問(wèn)題時(shí),回答創(chuàng)建出來(lái)的User對(duì)象是放在JVM的堆區(qū),也并沒(méi)有錯(cuò)。但是JVM的堆內(nèi)存區(qū)域又會(huì)分為年輕代和老年代,而年輕代又會(huì)分為Eden區(qū)和Survivor區(qū)。JVM堆空間的邏輯結(jié)構(gòu)如下圖所示。
而面試官更想了解的是你能不能說(shuō)出來(lái)創(chuàng)建的對(duì)象具體是存放在JVM堆空間的哪個(gè)區(qū)域。
在JVM內(nèi)部,會(huì)將整個(gè)堆空間劃分成年輕代和老年代,年輕代默認(rèn)會(huì)占整個(gè)堆內(nèi)存空間的1/3,老年代默認(rèn)會(huì)占整個(gè)堆內(nèi)存空間的2/3。年輕代又會(huì)劃分為Eden區(qū)和兩個(gè)Survivor區(qū),它們之間的默認(rèn)比例是Eden:Survivor1:Survivor2 = 8:1:1。
如果你能回答出 新創(chuàng)建的User對(duì)象是存放在JVM堆空間中年輕代的Eden區(qū),那面試官就會(huì)對(duì)你刮目相看了。當(dāng)然,這里沒(méi)有考慮JVM的逃逸分析情況,關(guān)于JVM的逃逸分析,大家可以參考《逃逸分析》一文。
JVM體系結(jié)構(gòu)
JVM主要由三個(gè)子系統(tǒng)構(gòu)成,分別為:類加載器子系統(tǒng)、運(yùn)行時(shí)數(shù)據(jù)區(qū)(內(nèi)存結(jié)構(gòu))和字節(jié)碼執(zhí)行引擎。
關(guān)于JVM的體系結(jié)構(gòu)全貌,我們先來(lái)看一張圖。
當(dāng)我們開(kāi)發(fā)Java程序時(shí),首先會(huì)編寫(xiě).java文件,之后,會(huì)將.java文件編譯成.class文件。
JVM中,會(huì)通過(guò)類裝載子系統(tǒng)將.class文件的內(nèi)容裝載到JVM的運(yùn)行時(shí)數(shù)據(jù)區(qū),而JVM的運(yùn)行時(shí)數(shù)據(jù)區(qū)又會(huì)分為:方法區(qū)、堆、棧、本地方法棧和程序計(jì)數(shù)器 幾個(gè)部分。
在裝載class文件的內(nèi)容時(shí),會(huì)將class文件的內(nèi)容拆分為幾個(gè)部分,分別裝載到JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)的幾個(gè)部分。其中,值得注意的是:程序計(jì)數(shù)器的作用是:記錄程序執(zhí)行的下一條指令的地址。
方法區(qū)也叫作元空間,主要包含了:運(yùn)行時(shí)常量池、類型信息、字段信息、方法信息、類加載器的引用、對(duì)應(yīng)的Class實(shí)例的引用等信息。
在JVM中,程序的執(zhí)行是通過(guò)執(zhí)行引擎進(jìn)行的,執(zhí)行引擎會(huì)調(diào)用本地方法的接口來(lái)執(zhí)行本地方法庫(kù),進(jìn)而完成整個(gè)程序邏輯的執(zhí)行。
我們常說(shuō)的垃圾收集器是包含在執(zhí)行引擎中的,在程序的運(yùn)行過(guò)程中,執(zhí)行引擎會(huì)開(kāi)啟垃圾收集器,并在后臺(tái)運(yùn)行,垃圾收集器會(huì)不斷監(jiān)控程序運(yùn)行過(guò)程中產(chǎn)生的內(nèi)存垃圾信息,并根據(jù)相應(yīng)的策略對(duì)垃圾信息進(jìn)行清理。
這里,大家需要注意的是:棧、本地方法棧和程序計(jì)數(shù)器是每個(gè)線程運(yùn)行時(shí)獨(dú)占的,而方法區(qū)和堆是所有線程共享的。所以,棧、本地方法棧和程序計(jì)數(shù)器不會(huì)涉及線程安全問(wèn)題,而方法區(qū)和堆會(huì)涉及線程安全問(wèn)題。
方法區(qū)(元空間)
很多小伙伴一看到方法區(qū)三個(gè)字,腦海中的第一印象可能是存儲(chǔ)方法的地方吧。
實(shí)則不然,方法區(qū)的另一個(gè)名字叫作元空間,相信不少小伙伴或多或少的聽(tīng)說(shuō)過(guò)元空間。這個(gè)區(qū)域是JDK1.8中劃分出來(lái)的。主要包含:運(yùn)行時(shí)常量池、類型信息、字段信息、方法信息、類加載器的引用、對(duì)應(yīng)的Class實(shí)例的引用等信息。方法區(qū)中的信息能夠被多個(gè)線程共享。
例如,在程序中聲明的常量、靜態(tài)變量和有關(guān)于類的信息等的引用,都會(huì)存放在方法區(qū),而這些引用所指向的具體對(duì)象 一般都會(huì)在堆中開(kāi)辟單獨(dú)的空間進(jìn)行存儲(chǔ),也可能會(huì)在直接內(nèi)存中進(jìn)行存儲(chǔ)。
堆
堆中主要存儲(chǔ)的是實(shí)際創(chuàng)建的對(duì)象,也就是會(huì)存儲(chǔ)通過(guò)new關(guān)鍵字創(chuàng)建的對(duì)象,堆中的對(duì)象能夠被多個(gè)線程共享。堆中的數(shù)據(jù)不需要事先明確生存期,可以動(dòng)態(tài)的分配內(nèi)存,不再使用的數(shù)據(jù)和對(duì)象由JVM中的GC機(jī)制自動(dòng)回收。對(duì)JVM的性能調(diào)優(yōu)一般就是對(duì)堆內(nèi)存的調(diào)優(yōu)。
Java中基本類型的包裝類:Byte、Short、Integer、Long、Float、Double、Boolean、Character類型的數(shù)據(jù)是存儲(chǔ)在堆中的。
堆一般會(huì)被分成年輕代和老年代。而年輕代又會(huì)被進(jìn)一步分為1個(gè)Eden區(qū)和2個(gè)Survivor區(qū)。在內(nèi)存分配上,如果保持默認(rèn)配置的話,年輕代和老年代的內(nèi)存大小比例為1 : 2,年輕代中的1個(gè)Eden區(qū)和2個(gè)Survivor區(qū)的內(nèi)存大小比例為:8 : 1 : 1。
棧
棧一般又叫作線程?;蛱摂M機(jī)棧,一般存儲(chǔ)的是局部變量。在Java中,每個(gè)線程都會(huì)有一個(gè)單獨(dú)的棧區(qū),每個(gè)棧中的元素都是私有的,不會(huì)被其他的棧所訪問(wèn)。棧中的數(shù)據(jù)大小和生存期都是確定的,存取速度比較快。
在Java中,所有的基本數(shù)據(jù)類型(byte、short、int、long、float、double、boolean、char)和引用變量(對(duì)象引用)都是在棧中的。一般情況下,線程退出或者方法退出時(shí),棧中的數(shù)據(jù)會(huì)被自動(dòng)清除。
程序在執(zhí)行過(guò)程中,會(huì)在棧中為不同的方法創(chuàng)建不同的棧幀,在棧幀中又包含了:局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接和方法出口。
關(guān)于局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接和方法出口的具體作用,會(huì)在《架構(gòu)師進(jìn)階系列》中的后續(xù)文章中詳細(xì)闡述。
棧中一般會(huì)存儲(chǔ)對(duì)象的引用,這些引用所指向的具體對(duì)象一般都會(huì)在堆中開(kāi)辟單獨(dú)的地址空間進(jìn)行存儲(chǔ),也有可能存儲(chǔ)在直接內(nèi)存中。
注意:這里說(shuō)的是這些引用所指向的具體對(duì)象一般都會(huì)在堆中開(kāi)辟單獨(dú)的地址空間進(jìn)行存儲(chǔ),也有可能存儲(chǔ)在直接內(nèi)存中。
因?yàn)樵贘VM中,如果開(kāi)啟了逃逸分析和標(biāo)量替換,則可能不會(huì)再在堆上創(chuàng)建對(duì)象,可能會(huì)將對(duì)象直接分配到棧上,也可能不再創(chuàng)建對(duì)象,而是進(jìn)一步分解對(duì)象中的成員變量,將其直接在棧上分配空間并賦值。
本地方法棧
本地方法棧相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,就是保存native方法進(jìn)入?yún)^(qū)域的地址。
例如,在Java中創(chuàng)建線程,調(diào)用Thread對(duì)象的start()方法時(shí),會(huì)通過(guò)本地方法start0()調(diào)用操作系統(tǒng)創(chuàng)建線程的方法。此時(shí),本地方法棧就會(huì)保存start0()方法進(jìn)入?yún)^(qū)域的內(nèi)存地址。
程序計(jì)數(shù)器
程序計(jì)數(shù)器也叫作PC計(jì)數(shù)器,只要存儲(chǔ)的是下一條將要執(zhí)行的命令的地址。
雙親委派機(jī)制
何為雙親委派?
JVM中是通過(guò)類的雙親委派機(jī)制來(lái)加載的,那什么是雙親委派機(jī)制呢?我們先來(lái)看一張圖。
當(dāng)JVM加載某個(gè)類的時(shí)候,不會(huì)直接使用當(dāng)前類的加載器加載該類,會(huì)先委托父加載器尋找要加載的目標(biāo)類,找不到再委托上層的父加載器進(jìn)行加載,直到引導(dǎo)類加載器同樣找不到要加載的目標(biāo)類,就會(huì)在自己的類加載路徑中查找并加載目標(biāo)類。
簡(jiǎn)單來(lái)說(shuō):雙親委派機(jī)制就是:先使用父加載器加載,如果父加載器找不到要加載的目標(biāo)類,就使用子加載器自己加載。
為何使用雙親委派機(jī)制?
這里,小伙伴們有沒(méi)有想過(guò)這樣一個(gè)問(wèn)題:JVM為何要使用雙親委派機(jī)制呢?
為了更好的說(shuō)明問(wèn)題,我們自己創(chuàng)建一個(gè)java.lang包,并在java.lang包下,創(chuàng)建一個(gè)String類,如下所示。
- package java.lang;
- /**
- * @author binghe (公眾號(hào):冰河技術(shù))
- * @version 1.0.0
- * @description 測(cè)試時(shí)雙親委派機(jī)制
- */
- public class String {
- public static void main(String[] args){
- System.out.println("自定義的String類");
- }
- }
這里,我們自己創(chuàng)建一個(gè)java.lang.String類,而JDK中也存在一個(gè)java.lang.String類,如果運(yùn)行我們自己創(chuàng)建的java.lang.String會(huì)發(fā)生什么呢?會(huì)輸出如下錯(cuò)誤信息。
錯(cuò)誤: 在類 java.lang.String 中找不到 main 方法, 請(qǐng)將 main 方法定義為:
- public static void main(String[] args)
否則 JavaFX 應(yīng)用程序類必須擴(kuò)展javafx.application.Application
那JVM為何要使用雙親委派機(jī)制呢?試想,如果我們自己寫(xiě)的類能夠隨隨便便覆蓋JDK中的類的話,那JDK中的代碼是不是就沒(méi)有任何安全性可言了?沒(méi)錯(cuò),JVM為了代碼的安全性,也即是沙箱安全機(jī)制,使用了雙親委派機(jī)制。
另外,使用雙親委派機(jī)制,也能防止JVM內(nèi)存中出現(xiàn)多份相同的字節(jié)碼。例如,兩個(gè)類A和B,都需要加載System類。如果JVM沒(méi)有提供雙親委派機(jī)制,那么A和B兩個(gè)類就會(huì)分別加載一份System的字節(jié)碼,這樣JVM內(nèi)存中就會(huì)出現(xiàn)這份System字節(jié)碼。
相反,JVM提供了雙親委派機(jī)制的話,在加載System類的過(guò)程中,會(huì)遞歸的向父加載器查找并加載,整個(gè)過(guò)程會(huì)優(yōu)先選用BootStrapClassLoader加載器,也就是我們通常說(shuō)的引導(dǎo)類加載器。如果找不到就逐級(jí)向下使用子加載器進(jìn)行加載。
而System類可以在BootStrapClassLoader中進(jìn)行加載,如果System類已經(jīng)通過(guò)A類的引用加載過(guò),此時(shí)B類也要加載System類,也會(huì)從BootStrapClassLoader開(kāi)始加載System類,此時(shí),BootStrapClassLoader發(fā)現(xiàn)已經(jīng)加載過(guò)System類了,就會(huì)直接返回內(nèi)存中的System,不再重新加載。
這樣,在JVM內(nèi)存中,就只會(huì)存在一份System類的字節(jié)碼。
類加載器的父子關(guān)系
如何確認(rèn)類加載器的父子關(guān)系呢?這里,我們?cè)賮?lái)看一個(gè)示例代碼,如下所示。
- /**
- * @author binghe (公眾號(hào):冰河技術(shù))
- * @version 1.0.0
- * @description 類的雙親委派機(jī)制
- */
- public class User {
- public static void main(String[] args){
- User user = new User();
- System.out.println(user.getClass().getClassLoader());
- System.out.println(user.getClass().getClassLoader().getParent());
- System.out.println(user.getClass().getClassLoader().getParent().getParent());
- }
- }
這段代碼也比較簡(jiǎn)單,創(chuàng)建了一個(gè)User對(duì)象,打印User對(duì)象的類加載器,父類加載和上層父加載器。在IDEA中運(yùn)行上述代碼,會(huì)輸出如下信息。
- sun.misc.Launcher$AppClassLoader@18b4aac2
- sun.misc.Launcher$ExtClassLoader@135fbaa4
- null
可以看到,User對(duì)象的類加載器是AppClassLoader,父加載器是ExtClassLoader。而輸出的null其實(shí)是BootStrapClassLoader,而B(niǎo)ootStrapClassLoader也就是上層父加載器。
這樣,類加載器的父子關(guān)系就出來(lái)了:AppClassLoader的父加載器是ExtClassLoader,ExtClassLoader的父加載器是BootStrapClassLoader。
這里,需要注意的是:父加載器并不是父類。
類加載器加載的類
- 引導(dǎo)類加載器(BootStrapClassLoader):負(fù)責(zé)加載%JAVA_HOME%/jre/lib目錄下的所有jar包,或者是-Xbootclasspath參數(shù)指定的路徑;
- 擴(kuò)展類加載器(ExtClassLoader):負(fù)責(zé)加載%JAVA_HOME%/jre/lib/ext目錄下的所有jar包,或者是java.ext.dirs參數(shù)指定的路徑;
- 應(yīng)用類加載器(AppClassLoader):負(fù)責(zé)加載用戶類路徑上所指定的類庫(kù)。
注意:引導(dǎo)類加載器和擴(kuò)展類加載器加載的類都是預(yù)先加載好的,而應(yīng)用類加載器用來(lái)加載應(yīng)用工程的classes以及l(fā)ib下的類庫(kù),僅僅聲明,并不會(huì)提前載入JVM內(nèi)存,等到使用的時(shí)候才會(huì)加載到JVM內(nèi)存中。
類的加載過(guò)程
一個(gè)類在JVM中的加載過(guò)程大致經(jīng)歷了加載、驗(yàn)證、準(zhǔn)備、解析和初始化。
加載: 主要是在計(jì)算機(jī)磁盤(pán)上通過(guò)IO流讀取字節(jié)碼文件(.class文件),當(dāng)程序需要使用某個(gè)類時(shí),才會(huì)對(duì)這個(gè)類進(jìn)行加載操作,比如,在程序中調(diào)用某個(gè)類的靜態(tài)方法,使用new關(guān)鍵字創(chuàng)建某個(gè)類的對(duì)象等。在加載階段,往往會(huì)在JVM的堆內(nèi)存中生成一個(gè)代表這個(gè)類的Class對(duì)象,這個(gè)對(duì)象作為存放在JVM方法區(qū)中這個(gè)類的各種數(shù)據(jù)的訪問(wèn)入口,也可以叫做訪問(wèn)句柄。
- 驗(yàn)證:主要的作用就是校驗(yàn)字節(jié)碼的正確性,是否符合JVM規(guī)范。
- 準(zhǔn)備:為類的靜態(tài)變量分配相應(yīng)的內(nèi)存,并賦予默認(rèn)值。
- 解析:將程序中的符號(hào)引用替換為直接引用,這里的符號(hào)引用包括:靜態(tài)方法等。此階段就是將一些靜態(tài)方法等符號(hào)引用替換成指向數(shù)據(jù)所在內(nèi)存地址的指針,這些指針就是直接引用。如果是在類加載過(guò)程中完成的符號(hào)引用到直接引用的替換,這個(gè)替換的過(guò)程就叫作靜態(tài)鏈接過(guò)程。如果是在運(yùn)行期間完成的符號(hào)引用到直接引用的替換,這個(gè)替換的過(guò)程就叫作動(dòng)態(tài)鏈接過(guò)程。
- 初始化:對(duì)類的靜態(tài)變量進(jìn)行初始化,為其賦予程序中指定的值,并執(zhí)行靜態(tài)代碼塊中的代碼。
注意:在準(zhǔn)備階段和初始化階段都會(huì)為類的靜態(tài)變量賦值,不同之處就是在準(zhǔn)備階段為類的靜態(tài)變量賦予的是默認(rèn)值,而在初始化階段為類的靜態(tài)變量賦予的是真正要賦予的值。
例如,在程序中有如下靜態(tài)變量。
- public static int count = 100;
在準(zhǔn)備階段會(huì)為count賦予一個(gè)默認(rèn)值0,而在初始化階段才會(huì)真正將count賦值為100。
JVM調(diào)優(yōu)參數(shù)
在JVM中,主要是對(duì)堆(新生代)、方法區(qū)和棧進(jìn)行性能調(diào)優(yōu)。各個(gè)區(qū)域的調(diào)優(yōu)參數(shù)如下所示。
- 堆:-Xms、-Xmx
- 新生代:-Xmn
- 方法區(qū)(元空間):-XX:MetaspaceSize、-XX:MaxMetaspaceSize
- 棧(線程):-Xss
為了更加直觀的表述,我們可以將JVM的內(nèi)存區(qū)域和對(duì)應(yīng)的調(diào)優(yōu)參數(shù)總結(jié)成下圖所示。
在設(shè)置JVM啟動(dòng)參數(shù)時(shí),需要特別注意方法區(qū)(元空間)的參數(shù)設(shè)置。
關(guān)于方法區(qū)(元空間)的JVM參數(shù)主要有兩個(gè):-XX:MetaspaceSize和-XX:MaxMetaspaceSize。
-XX:MetaspaceSize: 指的是方法區(qū)(元空間)觸發(fā)Full GC的初始內(nèi)存大小(方法區(qū)沒(méi)有固定的初始內(nèi)存大小),以字節(jié)為單位,默認(rèn)為21M。達(dá)到設(shè)置的值時(shí),會(huì)觸發(fā)Full GC,同時(shí)垃圾收集器會(huì)對(duì)這個(gè)值進(jìn)行修改。
如果在發(fā)生Full GC時(shí),回收了大量?jī)?nèi)存空間,則垃圾收集器會(huì)適當(dāng)降低此值的大小;如果在發(fā)生Full GC時(shí),釋放的空間比較少,則在不超過(guò)設(shè)置的-XX:MetaspaceSize值或者在沒(méi)設(shè)置-XX:MetaspaceSize的值時(shí)不超過(guò)21M,適當(dāng)提高此值。
-XX:MaxMetaspaceSize: 指的是方法區(qū)(元空間)的最大值,默認(rèn)值為-1,不受堆內(nèi)存大小限制,此時(shí),只會(huì)受限于本地內(nèi)存大小。
最后需要注意的是: 調(diào)整方法區(qū)(元空間)的大小會(huì)發(fā)生Full GC,這種操作的代價(jià)是非常昂貴的。如果發(fā)現(xiàn)應(yīng)用在啟動(dòng)的時(shí)候發(fā)生了Full GC,則很有可能是方法區(qū)(元空間)的大小被動(dòng)態(tài)調(diào)整了。
所以,為了盡量不讓JVM動(dòng)態(tài)調(diào)整方法區(qū)(元空間)的大小造成頻繁的Full GC,一般將-XX:MetaspaceSize和-XX:MaxMetaspaceSize設(shè)置成一樣的值。例如,物理內(nèi)存8G,可以將這兩個(gè)值設(shè)置為256M
最后,我們一起看下在物理內(nèi)存8G的情況下,啟動(dòng)應(yīng)用程序時(shí),可以設(shè)置的JVM參數(shù)。當(dāng)然,我這里給出的是一些經(jīng)驗(yàn)值,實(shí)際部署到生產(chǎn)環(huán)境時(shí),需要經(jīng)過(guò)壓測(cè)找到最佳的參數(shù)值。
- 啟動(dòng)SpringBoot
- java ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐jar xxx.jar
- 啟動(dòng)Tomcat(Linux)
在Tomcat bin目錄下catalina.sh文件里配置。
- ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M
- 啟動(dòng)Tomcat(Windows)
在Tomcat bin目錄下catalina.bat文件里配置。
- ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M
總結(jié)
本文以面試為背景,探討了有關(guān)JVM的常見(jiàn)面試問(wèn)題。文章開(kāi)頭以一個(gè)常見(jiàn)的面試題舉例,說(shuō)明了JVM在互聯(lián)網(wǎng)大廠面試中的重要性。接下里,介紹了JVM的體系結(jié)構(gòu),包含:方法區(qū)(元空間)、堆、棧、本地方法棧和程序計(jì)數(shù)器。
隨后,介紹了JVM中的雙親委派機(jī)制,說(shuō)明了何為雙親委派,為何使用雙親委派機(jī)制,類加載器的父子關(guān)系。需要注意的是:這里說(shuō)的類加載器的父子關(guān)系并不是父類和子類的關(guān)系。隨后,介紹了各個(gè)類加載器要加載哪些類。
接下來(lái),介紹了類的加載過(guò)程,主要包含:加載、驗(yàn)證、準(zhǔn)備、解析和初始化等步驟,同時(shí),說(shuō)明了各個(gè)步驟的主要作用。
最后,介紹了JVM中常用的調(diào)優(yōu)參數(shù),涵蓋堆、新生代、方法區(qū)(元空間)和棧(線程)常用的調(diào)優(yōu)參數(shù)。并以Tomcat調(diào)優(yōu)為例,詳細(xì)說(shuō)明了如何使用這些調(diào)優(yōu)參數(shù)。
說(shuō)了這么多你都掌握了嗎?
本文轉(zhuǎn)載自微信公眾號(hào)「冰河技術(shù)」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系冰河技術(shù)公眾號(hào)。





































