jvm 架構及優(yōu)化
寫在前面
先看一下知乎上一個有趣的問題:
Java工程師面試的時候,總是提問一些jvm如何優(yōu)化的問題,這些真的在開發(fā)中有用嗎,工作七年了項目中從來沒有用過,并且我獲得過多次優(yōu)秀員工,望做過優(yōu)化的大牛解答?
答一:
JVM優(yōu)化肯定是有用的,可能只是題主沒有遇到過這方面的需求。比如一些GC機制會引起JVM的Stop The World,也就是所有工作線程都會停下來等待GC完成。對于一些對延遲比較敏感的程序來說,這一停頓達到一百甚至是幾十毫秒的時候就是難以接受的。為了解決這類問題,就需要對JVM的參數(shù)做適當?shù)恼{整。比如調整堆的大小,選擇合適的垃圾回收器,控制對象晉升老年代的速度等等。
作者:謝知恒
鏈接:https://www.zhihu.com/question/40913700/answer/88862720
來源:知乎
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。
答二:
對于樓主這種情況也是可以理解的,以前我在傳統(tǒng)行業(yè)做行業(yè)軟件的時候也是對jvm一無所知,覺得一點用沒有,面試問只是裝逼而已。后來踏入互聯(lián)網(wǎng)行業(yè)才明白,這是必須掌握的,而且經(jīng)常會用,遇到tp99間斷性的提高,這種情況首先就會去看是否是fullGC引起的,還有就是內(nèi)存溢出之類的問題,不了解jvm怎么去解決這類問題。當然傳統(tǒng)行業(yè)軟件,用戶量少可能不會去監(jiān)控性能,遇到內(nèi)存溢出可能也就重啟解決了,不了了之,下次再重啟。然而,對于java開發(fā)人員來說,jvm就是一座商廈,我們在里面開店,連消防栓在哪里都不知道,哪天著火了,應急樓梯也不知道在哪里,只能坐著哭?所以還是建議樓主了解一下jvm,推薦看《深入理解java虛擬機》
作者:周易
鏈接:https://www.zhihu.com/question/40913700/answer/138011891
來源:知乎
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。
從上面兩段回答中可以看出來,熟悉jvm架構和調優(yōu)意義在于出了性能問題后,多給程序員留了一條退路。
jvm 架構

首先先附上一張jvm架構圖。
上圖中主要由幾個部分組成:
- 類加載器(ClassLoader):在JVM啟動時或者在類運行時將需要的class加載到JVM中。每當運行一個
java 程序時,就會首先動態(tài)創(chuàng)建相應的jvm instance(啟動三個程序,就會有三個jvm實例,這點和golang runtime非常類似),然后將該程序的所有類文件通過類加載器加載到jvm中。 - 執(zhí)行引擎:負責執(zhí)行class文件中包含的字節(jié)碼指令.執(zhí)行引擎是java 虛擬機模擬運行的運算引擎,基本起到了翻譯的作用。
有兩種翻譯的方式:- 一句話一句話翻譯。也就是解釋執(zhí)行。將程序計算器中指向的待執(zhí)行的java字節(jié)碼翻譯為cpu可以運行的機器指令。
- 一次性翻譯。即編譯執(zhí)行。通過JIT(just in time)一次性將所有的字節(jié)碼翻譯完成。
- 內(nèi)存區(qū)(也叫運行時數(shù)據(jù)區(qū)):是在JVM運行的時候操作所分配的內(nèi)存區(qū)。從上圖中可以看出,主要分為五個部分。
- 本地庫接口,本地方法庫:操作系統(tǒng)所有,用于處理jvm的native code和通過jit編譯后的本地代碼。
jvm 執(zhí)行引擎
關于jvm的執(zhí)行引擎,它并不是真正軟件模擬實現(xiàn)了cpu,最終所有計算機上的運算都是由cpu執(zhí)行機器指令來處理。
有個問題?jvm執(zhí)行引擎和本地代碼關系如何?本地代碼還需要過jvm執(zhí)行引擎嗎?
猜測答案:應該是需要過的,jvm封裝了所有的代碼,管理著程序執(zhí)行的結果。
關于執(zhí)行引擎采用哪種方式來運行,經(jīng)驗如下:
用JIT編譯器來編譯代碼所花的時間要比用解釋器去一條條解釋執(zhí)行花的時間要多。因此,如果代碼只被執(zhí)行一次的話,那么最好還是解釋執(zhí)行而不是編譯后再執(zhí)行。因此,內(nèi)置了JIT編譯器的JVM都會檢查方法的執(zhí)行頻率,如果一個方法的執(zhí)行頻率超過一個特定的值的話,那么這個方法就會被編譯成本地代碼。
jvm 內(nèi)存
從jvm 架構可以看出,jvm內(nèi)存主要分為五部分:
- 方法區(qū)(Method Area):用于存儲類結構信息的地方,包括常量池、靜態(tài)變量、構造函數(shù)等。雖然JVM規(guī)范把方法區(qū)描述為堆的一個邏輯部分, 但它卻有個別名non-heap(非堆),所以大家不要搞混淆了。方法區(qū)還包含一個運行時常量池。
- java堆(Heap):存儲java實例或者對象的地方。這塊是GC的主要區(qū)域。從存儲的內(nèi)容我們可以很容易知道,方法區(qū)和堆是被所有java線程共享的。
- java棧(Stack):java棧總是和線程關聯(lián)在一起,每當創(chuàng)建一個線程時,JVM就會為這個線程創(chuàng)建一個對應的java棧。在這個java棧中又會包含多個棧幀,每運行一個方法就創(chuàng)建一個棧幀,用于存儲局部變量表、操作棧、方法返回值等。每一個方法從調用直至執(zhí)行完成的過程,就對應一個棧幀在java棧中入棧到出棧的過程。所以java棧是現(xiàn)成私有的。
- 程序計數(shù)器(PC Register):用于保存當前線程執(zhí)行的內(nèi)存地址。由于JVM程序是多線程執(zhí)行的(線程輪流切換),所以為了保證線程切換回來后,還能恢復到原先狀態(tài),就需要一個獨立的計數(shù)器,記錄之前中斷的地方,可見程序計數(shù)器也是線程私有的。
- 本地方法棧(Native Method Stack):和java棧的作用差不多,只不過是為JVM使用到的native方法服務的。
java 堆存放對象實例。
java 棧存放普通的變量及其它類型的信息。
這樣的好處是對象通常占有內(nèi)存很大,但個數(shù)少。棧里存放的東西個數(shù)多,占用內(nèi)存少。對象是垃圾回收的主戰(zhàn)場。
所以要分出棧和堆來。
jvm調優(yōu)
傳統(tǒng)的軟件開發(fā)過程中不需要考慮到jvm調優(yōu)的內(nèi)容,主要出于以下的幾點考慮:
- 性能不是生命線。
- 系統(tǒng)出問題后可以采取其它的方式進行處理,例如停機,停機后再恢復,受影響不大。
而在互聯(lián)網(wǎng)領域,上述兩個問題就變?yōu)楸容^嚴重的問題。
為了留住用戶,響應時間和吞吐量就會成為必須去解決的問題。而jvm性能瓶頸主要在與jvm自帶的垃圾回收機制。
內(nèi)存分配機制
靜態(tài)內(nèi)存&動態(tài)內(nèi)存
Java的內(nèi)存分配原理與C/C++不同,C/C++每次申請內(nèi)存時都要malloc進行系統(tǒng)調用,而系統(tǒng)調用發(fā)生在內(nèi)核空間,每次都要中斷進行切換,這需要一定的開銷,
而Java虛擬機是先一次性分配一塊較大的空間,然后每次new時都在該空間上進行分配和釋放,減少了系統(tǒng)調用的次數(shù),節(jié)省了一定的開銷,這有點類似于內(nèi)存池的概念;二是有了這塊空間過后,如何進行分配和回收就跟GC機制有關了。
java一般內(nèi)存申請有兩種:
- 靜態(tài)內(nèi)存.編譯時就能夠確定的內(nèi)存就是靜態(tài)內(nèi)存,即內(nèi)存是固定的,系統(tǒng)一次性分配,比如int類型變量;java棧、程序計數(shù)器、本地方法棧都是線程私有的,線程生就生,線程滅就滅,棧中的棧幀隨著方法的結束也會撤銷,內(nèi)存自然就跟著回收了。我們不需要管的。
- 動態(tài)內(nèi)存。動態(tài)內(nèi)存分配就是在程序執(zhí)行時才知道要分配的存儲空間大小,比如java對象的內(nèi)存空間。所以這幾個區(qū)域的內(nèi)存分配與回收是確定的,但是java堆和方法區(qū)則不一樣,我們只有在程序運行期間才知道會創(chuàng)建哪些對象,所以這部分內(nèi)存的分配和回收都是動態(tài)的。一般我們所說的垃圾回收也是針對的這一部分。
總之Stack的內(nèi)存管理是順序分配的,而且定長,不存在內(nèi)存回收問題;而Heap 則是為java對象的實例隨機分配內(nèi)存,不定長度,所以存在內(nèi)存分配和回收的問題;
新生代&老生代
在 Java 中,堆被劃分成兩個不同的區(qū)域:
- 新生代 ( Young )。 生命周期較短。
- 老年代 ( Old )。生命周期較長。
- 永久代。很少被討論。
新生代 ( Young ) 又被劃分為三個區(qū)域:
- Eden
- From Survivor
- To Survivor。
GC機制簡述
JVM 使用的GC算法是什么?
分代收集:即將內(nèi)存分為幾個區(qū)域,將不同生命周期的對象放在不同區(qū)域里。GC根據(jù)用途來說有以下三種:
GC(或Minor GC):收集 生命周期短的區(qū)域(Young area)。Minor GC會把Eden中的所有活的對象都移到Survivor區(qū)域中,如果Survivor區(qū)中放不下,那么剩下的活的對象就被移到Old generation 中。
Full GC (或Major GC):基于標記-清除算法,收集生命周期短的區(qū)域(Young area)和生命周期比較長的區(qū)域(Old area)對整個堆進行垃圾收集。
Major GC :清理永久代。
GC 效率也會比較高,我們要盡量減少 Full GC 的次數(shù)。
在minor Gc 與Full Gc執(zhí)行機制上,都提供了三種選擇:
- 串行GC(SerialGC)
- 并行回收GC(ParallelScavenge
- 并行GC(ParNew)
可以根據(jù)具體的需要選擇相應的GC。
GC的調整是在吞吐量和響應時間上做一個平衡。
jvm調優(yōu)簡述與工具
怎樣調優(yōu)?
有何工具?
Jvm調優(yōu)工具有以下幾種:
- Jconsole : jdk自帶,功能簡單,但是可以在系統(tǒng)有一定負荷的情況下使用。對垃圾回收算法有很詳細的跟蹤。詳細說明參考這里
- JProfiler:商業(yè)軟件,需要付費。功能強大。詳細說明參考這里
- VisualVM:JDK自帶,功能強大,與JProfiler類似。推薦。
關于調優(yōu)這塊在實際業(yè)務中碰到這類的問題再進行具體分析。