電子商務(wù)網(wǎng)站開發(fā)代碼下載百度app
JVM內(nèi)存模型
1、線程私有的數(shù)據(jù)區(qū)
1)、程序計數(shù)器
我們知道,線程是CPU調(diào)度的基本單位。在多線程情況下,當(dāng)線程數(shù)超過CPU數(shù)量或CPU內(nèi)核數(shù)量時,線程之間就要根據(jù) 時間片輪詢搶奪CPU時間資源
。也就是說,在任何一個確定的時刻,一個處理器都只會執(zhí)行一條線程中的指令。因此,為了線程切換后能夠恢復(fù)到正確的執(zhí)行位置,每條線程都需要一個獨立的程序計數(shù)器去記錄其正在執(zhí)行的字節(jié)碼指令地址。
因此,程序計數(shù)器是線程私有的一塊較小的內(nèi)存空間,其可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。如果線程正在執(zhí)行的是一個 Java 方法,計數(shù)器記錄的是正在執(zhí)行的字節(jié)碼指令的地址;如果正在執(zhí)行的是 Native 方法,則計數(shù)器的值為空。
程序計數(shù)器是唯一一個沒有規(guī)定任何 OutOfMemoryError 的區(qū)域。
2)、虛擬機棧
虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型,是線程私有的。每個方法在執(zhí)行的時候都會創(chuàng)建一個棧幀,用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息,而且 每個方法從調(diào)用直至完成的過程,對應(yīng)一個棧幀在虛擬機棧中入棧到出棧的過程。其中,局部變量表主要存放一些基本類型的變量(int, short, long, byte, float, double, boolean, char)和 對象句柄,它們可以是方法參數(shù),也可以是方法的局部變量。
虛擬機棧有兩種異常情況:StackOverflowError 和 OutOfMemoryError。我們知道,一個線程擁有一個自己的棧,這個棧的大小決定了方法調(diào)用的可達(dá)深度(遞歸多少層次,或嵌套調(diào)用多少層其他方法,-Xss 參數(shù)可以設(shè)置虛擬機棧大小),若線程請求的棧深度大于虛擬機允許的深度,則拋出 StackOverFlowError 異常。此外,棧的大小可以是固定的,也可以是動態(tài)擴(kuò)展的,若虛擬機棧可以動態(tài)擴(kuò)展(大多數(shù)虛擬機都可以),但擴(kuò)展時無法申請到足夠的內(nèi)存(比如沒有足夠的內(nèi)存為一個新創(chuàng)建的線程分配??臻g時),則拋出 OutofMemoryError 異常。
3)、本地方法棧
? 本地方法棧與Java虛擬機棧非常相似,也是線程私有的,區(qū)別是虛擬機棧為虛擬機執(zhí)行 Java 方法服務(wù),而本地方法棧為虛擬機執(zhí)行 Native 方法服務(wù)。與虛擬機棧一樣,本地方法棧區(qū)域也會拋出 StackOverflowError 和 OutOfMemoryError 異常。
2、線程共享的數(shù)據(jù)區(qū)
1)、Java 堆
Java 堆的唯一目的就是存放對象實例,幾乎所有的對象實例(和數(shù)組)都在這里分配內(nèi)存。Java堆是線程共享的,類的對象從中分配空間,這些對象通過new、newarray、 anewarray 和 multianewarray 等指令建立,它們不需要程序代碼來顯式的釋放。
由于Java堆唯一目的就是用來存放對象實例,因此其也是垃圾收集器管理的主要區(qū)域,故也稱為稱為 GC堆。從內(nèi)存回收的角度看,由于現(xiàn)在的垃圾收集器基本都采用分代收集算法,所以為了方便垃圾回收J(rèn)ava堆還可以分為 新生代 和 老年代 。新生代用于存放剛創(chuàng)建的對象以及年輕的對象,如果對象一直沒有被回收,生存得足夠長,對象就會被移入老年代。新生代又可進(jìn)一步細(xì)分為 eden、survivorSpace0 和 survivorSpace1。剛創(chuàng)建的對象都放入 eden,s0 和 s1 都至少經(jīng)過一次GC并幸存。如果幸存對象經(jīng)過一定時間仍存在,則進(jìn)入老年代。更多關(guān)于Java堆和分代收集算法的介紹,請移步我的博文《Java 垃圾回收機制概述》。下圖給出了Java堆的結(jié)構(gòu)圖:
注意,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可。而且,Java堆在實現(xiàn)時,既可以是固定大小的,也可以是可拓展的,并且主流虛擬機都是按可擴(kuò)展來實現(xiàn)的(通過-Xmx(最大堆容量) 和 -Xms(最小堆容量)控制)。如果在堆中沒有內(nèi)存完成實例分配,并且堆也無法再拓展時,將會拋出 OutOfMemoryError 異常。
2)、方法區(qū)
方法區(qū)與Java堆一樣,也是線程共享的并且不需要連續(xù)的內(nèi)存,其用于存儲已被虛擬機加載的 類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。方法區(qū)通常和永久區(qū)(Perm)關(guān)聯(lián)在一起,但永久代與方法區(qū)不是一個概念,只是有的虛擬機用永久代來實現(xiàn)方法區(qū),這樣就可以用永久代GC來管理方法區(qū),省去專門內(nèi)存管理的工作。根據(jù)Java虛擬機規(guī)范的規(guī)定,當(dāng)方法區(qū)無法滿足內(nèi)存分配的需求時,將拋出 OutOfMemoryError 異常。
(1)、運行時常量池
運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分,用于存放編譯期生成的各種 字面量 和 符號引用。其中,字面量比較接近Java語言層次的常量概念,如文本字符串、被聲明為final的常量值等;而符號引用則屬于編譯原理方面的概念,包括以下三類常量:類和接口的全限定名、字段的名稱和描述符 和 方法的名稱和描述符。因為運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分,那么當(dāng)常量池?zé)o法再申請到內(nèi)存時也會拋出 OutOfMemoryError 異常。
運行時常量池相對于Class文件常量池的一個重要特征是具備動態(tài)性。Java語言并不要求常量一定只有編譯期才能產(chǎn)生,運行期間也可能將新的常量放入池中,比如字符串的手動入池方法intern()。
3)、Java堆 與 方法區(qū)的區(qū)別
Java堆是 Java代碼可及的內(nèi)存,是留給開發(fā)人員使用的;而非堆(Non-Heap)是JVM留給自己用的,所以方法區(qū)、JVM內(nèi)部處理或優(yōu)化所需的內(nèi)存 (如JIT編譯后的代碼緩存)、每個類結(jié)構(gòu) (如運行時常量池、字段和方法數(shù)據(jù))以及方法和構(gòu)造方法的代碼都在非堆內(nèi)存中。
說一下 JVM 有哪些垃圾回收器?
Serial:最早的單線程串行垃圾回收器。 Serial Old:Serial 垃圾回收器的老年版本,同樣也是單線程的,可以作為 CMS 垃圾回收器的備選預(yù)案。 ParNew:是 Serial 的多線程版本。 Parallel 和 ParNew 收集器類似是多線程的,但 Parallel 是吞吐量優(yōu)先的收集器,可以犧牲等待時間換取系統(tǒng)的吞吐量。 Parallel Old 是 Parallel 老生代版本,Parallel 使用的是復(fù)制的內(nèi)存回收算法,Parallel Old 使用的是標(biāo)記-整理的內(nèi)存回收算法。 CMS:一種以獲得最短停頓時間為目標(biāo)的收集器,非常適用 B/S 系統(tǒng)。 G1:一種兼顧吞吐量和停頓時間的 GC 實現(xiàn),是 JDK 9 以后的默認(rèn) GC 選項。
詳細(xì)介紹一下 CMS 垃圾回收器?
??
CMS 是英文 Concurrent Mark-Sweep 的簡稱,是以犧牲吞吐量為代價來獲得最短回收停頓時間的垃圾回收器。對于要求服務(wù)器響應(yīng)速度的應(yīng)用上,這種垃圾回收器非常適合。在啟動 JVM 的參數(shù)加上“-XX:+UseConcMarkSweepGC”來指定使用 CMS 垃圾回收器。 CMS 使用的是標(biāo)記-清除的算法實現(xiàn)的,所以在 gc 的時候回產(chǎn)生大量的內(nèi)存碎片,當(dāng)剩余內(nèi)存不能滿足程序運行要求時,系統(tǒng)將會出現(xiàn) Concurrent Mode Failure,臨時 CMS 會采用 Serial Old 回收器進(jìn)行垃圾清除,此時的性能將會被降低。
新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么區(qū)別?
新生代回收器:Serial、ParNew、Parallel Scavenge 老年代回收器:Serial Old、Parallel Old、CMS 整堆回收器:G1新生代垃圾回收器一般采用的是復(fù)制算法,復(fù)制算法的優(yōu)點是效率高,缺點是內(nèi)存利用率低;老年代回收器一般采用的是標(biāo)記-整理的算法進(jìn)行垃圾回收。
簡述分代垃圾回收器是怎么工作的?
分代回收器有兩個分區(qū):老生代和新生代,新生代默認(rèn)的空間占比總空間的 1/3,老生代的默認(rèn)占比是 2/3。 新生代使用的是復(fù)制算法,新生代里有 3 個分區(qū):Eden、To Survivor、From Survivor,它們的默認(rèn)占比是 8:1:1,它的執(zhí)行流程如下: 把 Eden + From Survivor 存活的對象放入 To Survivor 區(qū); 清空 Eden 和 From Survivor 分區(qū); From Survivor 和 To Survivor 分區(qū)交換,From Survivor 變 To Survivor,To Survivor 變 From Survivor。 每次在 From Survivor 到 To Survivor 移動時都存活的對象,年齡就 +1,當(dāng)年齡到達(dá) 15(默認(rèn)配置是 15)時,升級為老生代。大對象也會直接進(jìn)入老生代。 老生代當(dāng)空間占用到達(dá)某個值之后就會觸發(fā)全局垃圾收回,一般使用標(biāo)記整理的執(zhí)行算法。以上這些循環(huán)往復(fù)就構(gòu)成了整個分代垃圾回收的整體執(zhí)行流程。
說一下 JVM 調(diào)優(yōu)的工具?
JDK 自帶了很多監(jiān)控工具,都位于 JDK 的 bin 目錄下,其中最常用的是 jconsole 和 jvisualvm 這兩款視圖監(jiān)控工具。 jconsole:用于對 JVM 中的內(nèi)存、線程和類等進(jìn)行監(jiān)控; jvisualvm:JDK 自帶的全能分析工具,可以分析:內(nèi)存快照、線程快照、程序死鎖、監(jiān)控內(nèi)存的變化、gc 變化等。
常用的 JVM 調(diào)優(yōu)的參數(shù)都有哪些?
-Xms2g:初始化推大小為 2g;
-Xmx2g:堆最大內(nèi)存為 2g;
-XX:NewRatio=4:設(shè)置年輕的和老年代的內(nèi)存比例為 1:4;
-XX:SurvivorRatio=8:設(shè)置新生代 Eden 和 Survivor 比例為 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;
-XX:+PrintGC:開啟打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 詳細(xì)信息。