JavaGuide知識(shí)點(diǎn)整理——帶你認(rèn)識(shí)JVM

本篇文章是一個(gè)自己理解的展示,此文旨在提及而不深究。如果措辭或理解有問(wèn)題歡迎指出。

JVM的基本介紹

JVM是java Virtual Machine的縮寫,它是一個(gè)虛構(gòu)出來(lái)的計(jì)算機(jī),一種規(guī)范。通過(guò)在實(shí)際的計(jì)算機(jī)上仿真模擬各類計(jì)算機(jī)功能實(shí)現(xiàn)。
直白說(shuō):jvm類似于一臺(tái)小電腦運(yùn)行在windows或者linux操作系統(tǒng)環(huán)境下即可。它直接和操作系統(tǒng)進(jìn)行交互,與硬件不直接交互,而操作系統(tǒng)可以幫助我們完成和硬件進(jìn)行交互的工作。


image.png

java文件是如何被運(yùn)行的

比如我們現(xiàn)在寫了一個(gè)helloWorld.java,那么這個(gè)helloWorld.java拋開(kāi)所有東西不談,就類似與一個(gè)文件文件,只不過(guò)這個(gè)文件都是英文且有一定的縮進(jìn)。
我們的jvm是不認(rèn)識(shí)文本文件的,所以它需要一個(gè)編譯。讓其成為一個(gè)它會(huì)讀二進(jìn)制文件的helloWorld.class

  1. 類加載器
    如果JVM想要執(zhí)行這個(gè).class文件,我們需要將其裝進(jìn)一個(gè)類加載器中,它就像一個(gè)搬運(yùn)工一樣,會(huì)把所有的.class文件全部搬進(jìn)JVM里面來(lái)。
    image.png
  2. 方法區(qū)
    方法區(qū)是用于存放類似于元數(shù)據(jù)信息方面的數(shù)據(jù)的,比如類信息,常量,靜態(tài)變量,編譯后代碼等。
    類加載器將.class文件搬過(guò)來(lái)就是先丟到這一塊上。

  3. 堆主要放了一些存儲(chǔ)的數(shù)據(jù),比如對(duì)象實(shí)例,數(shù)組等,它和方法區(qū)都同屬于線程共享區(qū)域,也就是說(shuō)它們都是線程不安全的。

  4. 棧是我們的代碼運(yùn)行空間,我們編寫的每一個(gè)方法都會(huì)放到棧里面運(yùn)行。我們會(huì)聽(tīng)說(shuō)過(guò)本地方法?;虮镜胤椒ń涌谶@兩個(gè)名詞,不過(guò)我們基本上不會(huì)涉及到這兩塊的內(nèi)容,因?yàn)樗鼈z底層是使用c來(lái)進(jìn)行工作的,和java沒(méi)多大的關(guān)系。
  5. 程序計(jì)數(shù)器
    主要就是完成一個(gè)加載工作,類似于一個(gè)指針一樣。指向下一行我們需要執(zhí)行的代碼。和棧一樣,都是線程獨(dú)有的,就是說(shuō)每一個(gè)線程都會(huì)有自己對(duì)應(yīng)的一塊區(qū)域而不會(huì)存在并發(fā)和多線程的問(wèn)題。
    image.png

    小總結(jié)
    1. java文件經(jīng)過(guò)編譯后變成.class字節(jié)碼文件
    2. 字節(jié)碼文件通過(guò)類加載器被搬運(yùn)到j(luò)vm虛擬機(jī)中
    3. 虛擬機(jī)主要有五大塊: 方法區(qū)和堆都是線程共享區(qū)域,有線程安全問(wèn)題。棧,本地方法棧和程序計(jì)數(shù)器都是獨(dú)享區(qū)域,不存在線程安全問(wèn)題。而jvm調(diào)優(yōu)主要就是圍繞堆,棧兩大塊進(jìn)行。

簡(jiǎn)單的代碼例子

下面是簡(jiǎn)單的測(cè)試代碼:


測(cè)試代碼

執(zhí)行main方法的步驟如下:

  1. 編譯好Main.java后得到Main.class,執(zhí)行Main.class系統(tǒng)會(huì)啟動(dòng)一個(gè)JVM進(jìn)程。從classpath路徑中找到一個(gè)名為Main.class的二進(jìn)制文件。將Main的類信息加載到運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),這個(gè)過(guò)程叫做Main類的加載
  2. JVM找到Main的主程序入庫(kù),執(zhí)行main方法。
  3. 這個(gè)main方法中的第一條語(yǔ)句是創(chuàng)建一個(gè)User對(duì)象,但是這個(gè)時(shí)候方法區(qū)中是沒(méi)有User類的信息的,所以JVM馬上加載User類。把User類的信息放到方法區(qū)中。
  4. 加載完User類后,JVM在堆中為一個(gè)新的User實(shí)例分配內(nèi)存,然后調(diào)用構(gòu)造函數(shù)初始化User實(shí)例,這個(gè)User實(shí)例持有指向方法區(qū)中的User類的類型信息的引用。
  5. 執(zhí)行user.sayName()時(shí),JVM根據(jù)user的引用找到user對(duì)象,然后根據(jù)user對(duì)象持有的引用定位到方法區(qū)中user類的類型信息的方法表,獲得sayName的字節(jié)碼地址。
  6. 執(zhí)行sayName()

其實(shí)也不用管太多,只需要知道對(duì)象實(shí)例初始化時(shí)會(huì)去方法區(qū)中找類的信息,完成后再去棧那里運(yùn)行方法,找方法就在方法表中找。

類加載器的介紹

之前也提到了它是負(fù)責(zé)加載.class文件的,它們?cè)谖募_(kāi)頭會(huì)有特定的文件標(biāo)示,將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中,并將這些內(nèi)容轉(zhuǎn)換成方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),并且ClassLoader只負(fù)責(zé)class文件的加載,而能否運(yùn)行則由Execution Engine來(lái)決定。

類加載器的流程

從類被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到釋放內(nèi)存總共有七個(gè)步驟:加載,驗(yàn)證,準(zhǔn)備,解析,初始化,使用,卸載。其中驗(yàn)證,準(zhǔn)備,解析三個(gè)部分統(tǒng)稱為鏈接。

  1. 加載

    1. 將class文件加載到內(nèi)存
    2. 將靜態(tài)數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化成方法區(qū)中運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
    3. 在堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象作為數(shù)據(jù)訪問(wèn)的入口
  2. 鏈接

    1. 驗(yàn)證:確保加載的類符合JVM規(guī)范和安全,保證被校驗(yàn)類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)的事件,其實(shí)就是一個(gè)安全檢查。
    2. 準(zhǔn)備:為static變量在方法區(qū)中分配內(nèi)存空間,設(shè)置變量的初始值,注意準(zhǔn)備階段只設(shè)置類中的靜態(tài)變量,不包括實(shí)例變量。實(shí)例變量是對(duì)象初始化時(shí)賦值的。
    3. 解析:虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程
  3. 初始化
    初始化其實(shí)就是執(zhí)行類構(gòu)造器方法的<clinit>()的過(guò)程,而且要保證執(zhí)行前父類的<clinit>()方法執(zhí)行完畢,這個(gè)方法由編譯器收集,順序執(zhí)行所有類變量顯示初始化和靜態(tài)代碼塊中語(yǔ)句。此時(shí)準(zhǔn)備階段時(shí)的默認(rèn)值會(huì)變?yōu)閷?shí)際值。由于執(zhí)行順序緣故,初始化階段類變量如果在靜態(tài)代碼塊中又進(jìn)行了修改,會(huì)覆蓋類變量的顯示初始化,最終值會(huì)是靜態(tài)代碼塊中的賦值。

  4. 卸載
    GC將無(wú)用對(duì)象從內(nèi)存中卸載

類加載器的加載順序

加載一個(gè)Class類的順序也是有優(yōu)先級(jí)的。類加載器從最底層開(kāi)始網(wǎng)上的順序是這樣的:

  1. BootStrap ClassLoader:rt.jar
  2. Extension ClassLoader:加載擴(kuò)展的jar包
  3. App ClassLoader:指定的classpath下面的jar包
  4. Custom ClassLoader:自定義的類加載器

雙親委派機(jī)制

當(dāng)一個(gè)類收到了加載請(qǐng)求時(shí),它是不會(huì)先自己去嘗試加載的,而是委派給父類去完成。比如我現(xiàn)在要new 一個(gè)User,這個(gè)User是我們自定義的類,如果我們要加載它,就會(huì)先委派App ClassLoader.只有當(dāng)父類加載器都反饋?zhàn)约簾o(wú)法完成這個(gè)請(qǐng)求(也就是父類加載器都沒(méi)有找到加載所需的Class)時(shí),子類加載器才會(huì)自行嘗試加載。
這樣做的好處是,加載位于rt.jar包中的類時(shí),不管是哪個(gè)加載器加載,最終都會(huì)委托到BootStrap ClassLoader進(jìn)行加載,這樣保證了使用不同的類加載器得到的都是同一個(gè)結(jié)果。
其實(shí)這個(gè)也是一個(gè)隔離的作用,避免我們的代碼影響JDK代碼,比如我們現(xiàn)在自己定義一個(gè)java.lang.String類:

package java.lang;
public class String {
    public static void main(String[] args) {
        System.out.println();
    }
}

嘗試運(yùn)行當(dāng)前類的main函數(shù)的時(shí)候,我們的代碼肯定會(huì)報(bào)錯(cuò)。這是因?yàn)樵诩虞d的時(shí)候其實(shí)找到了rt.jar中的java.lang.String,然而發(fā)現(xiàn)這個(gè)里面并沒(méi)有main方法。

運(yùn)行時(shí)區(qū)域

本地方法棧和程序計(jì)數(shù)器

比如說(shuō)我們現(xiàn)在點(diǎn)開(kāi)Thread類的源碼,會(huì)看到他的start()方法帶有一個(gè)native關(guān)鍵字修飾,而且不存在方法體,這種用native修飾的方法就是本地方法,這是使用C來(lái)實(shí)現(xiàn)的,然后一般這些方法都會(huì)放到一個(gè)叫做本地方法棧的區(qū)域。
程序計(jì)數(shù)器其實(shí)就是一個(gè)指針,它指向了我們程序中下一句需要執(zhí)行的命令,它也是內(nèi)存區(qū)域中唯一一個(gè)不會(huì)出現(xiàn)OOM的區(qū)域,而且占用內(nèi)存空間小到基本可以忽略不計(jì)。這個(gè)內(nèi)存僅代表當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器,字節(jié)碼解析器通過(guò)改變這個(gè)計(jì)數(shù)器的值選取下一條需要執(zhí)行的字節(jié)碼指令。
如果執(zhí)行的是native方法,那么這個(gè)指針就不工作了。

方法區(qū)

方法區(qū)主要的作用是存放類的元數(shù)據(jù)信息,常量和靜態(tài)變量等。當(dāng)它存儲(chǔ)的信息過(guò)大時(shí),會(huì)在無(wú)法滿足內(nèi)存分配時(shí)報(bào)錯(cuò)(jdk8以后方法區(qū)的實(shí)現(xiàn)是元空間了,受物理內(nèi)存大小的限制)

虛擬機(jī)棧和虛擬機(jī)堆

一句話:棧管運(yùn)行,堆管存儲(chǔ)。虛擬機(jī)棧負(fù)責(zé)運(yùn)行代碼,虛擬機(jī)堆負(fù)責(zé)存儲(chǔ)數(shù)據(jù)。
虛擬機(jī)棧的概念
它是java方法執(zhí)行的內(nèi)存模型。里面會(huì)對(duì)局部變量,動(dòng)態(tài)鏈表,方法出口,棧的操作(入棧和出棧)進(jìn)行存儲(chǔ),且線程私有。同時(shí)如果我們聽(tīng)到局部變量表,那也是在說(shuō)虛擬機(jī)棧。
虛擬機(jī)棧存在的異常
如果線程請(qǐng)求的棧深度大于虛擬機(jī)棧的最大深度,就會(huì)報(bào)StackOverflowError(死遞歸會(huì)導(dǎo)致),java虛擬機(jī)可以動(dòng)態(tài)擴(kuò)展,但是隨著擴(kuò)展會(huì)不斷申請(qǐng)內(nèi)存,當(dāng)無(wú)法申請(qǐng)足夠的內(nèi)存時(shí)就會(huì)報(bào)錯(cuò)OutOfMemoryError。
虛擬機(jī)棧的生命周期
對(duì)于棧來(lái)說(shuō),不存在垃圾回收。只要程序運(yùn)行結(jié)束,棧的空間自然就會(huì)釋放了。棧的生命周期和所處的線程是一致的。
注意:八種基本類型的變量+對(duì)象的引用變量+實(shí)例方法都是在棧里面分配內(nèi)存。
虛擬機(jī)棧的執(zhí)行
我們經(jīng)常說(shuō)的棧幀數(shù)據(jù),說(shuō)白了在jvm中叫做棧幀,在java里其實(shí)就是方法,它也是存在棧中的。
棧中的數(shù)據(jù)都是以棧幀的格式存在,它是一個(gè)關(guān)于方法和運(yùn)行期數(shù)據(jù)的數(shù)據(jù)集/比如我們執(zhí)行一個(gè)方法a,就會(huì)對(duì)應(yīng)產(chǎn)生一個(gè)棧幀A1,然后A1會(huì)被壓入棧中。同理方法b會(huì)有一個(gè)B1,方法c會(huì)有一個(gè)C1,等這個(gè)線程執(zhí)行完畢后,會(huì)先彈出C1,然后B1,A1這種,它是一個(gè)先進(jìn)后出的原則。
局部變量的復(fù)用
局部變量表用于存放方法參數(shù)和方法內(nèi)部所定義的局部變量。它的容量是以slot為最小單位,一個(gè)slot可以存放32位以內(nèi)的數(shù)據(jù)類型。
虛擬機(jī)通過(guò)索引定位的方式使用局部變量表。范圍為0-slot的數(shù)量。方法中的參數(shù)就會(huì)按照一定順序排列在這個(gè)局部變量表里,至于怎么排序的我們可以先不關(guān)心。
而為了節(jié)省棧幀空間,這些slot是可以復(fù)用的。當(dāng)方法執(zhí)行位置超過(guò)了某個(gè)變量,那么這個(gè)變量的slot可以被其它變量復(fù)用。當(dāng)然如果需要復(fù)用那么我們的垃圾回收自然就不會(huì)動(dòng)這些內(nèi)存。
虛擬機(jī)堆的概念
JVM內(nèi)存會(huì)劃分為堆內(nèi)存和非堆內(nèi)存。堆內(nèi)存中會(huì)劃分為年輕代和老年代。而非堆內(nèi)存則為永久代。年輕代又分為Eden和Survivor區(qū)。Survivor區(qū)分為Form和To。這兩個(gè)總會(huì)有一個(gè)是空的。Eden,From,To默認(rèn)比例8:1:1.當(dāng)然這個(gè)比例可以調(diào)整。
堆內(nèi)存中存放的是對(duì)象,垃圾收集就是收集這些對(duì)象然后交給GC算法進(jìn)行回收。非堆內(nèi)存我們已經(jīng)說(shuō)過(guò)了,就是方法區(qū)。 1.7之前叫做永久代,1.8后叫做元空間。最大的區(qū)別就是元空間不存在與JVM中,使用的是本地內(nèi)存。元空間有兩個(gè)參數(shù):

MetaspaceSize:初始化元空間大小,控制發(fā)生GC
MaxMetaspaceSize:限制元空間大小上限,防止占用過(guò)多物理內(nèi)存。

Eden年輕代的介紹
我們new一個(gè)對(duì)象后,一般會(huì)先放在Eden劃分出來(lái)的一塊作為存儲(chǔ)空間的內(nèi)存。但是我們知道堆內(nèi)存是線程共享的,所以有可能出現(xiàn)兩個(gè)對(duì)象共用一個(gè)內(nèi)存的情況。這里jvm的處理是每個(gè)線程都會(huì)預(yù)先申請(qǐng)好一塊連續(xù)的內(nèi)存空間并規(guī)定了對(duì)象的存放的位置,而如果空間不足則會(huì)再申請(qǐng)多塊內(nèi)存空間。這個(gè)操作稱為TLAB。
當(dāng)Eden空間滿了以后,會(huì)觸發(fā)Minor GC(就是在年輕代發(fā)現(xiàn)的GC)。存活下來(lái)的對(duì)象移動(dòng)到s0區(qū),s0區(qū)滿了以后出發(fā)Minor GC,將存活的對(duì)象移動(dòng)到s1,并且s0和s1指針交換。 總保證有一個(gè)s區(qū)是空的。
經(jīng)過(guò)多次GC還存活的對(duì)象(默認(rèn)15,因?yàn)閔otspot記錄年齡的空間只有4位,最多只能記錄到15)會(huì)移動(dòng)到老年代。老年代是存儲(chǔ)長(zhǎng)期存活的對(duì)象的,老年代滿了會(huì)觸發(fā)full GC,期間停止所有線程等待GC完成,所以對(duì)于響應(yīng)要求高的應(yīng)用應(yīng)該盡量減少Full GC而避免響應(yīng)超時(shí)。
當(dāng)老年代執(zhí)行了Full GC還是空間不足,會(huì)產(chǎn)生OOM,這個(gè)就是虛擬機(jī)中堆內(nèi)存不足。堆內(nèi)存可以通過(guò)-Xms,-Xmx調(diào)整。

如何判斷一個(gè)對(duì)象需要被干掉

image.png

上圖中程序計(jì)數(shù)器,虛擬機(jī)棧,本地方法棧三個(gè)區(qū)域隨著線程的生存而生存。內(nèi)存分配和回收都是確定的,隨著線程的結(jié)束內(nèi)存自然就被回收了,因此不需要考慮垃圾回收的問(wèn)題。而java堆和方法區(qū)則不一樣,各線程共享,內(nèi)存的分配和回收都是動(dòng)態(tài)的。因此垃圾收集器所關(guān)注的都是堆和方法這部分內(nèi)存。
在進(jìn)行回收前要判斷哪些對(duì)象還存活,哪些已經(jīng)死去。下面介紹兩個(gè)基礎(chǔ)的計(jì)算方法:

  1. 引用計(jì)數(shù)器:給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每次引用這個(gè)對(duì)象時(shí)計(jì)數(shù)器加1,引用失效時(shí)減1,計(jì)數(shù)器為0的就是不會(huì)再次使用。不過(guò)這些情況無(wú)法解決循環(huán)依賴。
  2. 可達(dá)性分析:這是一種類似于二叉樹(shù)的實(shí)現(xiàn),將一系列GC ROOTS作為起始的存活對(duì)象集,從這個(gè)節(jié)點(diǎn)往下搜索,搜索所走過(guò)的路徑成為引用鏈,把能被該集合引用到的對(duì)象加入到集合中。當(dāng)一個(gè)對(duì)象到GC ROOT沒(méi)有任何引用鏈時(shí),說(shuō)明對(duì)象是不可用的。
    這種方法的優(yōu)點(diǎn)是能夠解決循環(huán)依賴的問(wèn)題,缺點(diǎn)是耗費(fèi)大量資源和時(shí)間。它的分析過(guò)程引用關(guān)系不能發(fā)生變化,所以需要停止所有進(jìn)程。

如何宣告一個(gè)對(duì)象真正死亡
首先必須要提到一個(gè)finalize()的方法。
這個(gè)方法是Object類的一個(gè)方法,一個(gè)對(duì)象的finalize()方法只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次。經(jīng)過(guò)finalize()方法逃脫死亡的對(duì)象,第二次不會(huì)再調(diào)用。
判斷一個(gè)對(duì)象的死亡至少需要經(jīng)過(guò)兩次標(biāo)記

  1. 如果對(duì)象進(jìn)行可達(dá)性分析后沒(méi)有與GC ROOT相連的引用鏈,那么它將會(huì)第一次標(biāo)記并且進(jìn)行一次篩選。判斷的條件是決定這個(gè)對(duì)象是否有必要執(zhí)行finalize()方法。如果對(duì)象有必要執(zhí)行的話則放入F-Queue隊(duì)列中。
  2. GC對(duì)F-Queue隊(duì)列中的對(duì)象進(jìn)行二次標(biāo)記,如果對(duì)象在finalize()方法中重新與引用鏈上的任何一個(gè)對(duì)象建立了關(guān)聯(lián),那么二次標(biāo)記時(shí)則會(huì)將它移出已將回收的集合。如果此時(shí)對(duì)象還沒(méi)成功逃脫,那么只能被回收了。

垃圾回收算法

標(biāo)記清除算法
標(biāo)記清除算法就是分為標(biāo)記和清除兩個(gè)階段,標(biāo)記出所有需要回收的對(duì)象,標(biāo)記結(jié)束后統(tǒng)一回收。這個(gè)套路很簡(jiǎn)單,但是問(wèn)題會(huì)出現(xiàn)內(nèi)存碎片。后續(xù)的算法都是根據(jù)這個(gè)基礎(chǔ)加以改進(jìn)的。
其實(shí)它就是把已死亡的對(duì)象標(biāo)記為空閑內(nèi)存,然后記錄在列表里,當(dāng)我們需要new一個(gè)對(duì)象的時(shí)候,內(nèi)存管理模塊會(huì)從空閑列表中尋找空閑的內(nèi)存來(lái)分給新的對(duì)象。
不足的方面就是標(biāo)記和清除的效率比較低下,而且內(nèi)存碎片會(huì)很多。這就導(dǎo)致我們需要大內(nèi)存塊時(shí),無(wú)法獲取到足夠的內(nèi)存。

標(biāo)記清除算法

復(fù)制算法
為了解決效率問(wèn)題,復(fù)制算法出現(xiàn)了。它將可用內(nèi)存劃分成兩等份,每次只使用其中一塊。和survivor一樣也是from ,to兩個(gè)指針這樣玩法。這樣就解決了碎片問(wèn)題。
但是這樣其實(shí)只能使用一半內(nèi)存,堆內(nèi)存的使用效率十分低下。
復(fù)制算法

標(biāo)記整理算法
復(fù)制算法在對(duì)象存活率高的時(shí)候會(huì)有效率問(wèn)題,標(biāo)記過(guò)程中仍然是標(biāo)記-清除算法一樣,但是后續(xù)步驟不是直接堆可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存。
標(biāo)記整理算法

分代收集算法
這種算法就是根據(jù)對(duì)象存貨周期不同把內(nèi)存劃分為幾塊,一般是把java堆分為新生代和老年代。這樣就可以根據(jù)年代特點(diǎn)采用合適的收集算法了。
新生代中死亡率很高,采用復(fù)制算法。老年代中存活率高,就用標(biāo)記-清除或者標(biāo)記-整理算法。
各種各樣的垃圾回收器

jdk8默認(rèn)的垃圾收集器是Parallel Scavenge 和Parallel Old
從JDK9開(kāi)始,G1變成默認(rèn)的垃圾收集器了。

關(guān)于JVM調(diào)優(yōu)

根據(jù)上面的一些知識(shí)點(diǎn),我們可以嘗試對(duì)jvm進(jìn)行調(diào)優(yōu),主要就是堆內(nèi)存這塊。
所有線程共享數(shù)據(jù)區(qū)大小 = 新生代大小+老年代大小+持久代大小(元空間使用物理內(nèi)存)。所以java堆中增大年輕代就會(huì)縮小老年代。因?yàn)槔夏甏褂胒ull GC,對(duì)性能影響比較大,所以推薦年輕代/老年代 是 3/8

調(diào)整最大堆內(nèi)存和最小堆內(nèi)存

-Xmx -Xms:指定java堆最大值(默認(rèn)物理內(nèi)存四分之一)和初始化java堆最小值(默認(rèn)物理內(nèi)存的六十四分之一)。
在使用的時(shí)候內(nèi)存占用超過(guò)百分之60會(huì)動(dòng)態(tài)申請(qǐng)內(nèi)存,直到最大值位置。使用內(nèi)存不足百分之30則會(huì)自動(dòng)縮小內(nèi)存,直到最小值。

調(diào)整新生代和老年代的比值

-XX:NewRatio 新生代和老年代的比值
比如-XX:NewRatio = 4.表示新生代:老年代 = 1:4.即新生代占用整個(gè)堆的1/5.

調(diào)整Survivor和Eden區(qū)的比值

-XX:SurvivorRatio 設(shè)置兩個(gè)Survivor和eden的比值
比如-XX:SurvivorRatio=8.表示兩個(gè)Survivor:eden = 2:8。也就是一個(gè)Survivor占用年輕代的十分之一。

設(shè)置年輕代和老年代的大小

-XX:NewSize --- 設(shè)置年輕代大小
-XX:MaxNewSize --- 設(shè)置年輕代最大值

永久區(qū)的設(shè)置

-XX:PermSize -XX:MaxPermSize
初始空間默認(rèn)物理內(nèi)存的六十四分之一,最大空間默認(rèn)物理內(nèi)存的四分之一。

調(diào)整每個(gè)線程的棧空間的大小

可以通過(guò)-Xss:調(diào)整每個(gè)線程??臻g的大小
jdk5以后每個(gè)線程堆棧大小為1m,以前每個(gè)線程堆棧大小為256k。相同物理內(nèi)存下,減小這個(gè)值能生成更多的線程,但是操作系統(tǒng)對(duì)一個(gè)進(jìn)程內(nèi)的線程數(shù)還是有限制的,不能無(wú)限生成。界限值大概3000-5000左右。

本篇筆記就記到這里,很多知識(shí)點(diǎn)都是一帶而過(guò),提及而不深究,感興趣的可以自己去找資料,因?yàn)閖vm這塊細(xì)節(jié)很多,但是常用的并不多??偠灾执虿灰祝绻晕偷侥懔擞浀命c(diǎn)個(gè)喜歡點(diǎn)個(gè)關(guān)注,也祝大家工作順順利利,身體健健康康!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容