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