1. JVM的體系結(jié)構(gòu)
類(lèi)加載器(ClassLoader)
作用:負(fù)責(zé)加載字節(jié)碼文件(.class文件)到內(nèi)存中。它是JVM執(zhí)行類(lèi)加載機(jī)制的基礎(chǔ)組件,將類(lèi)的字節(jié)碼數(shù)據(jù)加載到*區(qū),在堆中創(chuàng)建對(duì)應(yīng)的Class對(duì)象作為*區(qū)中類(lèi)數(shù)據(jù)的訪問(wèn)入口。
分類(lèi):主要包括啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader),它負(fù)責(zé)加載Java核心類(lèi)庫(kù)(如java.lang包中的類(lèi)),是由C++實(shí)現(xiàn)的,是JVM的一部分;擴(kuò)展類(lèi)加載器(Ex* ClassLoader),用于加載Java的擴(kuò)展庫(kù)(位于jre/lib/ext目錄下);應(yīng)用程序類(lèi)加載器(Application ClassLoader),也稱(chēng)為系統(tǒng)類(lèi)加載器,負(fù)責(zé)加載用戶(hù)類(lèi)路徑(classpath)下的類(lèi)。
雙親委派模型:這是類(lèi)加載器的一種工作機(jī)制。當(dāng)一個(gè)類(lèi)加載器收到類(lèi)加載請(qǐng)求時(shí),它首先會(huì)把請(qǐng)求委派給父類(lèi)加載器。只有當(dāng)父類(lèi)加載器無(wú)法完成該加載任務(wù)時(shí)(它的搜索范圍中沒(méi)有找到所需的類(lèi)),子加載器才會(huì)嘗試自己加載。這種模型可以避免類(lèi)的重復(fù)加載,并且保證了Java核心類(lèi)庫(kù)的安全性,例如,用戶(hù)自定義的java.lang.Object類(lèi)不會(huì)被加載,因?yàn)閱?dòng)類(lèi)加載器已經(jīng)加載了系統(tǒng)的java.lang.Object類(lèi)。
運(yùn)行時(shí)數(shù)據(jù)區(qū)(Runtime Data Areas)
程序計(jì)數(shù)器(Program Counter Register):它是一塊較小的內(nèi)存空間,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴(lài)這個(gè)計(jì)數(shù)器來(lái)完成。它是線程私有的,每個(gè)線程都有自己獨(dú)立的程序計(jì)數(shù)器,這樣可以保證各個(gè)線程按自己的執(zhí)行順序執(zhí)行字節(jié)碼。
Java虛擬機(jī)棧(Java Virtual Machine Stacks):它也是線程私有的,生命周期與線程相同。虛擬機(jī)棧描述的是Java*執(zhí)行的內(nèi)存模型,每個(gè)*在執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame),用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、*出口等信息。當(dāng)一個(gè)*被調(diào)用時(shí),一個(gè)新的棧幀就會(huì)被壓入棧中;當(dāng)*執(zhí)行完成后,棧幀就會(huì)從棧中彈出。如果棧的深度超過(guò)了虛擬機(jī)允許的范圍,就會(huì)拋出StackOverflowError異常;如果虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展,但是在擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryError異常。
本地*棧(Native Method Stacks):與Java虛擬機(jī)棧類(lèi)似,不過(guò)它是為本地(Native)*服務(wù)的。本地*是指用非Java語(yǔ)言(如C或C++)編寫(xiě)的,并且被Java代碼調(diào)用的*。它的具體實(shí)現(xiàn)方式和內(nèi)存分配方式可能因JVM的不同而有所差異,在某些JVM實(shí)現(xiàn)中,本地*棧和Java虛擬機(jī)棧是合二為一的。同樣,本地*棧也會(huì)出現(xiàn)StackOverflowError和OutOfMemoryError異常。
堆(Heap):它是JVM管理的內(nèi)存中*的一塊,是被所有線程共享的一塊內(nèi)存區(qū)域。幾乎所有的對(duì)象實(shí)例和數(shù)組都在堆上分配內(nèi)存。堆的內(nèi)存空間是不連續(xù)的,它主要分為新生代(Young Generation)和老年代(Old Generation)。新生代又可以細(xì)分為Eden空間、From Survivor空間和To Survivor空間。垃圾收集器主要就是針對(duì)堆內(nèi)存進(jìn)行回收操作,以釋放那些不再被引用的對(duì)象所占用的空間。因?yàn)槎咽枪蚕淼?,并且需要頻繁地進(jìn)行對(duì)象的創(chuàng)建和銷(xiāo)毀,所以它也是最容易出現(xiàn)OutOfMemoryError異常的區(qū)域。
*區(qū)(Method Area):它也是所有線程共享的內(nèi)存區(qū)域,用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息(包括類(lèi)的版本、字段、*、接口等信息)、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。在Java 8之前,*區(qū)是通過(guò)*代(PermGen)實(shí)現(xiàn)的,*代有固定的大小限制,容易出現(xiàn)OutOfMemoryError異常。在Java 8及以后,*區(qū)被元空間(Met*ace)取代,元空間使用本地內(nèi)存,理論上它的大小只受限于本地內(nèi)存的大小,不過(guò)也需要合理配置參數(shù),否則也可能出現(xiàn)內(nèi)存問(wèn)題。
2. 垃圾回收(Garbage Collection,GC)
垃圾回收的基本原理
引用計(jì)數(shù)法(Reference Counting):這是一種簡(jiǎn)單的垃圾回收算法。每個(gè)對(duì)象都有一個(gè)引用計(jì)數(shù)器,當(dāng)有一個(gè)地方引用這個(gè)對(duì)象時(shí),計(jì)數(shù)器就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器就減1。當(dāng)計(jì)數(shù)器的值為0時(shí),就表示這個(gè)對(duì)象可以被回收了。但是這種*無(wú)法解決循環(huán)引用的問(wèn)題,例如,對(duì)象A引用對(duì)象B,對(duì)象B又引用對(duì)象A,此時(shí)它們的引用計(jì)數(shù)都不為0,但實(shí)際上這兩個(gè)對(duì)象可能已經(jīng)沒(méi)有其他有效的外部引用了,應(yīng)該被回收。
可達(dá)性分析算法(Reachability *ysis):這是目前主流JVM使用的垃圾回收算法。它以一系列被稱(chēng)為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路徑稱(chēng)為引用鏈(Reference Chain)。當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連(即不可達(dá))時(shí),則證明此對(duì)象是可以被回收的。GC Roots對(duì)象包括虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象、本地*棧中JNI(Java Native Inte*ce)引用的對(duì)象、*區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象、*區(qū)中常量引用的對(duì)象等。
垃圾收集器(Garbage Collector)
Serial收集器:這是最基本、歷史最悠久的收集器。它是一個(gè)單線程收集器,在進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程,直到收集結(jié)束。它的優(yōu)點(diǎn)是簡(jiǎn)單高效,對(duì)于限定單個(gè)CPU的環(huán)境來(lái)說(shuō),由于沒(méi)有線程交互的開(kāi)銷(xiāo),專(zhuān)心做垃圾收集可以獲得*的單線程收集效率。
ParNew收集器:它是Serial收集器的多線程版本。除了使用多線程進(jìn)行垃圾收集外,其余行為包括收集算法、Stop
The
World機(jī)制等都和Serial收集器一樣。它是許多運(yùn)行在Server模式下的JVM虛擬機(jī)*的新生代收集器,因?yàn)樗芘cCMS收集器(老年代收集器)很好地配合工作。
Parallel Scavenge收集器:它也是一個(gè)新生代收集器,采用復(fù)制算法。它的特點(diǎn)是關(guān)注的是吞吐量(Throughput),即CPU用于運(yùn)行用戶(hù)代碼的時(shí)間與CPU總消耗時(shí)間的比值。它提供了兩個(gè)參數(shù)用于*控制吞吐量,如
XX:MaxGCPauseMillis(控制*垃圾收集停頓時(shí)間)和
XX:GCTimeRatio(直接設(shè)置吞吐量大小)。
CMS收集器(Concurrent Mark Sweep):這是一種以獲取最短回收停頓時(shí)間為目標(biāo)的老年代收集器。它的工作過(guò)程比較復(fù)雜,主要分為四個(gè)階段:初始標(biāo)記(Initial Mark)、并發(fā)標(biāo)記(Concurrent Mark)、重新標(biāo)記(Re
Mark)和并發(fā)清除(Concurrent Sweep)。其中初始標(biāo)記和重新標(biāo)記這兩個(gè)階段需要暫停所有用戶(hù)線程(Stop
The
World),但時(shí)間比較短;并發(fā)標(biāo)記和并發(fā)清除階段是與用戶(hù)線程同時(shí)進(jìn)行的,這樣就可以在一定程度上減少垃圾收集時(shí)對(duì)用戶(hù)線程的影響,從而提高應(yīng)用程序的響應(yīng)速度。不過(guò),CMS收集器也有一些缺點(diǎn),比如它對(duì)CPU資源比較敏感,在并發(fā)階段會(huì)占用一部分CPU資源,導(dǎo)致應(yīng)用程序的性能下降;而且它會(huì)產(chǎn)生大量的空間碎片,需要定期進(jìn)行碎片整理。
Garbage
First(G1)收集器:它是一款面向服務(wù)端應(yīng)用的垃圾收集器,主要應(yīng)用于多處理器和大容量?jī)?nèi)存環(huán)境。G1收集器在收集過(guò)程中不會(huì)產(chǎn)生空間碎片,它把堆內(nèi)存劃分成多個(gè)大小相等的獨(dú)立區(qū)域(Region),在進(jìn)行垃圾回收時(shí),會(huì)優(yōu)先回收垃圾最多的區(qū)域。它采用了標(biāo)記
整理(Mark
Compact)和復(fù)制(Copy)算法相結(jié)合的方式。G1收集器可以*地控制停頓時(shí)間,通過(guò)設(shè)置
XX:MaxGCPauseMillis參數(shù)來(lái)指定目標(biāo)停頓時(shí)間,它會(huì)盡量在這個(gè)時(shí)間范圍內(nèi)完成垃圾收集工作。 ### 3. JVM性能調(diào)優(yōu)
性能指標(biāo)
響應(yīng)時(shí)間(Resp*e Time):指從用戶(hù)發(fā)出請(qǐng)求到收到響應(yīng)的時(shí)間間隔。在JVM性能調(diào)優(yōu)中,需要關(guān)注*執(zhí)行時(shí)間、線程阻塞時(shí)間等因素對(duì)響應(yīng)時(shí)間的影響。例如,一個(gè)Web應(yīng)用程序,用戶(hù)點(diǎn)擊一個(gè)按鈕后,等待服務(wù)器返回?cái)?shù)據(jù)的時(shí)間就是響應(yīng)時(shí)間。如果響應(yīng)時(shí)間過(guò)長(zhǎng),用戶(hù)體驗(yàn)就會(huì)很差。
吞吐量(Throughput):是指單位時(shí)間內(nèi)系統(tǒng)處理的請(qǐng)求數(shù)量。對(duì)于一個(gè)處理大量并發(fā)請(qǐng)求的服務(wù)器來(lái)說(shuō),吞吐量是一個(gè)重要的性能指標(biāo)。例如,一個(gè)每秒能夠處理100個(gè)HTTP請(qǐng)求的Web服務(wù)器,其吞吐量就是100個(gè)請(qǐng)求/秒。在調(diào)優(yōu)過(guò)程中,需要平衡吞吐量和響應(yīng)時(shí)間之間的關(guān)系。
內(nèi)存占用(Memory Footprint):指JVM進(jìn)程占用的內(nèi)存大小。包括堆內(nèi)存、棧內(nèi)存、*區(qū)內(nèi)存等各個(gè)部分的占用情況。如果內(nèi)存占用過(guò)高,可能會(huì)導(dǎo)致系統(tǒng)頻繁地進(jìn)行垃圾回收,甚至出現(xiàn)OutOfMemoryError異常。例如,一個(gè)Java應(yīng)用程序在處理大量數(shù)據(jù)時(shí),需要合理配置堆內(nèi)存大小,以避免內(nèi)存溢出。
調(diào)優(yōu)工具
JDK自帶的工具
jc*ole:它是一個(gè)基于JMX(Java Management Extensi*)的可視化監(jiān)控工具,可以用來(lái)監(jiān)控Java應(yīng)用程序的運(yùn)行時(shí)狀態(tài),包括內(nèi)存使用情況、線程狀態(tài)、類(lèi)加載情況等。通過(guò)jc*ole,可以直觀地看到堆內(nèi)存的使用量、各個(gè)線程的狀態(tài)(如運(yùn)行、阻塞、等待等),并且可以檢測(cè)到死鎖等問(wèn)題。
jvisualvm:它是一個(gè)功能更強(qiáng)大的多合一工具,不僅可以監(jiān)控Java應(yīng)用程序的性能,還可以進(jìn)行性能分析和故障排查。它可以生成詳細(xì)的性能報(bào)告,包括*的執(zhí)行時(shí)間、對(duì)象的分配情況等。例如,可以通過(guò)jvisualvm來(lái)分析一個(gè)應(yīng)用程序中哪個(gè)*占用了大量的時(shí)間,從而對(duì)其進(jìn)行優(yōu)化。
第三方工具
YourKit Java Profiler:這是一款商業(yè)的Java性能分析工具,它提供了非常詳細(xì)的性能分析功能,包括CPU使用率分析、內(nèi)存泄漏檢測(cè)、線程性能分析等。它可以幫助開(kāi)發(fā)人員深入了解應(yīng)用程序的性能瓶頸,并且提供了多種可視化的圖表來(lái)展示分析結(jié)果。
調(diào)優(yōu)策略
調(diào)整堆內(nèi)存大小:根據(jù)應(yīng)用程序的實(shí)際需求,合理配置堆內(nèi)存的大小。如果應(yīng)用程序需要處理大量的對(duì)象,并且內(nèi)存占用比較高,可以適當(dāng)增加堆內(nèi)存的大小。但是,過(guò)大的堆內(nèi)存也可能會(huì)導(dǎo)致垃圾回收時(shí)間過(guò)長(zhǎng)。例如,對(duì)于一個(gè)內(nèi)存密集型的應(yīng)用程序,可以通過(guò)設(shè)置
Xmx(*堆內(nèi)存)和
Xms(初始堆內(nèi)存)參數(shù)來(lái)調(diào)整堆內(nèi)存大小。
選擇合適的垃圾收集器:根據(jù)應(yīng)用程序的性能要求和特點(diǎn),選擇合適的垃圾收集器。例如,如果應(yīng)用程序?qū)憫?yīng)時(shí)間比較敏感,要求盡量減少垃圾收集時(shí)的停頓時(shí)間,可以選擇CMS收集器或者G1收集器;如果應(yīng)用程序?qū)ν掏铝恳蟊容^高,對(duì)停頓時(shí)間不是特別敏感,可以選擇Parallel Scavenge收集器。
優(yōu)化代碼層面:在代碼層面進(jìn)行優(yōu)化也是提高JVM性能的重要手段。例如,盡量減少對(duì)象的創(chuàng)建和銷(xiāo)毀,避免在循環(huán)中創(chuàng)建大量的臨時(shí)對(duì)象;合理使用緩存,減少重復(fù)計(jì)算;及時(shí)釋放資源,避免資源泄漏等。 ### 4. JVM字節(jié)碼和指令集
字節(jié)碼(Bytecode)
概念:Java源代碼經(jīng)過(guò)編譯器編譯后生成的中間形式的代碼就是字節(jié)碼。字節(jié)碼是一種二進(jìn)制格式的代碼,它不依賴(lài)于具體的硬件平臺(tái)和操作系統(tǒng),具有良好的可移植性。字節(jié)碼文件(.class文件)的結(jié)構(gòu)是按照J(rèn)VM規(guī)范定義的,它包含了類(lèi)的各種信息,如常量池、類(lèi)的訪問(wèn)標(biāo)志、字段和*的信息等。
示例:以一個(gè)簡(jiǎn)單的Java類(lèi)為例,如`public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } }`,這個(gè)類(lèi)經(jīng)過(guò)編譯后會(huì)生成一個(gè)字節(jié)碼文件。通過(guò)反編譯工具(如javap)可以查看字節(jié)碼的內(nèi)容,字節(jié)碼中包含了很多指令,如`ldc`(將常量池中的常量加載到操作數(shù)棧)、`invokevirtual`(調(diào)用實(shí)例*)等,這些指令是JVM執(zhí)行的最小單位。
指令集(Instruction Set)
概念:JVM指令集是JVM能夠識(shí)別和執(zhí)行的一套指令規(guī)范。它包括操作碼(Opcode)和操作數(shù)(Operand)兩部分。操作碼用于指定要執(zhí)行的操作類(lèi)型,如加載、存儲(chǔ)、運(yùn)算、跳轉(zhuǎn)等;操作數(shù)則是操作的對(duì)象或者數(shù)據(jù)。不同的JVM實(shí)現(xiàn)可能會(huì)對(duì)指令集有一些細(xì)微的差異,但都必須遵循JVM規(guī)范。
示例:在JVM指令集中,`aload_0`指令用于將*個(gè)引用類(lèi)型本地變量加載到操作數(shù)棧頂。如果在一個(gè)*中有一個(gè)本地變量是一個(gè)對(duì)象引用,就可以使用這個(gè)指令將其加載到操作數(shù)棧,以便后續(xù)進(jìn)行*調(diào)用或者其他操作。