国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

一天吃透JVM面試八股文

這篇具有很好參考價(jià)值的文章主要介紹了一天吃透JVM面試八股文。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

什么是JVM?

JVM,全稱(chēng)Java Virtual Machine(Java虛擬機(jī)),是通過(guò)在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來(lái)實(shí)現(xiàn)的。由一套字節(jié)碼指令集、一組寄存器、一個(gè)棧、一個(gè)垃圾回收堆和一個(gè)存儲(chǔ)方法域等組成。JVM屏蔽了與操作系統(tǒng)平臺(tái)相關(guān)的信息,使得Java程序只需要生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可在多種平臺(tái)上不加修改的運(yùn)行,這也是Java能夠“一次編譯,到處運(yùn)行的”原因。

講一下JVM內(nèi)存結(jié)構(gòu)?

JVM內(nèi)存結(jié)構(gòu)分為5大區(qū)域,程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧、、方法區(qū)。最全面的Java面試網(wǎng)站

程序計(jì)數(shù)器

線(xiàn)程私有的,作為當(dāng)前線(xiàn)程的行號(hào)指示器,用于記錄當(dāng)前虛擬機(jī)正在執(zhí)行的線(xiàn)程指令地址。程序計(jì)數(shù)器主要有兩個(gè)作用:

  1. 當(dāng)前線(xiàn)程所執(zhí)行的字節(jié)碼的行號(hào)指示器,通過(guò)它實(shí)現(xiàn)代碼的流程控制,如:順序執(zhí)行、選擇、循環(huán)、異常處理。
  2. 在多線(xiàn)程的情況下,程序計(jì)數(shù)器用于記錄當(dāng)前線(xiàn)程執(zhí)行的位置,當(dāng)線(xiàn)程被切換回來(lái)的時(shí)候能夠知道它上次執(zhí)行的位置。

程序計(jì)數(shù)器是唯一一個(gè)不會(huì)出現(xiàn) OutOfMemoryError 的內(nèi)存區(qū)域,它的生命周期隨著線(xiàn)程的創(chuàng)建而創(chuàng)建,隨著線(xiàn)程的結(jié)束而死亡。

本文已經(jīng)收錄到Github倉(cāng)庫(kù),該倉(cāng)庫(kù)包含計(jì)算機(jī)基礎(chǔ)、Java基礎(chǔ)、多線(xiàn)程、JVM、數(shù)據(jù)庫(kù)、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服務(wù)、設(shè)計(jì)模式、架構(gòu)、校招社招分享等核心知識(shí)點(diǎn),歡迎star~

Github地址

如果訪(fǎng)問(wèn)不了Github,可以訪(fǎng)問(wèn)gitee地址。

gitee地址

虛擬機(jī)棧

Java 虛擬機(jī)棧是由一個(gè)個(gè)棧幀組成,而每個(gè)棧幀中都擁有:局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口信息。每一次函數(shù)調(diào)用都會(huì)有一個(gè)對(duì)應(yīng)的棧幀被壓入虛擬機(jī)棧,每一個(gè)函數(shù)調(diào)用結(jié)束后,都會(huì)有一個(gè)棧幀被彈出。

局部變量表是用于存放方法參數(shù)和方法內(nèi)的局部變量。

每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧所屬方法的符號(hào)引用,在方法調(diào)用過(guò)程中,會(huì)進(jìn)行動(dòng)態(tài)鏈接,將這個(gè)符號(hào)引用轉(zhuǎn)化為直接引用。

  • 部分符號(hào)引用在類(lèi)加載階段的時(shí)候就轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化就是靜態(tài)鏈接
  • 部分符號(hào)引用在運(yùn)行期間轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化就是動(dòng)態(tài)鏈接

Java 虛擬機(jī)棧也是線(xiàn)程私有的,每個(gè)線(xiàn)程都有各自的 Java 虛擬機(jī)棧,而且隨著線(xiàn)程的創(chuàng)建而創(chuàng)建,隨著線(xiàn)程的死亡而死亡。Java 虛擬機(jī)棧會(huì)出現(xiàn)兩種錯(cuò)誤:StackOverFlowErrorOutOfMemoryError

可以通過(guò)-Xss參數(shù)來(lái)指定每個(gè)線(xiàn)程的虛擬機(jī)棧內(nèi)存大?。?/p>

java -Xss2M

本地方法棧

虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法服務(wù),而本地方法棧則為虛擬機(jī)使用到的 Native 方法服務(wù)。Native 方法一般是用其它語(yǔ)言(C、C++等)編寫(xiě)的。

本地方法被執(zhí)行的時(shí)候,在本地方法棧也會(huì)創(chuàng)建一個(gè)棧幀,用于存放該本地方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、出口信息。

堆用于存放對(duì)象實(shí)例,是垃圾收集器管理的主要區(qū)域,因此也被稱(chēng)作GC堆。堆可以細(xì)分為:新生代(Eden空間、From Survivor、To Survivor空間)和老年代。

通過(guò) -Xms設(shè)定程序啟動(dòng)時(shí)占用內(nèi)存大小,通過(guò)-Xmx設(shè)定程序運(yùn)行期間最大可占用的內(nèi)存大小。如果程序運(yùn)行需要占用更多的內(nèi)存,超出了這個(gè)設(shè)置值,就會(huì)拋出OutOfMemory異常。

java -Xms1M -Xmx2M

1.方法區(qū)

方法區(qū)與 Java 堆一樣,是各個(gè)線(xiàn)程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。

對(duì)方法區(qū)進(jìn)行垃圾回收的主要目標(biāo)是對(duì)常量池的回收和對(duì)類(lèi)的卸載

2.永久代

方法區(qū)是 JVM 的規(guī)范,而永久代PermGen是方法區(qū)的一種實(shí)現(xiàn)方式,并且只有 HotSpot 有永久代。對(duì)于其他類(lèi)型的虛擬機(jī),如JRockit沒(méi)有永久代。由于方法區(qū)主要存儲(chǔ)類(lèi)的相關(guān)信息,所以對(duì)于動(dòng)態(tài)生成類(lèi)的場(chǎng)景比較容易出現(xiàn)永久代的內(nèi)存溢出。

3.元空間

JDK 1.8 的時(shí)候,HotSpot的永久代被徹底移除了,使用元空間替代。元空間的本質(zhì)和永久代類(lèi)似,都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。兩者最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用直接內(nèi)存。

為什么要將永久代替換為元空間呢?

永久代內(nèi)存受限于 JVM 可用內(nèi)存,而元空間使用的是直接內(nèi)存,受本機(jī)可用內(nèi)存的限制,雖然元空間仍舊可能溢出,但是相比永久代內(nèi)存溢出的概率更小。

運(yùn)行時(shí)常量池

運(yùn)行時(shí)常量池是方法區(qū)的一部分,在類(lèi)加載之后,會(huì)將編譯器生成的各種字面量和符號(hào)引號(hào)放到運(yùn)行時(shí)常量池。在運(yùn)行期間動(dòng)態(tài)生成的常量,如 String 類(lèi)的 intern()方法,也會(huì)被放入運(yùn)行時(shí)常量池。

直接內(nèi)存

直接內(nèi)存并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,但是這部分內(nèi)存也被頻繁地使用。而且也可能導(dǎo)致 OutOfMemoryError 錯(cuò)誤出現(xiàn)。

NIO的Buffer提供了DirectBuffer,可以直接訪(fǎng)問(wèn)系統(tǒng)物理內(nèi)存,避免堆內(nèi)內(nèi)存到堆外內(nèi)存的數(shù)據(jù)拷貝操作,提高效率。DirectBuffer直接分配在物理內(nèi)存中,并不占用堆空間,其可申請(qǐng)的最大內(nèi)存受操作系統(tǒng)限制,不受最大堆內(nèi)存的限制。

直接內(nèi)存的讀寫(xiě)操作比堆內(nèi)存快,可以提升程序I/O操作的性能。通常在I/O通信過(guò)程中,會(huì)存在堆內(nèi)內(nèi)存到堆外內(nèi)存的數(shù)據(jù)拷貝操作,對(duì)于需要頻繁進(jìn)行內(nèi)存間數(shù)據(jù)拷貝且生命周期較短的暫存數(shù)據(jù),都建議存儲(chǔ)到直接內(nèi)存。

好東西應(yīng)該要分享出來(lái)!我把自己學(xué)習(xí)計(jì)算機(jī)多年以來(lái)的書(shū)籍分享出來(lái)了,匯總到一個(gè)計(jì)算機(jī)經(jīng)典編程書(shū)籍倉(cāng)庫(kù)了,一共300多本,包括C語(yǔ)言、C++、Java、Python、前端、數(shù)據(jù)庫(kù)、操作系統(tǒng)、計(jì)算機(jī)網(wǎng)絡(luò)、數(shù)據(jù)結(jié)構(gòu)和算法、機(jī)器學(xué)習(xí)、編程人生等,可以star一下,下次找書(shū)直接在上面搜索,倉(cāng)庫(kù)持續(xù)更新中~

Github地址

Java對(duì)象的定位方式

Java 程序通過(guò)棧上的 reference 數(shù)據(jù)來(lái)操作堆上的具體對(duì)象。對(duì)象的訪(fǎng)問(wèn)方式由虛擬機(jī)實(shí)現(xiàn)而定,目前主流的訪(fǎng)問(wèn)方式有使用句柄和直接指針兩種:

  • 如果使用句柄的話(huà),那么 Java 堆中將會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池,reference 中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類(lèi)型數(shù)據(jù)各自的具體地址信息。使用句柄來(lái)訪(fǎng)問(wèn)的最大好處是 reference 中存儲(chǔ)的是穩(wěn)定的句柄地址,在對(duì)象被移動(dòng)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而 reference 本身不需要修改。
  • 直接指針。reference 中存儲(chǔ)的直接就是對(duì)象的地址。對(duì)象包含到對(duì)象類(lèi)型數(shù)據(jù)的指針,通過(guò)這個(gè)指針可以訪(fǎng)問(wèn)對(duì)象類(lèi)型數(shù)據(jù)。使用直接指針訪(fǎng)問(wèn)方式最大的好處就是訪(fǎng)問(wèn)對(duì)象速度快,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷(xiāo),虛擬機(jī)hotspot主要是使用直接指針來(lái)訪(fǎng)問(wèn)對(duì)象。

說(shuō)一下堆棧的區(qū)別?

  1. 堆的物理地址分配是不連續(xù)的,性能較慢;棧的物理地址分配是連續(xù)的,性能相對(duì)較快。

  2. 堆存放的是對(duì)象的實(shí)例和數(shù)組;棧存放的是局部變量,操作數(shù)棧,返回結(jié)果等。

  3. 堆是線(xiàn)程共享的;棧是線(xiàn)程私有的。

什么情況下會(huì)發(fā)生棧溢出?

  • 當(dāng)線(xiàn)程請(qǐng)求的棧深度超過(guò)了虛擬機(jī)允許的最大深度時(shí),會(huì)拋出StackOverFlowError異常。這種情況通常是因?yàn)榉椒ㄟf歸沒(méi)終止條件。
  • 新建線(xiàn)程的時(shí)候沒(méi)有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的虛擬機(jī)棧,虛擬機(jī)會(huì)拋出OutOfMemoryError異常。比如線(xiàn)程啟動(dòng)過(guò)多就會(huì)出現(xiàn)這種情況。

類(lèi)文件結(jié)構(gòu)

Class 文件結(jié)構(gòu)如下:

ClassFile {
    u4             magic; //類(lèi)文件的標(biāo)志
    u2             minor_version;//小版本號(hào)
    u2             major_version;//大版本號(hào)
    u2             constant_pool_count;//常量池的數(shù)量
    cp_info        constant_pool[constant_pool_count-1];//常量池
    u2             access_flags;//類(lèi)的訪(fǎng)問(wèn)標(biāo)記
    u2             this_class;//當(dāng)前類(lèi)的索引
    u2             super_class;//父類(lèi)
    u2             interfaces_count;//接口
    u2             interfaces[interfaces_count];//一個(gè)類(lèi)可以實(shí)現(xiàn)多個(gè)接口
    u2             fields_count;//字段屬性
    field_info     fields[fields_count];//一個(gè)類(lèi)會(huì)可以有個(gè)字段
    u2             methods_count;//方法數(shù)量
    method_info    methods[methods_count];//一個(gè)類(lèi)可以有個(gè)多個(gè)方法
    u2             attributes_count;//此類(lèi)的屬性表中的屬性數(shù)
    attribute_info attributes[attributes_count];//屬性表集合
}

主要參數(shù)如下:

魔數(shù)class文件標(biāo)志。

文件版本:高版本的 Java 虛擬機(jī)可以執(zhí)行低版本編譯器生成的類(lèi)文件,但是低版本的 Java 虛擬機(jī)不能執(zhí)行高版本編譯器生成的類(lèi)文件。

常量池:存放字面量和符號(hào)引用。字面量類(lèi)似于 Java 的常量,如字符串,聲明為final的常量值等。符號(hào)引用包含三類(lèi):類(lèi)和接口的全限定名,方法的名稱(chēng)和描述符,字段的名稱(chēng)和描述符。

訪(fǎng)問(wèn)標(biāo)志:識(shí)別類(lèi)或者接口的訪(fǎng)問(wèn)信息,比如這個(gè)Class是類(lèi)還是接口,是否為 public 或者 abstract 類(lèi)型等等。

當(dāng)前類(lèi)的索引:類(lèi)索引用于確定這個(gè)類(lèi)的全限定名。

什么是類(lèi)加載?類(lèi)加載的過(guò)程?

類(lèi)的加載指的是將類(lèi)的class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個(gè)此類(lèi)的對(duì)象,通過(guò)這個(gè)對(duì)象可以訪(fǎng)問(wèn)到方法區(qū)對(duì)應(yīng)的類(lèi)信息。

加載

  1. 通過(guò)類(lèi)的全限定名獲取定義此類(lèi)的二進(jìn)制字節(jié)流
  2. 將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
  3. 在內(nèi)存中生成一個(gè)代表該類(lèi)的Class對(duì)象,作為方法區(qū)類(lèi)信息的訪(fǎng)問(wèn)入口

驗(yàn)證

確保Class文件的字節(jié)流中包含的信息符合虛擬機(jī)規(guī)范,保證在運(yùn)行后不會(huì)危害虛擬機(jī)自身的安全。主要包括四種驗(yàn)證:文件格式驗(yàn)證,元數(shù)據(jù)驗(yàn)證,字節(jié)碼驗(yàn)證,符號(hào)引用驗(yàn)證。

準(zhǔn)備

為類(lèi)變量分配內(nèi)存并設(shè)置類(lèi)變量初始值的階段。

解析

虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程。符號(hào)引用用于描述目標(biāo),直接引用直接指向目標(biāo)的地址。

初始化

開(kāi)始執(zhí)行類(lèi)中定義的Java代碼,初始化階段是調(diào)用類(lèi)構(gòu)造器的過(guò)程。

什么是雙親委派模型?

一個(gè)類(lèi)加載器收到一個(gè)類(lèi)的加載請(qǐng)求時(shí),它首先不會(huì)自己嘗試去加載它,而是把這個(gè)請(qǐng)求委派給父類(lèi)加載器去完成,這樣層層委派,因此所有的加載請(qǐng)求最終都會(huì)傳送到頂層的啟動(dòng)類(lèi)加載器中,只有當(dāng)父類(lèi)加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載。

雙親委派模型的具體實(shí)現(xiàn)代碼在 java.lang.ClassLoader中,此類(lèi)的 loadClass() 方法運(yùn)行過(guò)程如下:先檢查類(lèi)是否已經(jīng)加載過(guò),如果沒(méi)有則讓父類(lèi)加載器去加載。當(dāng)父類(lèi)加載器加載失敗時(shí)拋出 ClassNotFoundException,此時(shí)嘗試自己去加載。源碼如下:

public abstract class ClassLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
}

為什么需要雙親委派模型?

雙親委派模型的好處:可以防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼。如果沒(méi)有雙親委派模型而是由各個(gè)類(lèi)加載器自行加載的話(huà),如果用戶(hù)編寫(xiě)了一個(gè)java.lang.Object的同名類(lèi)并放在ClassPath中,多個(gè)類(lèi)加載器都去加載這個(gè)類(lèi)到內(nèi)存中,系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類(lèi),那么類(lèi)之間的比較結(jié)果及類(lèi)的唯一性將無(wú)法保證。

什么是類(lèi)加載器,類(lèi)加載器有哪些?

  • 實(shí)現(xiàn)通過(guò)類(lèi)的全限定名獲取該類(lèi)的二進(jìn)制字節(jié)流的代碼塊叫做類(lèi)加載器。

    主要有一下四種類(lèi)加載器:

    • 啟動(dòng)類(lèi)加載器:用來(lái)加載 Java 核心類(lèi)庫(kù),無(wú)法被 Java 程序直接引用。
    • 擴(kuò)展類(lèi)加載器:它用來(lái)加載 Java 的擴(kuò)展庫(kù)。Java 虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫(kù)目錄。該類(lèi)加載器在此目錄里面查找并加載 Java 類(lèi)。
    • 系統(tǒng)類(lèi)加載器:它根據(jù)應(yīng)用的類(lèi)路徑來(lái)加載 Java 類(lèi)??赏ㄟ^(guò)ClassLoader.getSystemClassLoader()獲取它。
    • 自定義類(lèi)加載器:通過(guò)繼承java.lang.ClassLoader類(lèi)的方式實(shí)現(xiàn)。

類(lèi)的實(shí)例化順序?

  1. 父類(lèi)中的static代碼塊,當(dāng)前類(lèi)的static代碼塊
  2. 父類(lèi)的普通代碼塊
  3. 父類(lèi)的構(gòu)造函數(shù)
  4. 當(dāng)前類(lèi)普通代碼塊
  5. 當(dāng)前類(lèi)的構(gòu)造函數(shù)

如何判斷一個(gè)對(duì)象是否存活?

對(duì)堆垃圾回收前的第一步就是要判斷那些對(duì)象已經(jīng)死亡(即不再被任何途徑引用的對(duì)象)。判斷對(duì)象是否存活有兩種方法:引用計(jì)數(shù)法和可達(dá)性分析。

引用計(jì)數(shù)法

給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器就加 1;當(dāng)引用失效,計(jì)數(shù)器就減 1;任何時(shí)候計(jì)數(shù)器為 0 的對(duì)象就是不可能再被使用的。

這種方法很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題。比如下面的代碼,obj1obj2 互相引用,這種情況下,引用計(jì)數(shù)器的值都是1,不會(huì)被垃圾回收。

public class ReferenceCount {
    Object instance = null;
	public static void main(String[] args) {
		ReferenceCount obj1 = new ReferenceCount();
		ReferenceCount obj2 = new ReferenceCount();
		obj1.instance = obj2;
		obj2.instance = obj1;
		obj1 = null;
		obj2 = null;
	}
}

可達(dá)性分析

通過(guò)GC Root對(duì)象為起點(diǎn),從這些節(jié)點(diǎn)向下搜索,搜索所走過(guò)的路徑叫引用鏈,當(dāng)一個(gè)對(duì)象到GC Root沒(méi)有任何的引用鏈相連時(shí),說(shuō)明這個(gè)對(duì)象是不可用的。

可作為GC Roots的對(duì)象有哪些?

  1. 虛擬機(jī)棧中引用的對(duì)象
  2. 本地方法棧中Native方法引用的對(duì)象
  3. 方法區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象
  4. 方法區(qū)中常量引用的對(duì)象

什么情況下類(lèi)會(huì)被卸載?

需要同時(shí)滿(mǎn)足以下 3 個(gè)條件類(lèi)才可能會(huì)被卸載 :

  • 該類(lèi)所有的實(shí)例都已經(jīng)被回收。
  • 加載該類(lèi)的類(lèi)加載器已經(jīng)被回收。
  • 該類(lèi)對(duì)應(yīng)的 java.lang.Class 對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪(fǎng)問(wèn)該類(lèi)的方法。

虛擬機(jī)可以對(duì)滿(mǎn)足上述 3 個(gè)條件的類(lèi)進(jìn)行回收,但不一定會(huì)進(jìn)行回收。

強(qiáng)引用、軟引用、弱引用、虛引用是什么,有什么區(qū)別?

強(qiáng)引用:在程序中普遍存在的引用賦值,類(lèi)似Object obj = new Object()這種引用關(guān)系。只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。

軟引用:如果內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它,如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。

//軟引用
SoftReference<String> softRef = new SoftReference<String>(str);

弱引用:在進(jìn)行垃圾回收時(shí),不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收只具有弱引用的對(duì)象。

//弱引用
WeakReference<String> weakRef = new WeakReference<String>(str);

虛引用:虛引用并不會(huì)決定對(duì)象的生命周期。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒(méi)有任何引用一樣,在任何時(shí)候都可能被垃圾回收。虛引用主要是為了能在對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。

GC是什么?為什么要GC?

GC(Garbage Collection),垃圾回收,是Java與C++的主要區(qū)別之一。作為Java開(kāi)發(fā)者,一般不需要專(zhuān)門(mén)編寫(xiě)內(nèi)存回收和垃圾清理代碼。這是因?yàn)樵贘ava虛擬機(jī)中,存在自動(dòng)內(nèi)存管理和垃圾清理機(jī)制。對(duì)JVM中的內(nèi)存進(jìn)行標(biāo)記,并確定哪些內(nèi)存需要回收,根據(jù)一定的回收策略,自動(dòng)的回收內(nèi)存,保證JVM中的內(nèi)存空間,防止出現(xiàn)內(nèi)存泄露和溢出問(wèn)題。

Minor GC 和 Full GC的區(qū)別?

  • Minor GC:回收新生代,因?yàn)樾律鷮?duì)象存活時(shí)間很短,因此 Minor GC會(huì)頻繁執(zhí)行,執(zhí)行的速度一般也會(huì)比較快。

  • Full GC:回收老年代和新生代,老年代的對(duì)象存活時(shí)間長(zhǎng),因此 Full GC 很少執(zhí)行,執(zhí)行速度會(huì)比 Minor GC 慢很多。

內(nèi)存的分配策略?

對(duì)象優(yōu)先在 Eden 分配

大多數(shù)情況下,對(duì)象在新生代 Eden 上分配,當(dāng) Eden 空間不夠時(shí),觸發(fā) Minor GC。

大對(duì)象直接進(jìn)入老年代

大對(duì)象是指需要連續(xù)內(nèi)存空間的對(duì)象,最典型的大對(duì)象有長(zhǎng)字符串和大數(shù)組??梢栽O(shè)置JVM參數(shù) -XX:PretenureSizeThreshold,大于此值的對(duì)象直接在老年代分配。

長(zhǎng)期存活的對(duì)象進(jìn)入老年代

通過(guò)參數(shù) -XX:MaxTenuringThreshold 可以設(shè)置對(duì)象進(jìn)入老年代的年齡閾值。對(duì)象在Survivor區(qū)每經(jīng)過(guò)一次 Minor GC,年齡就增加 1 歲,當(dāng)它的年齡增加到一定程度,就會(huì)被晉升到老年代中。

動(dòng)態(tài)對(duì)象年齡判定

并非對(duì)象的年齡必須達(dá)到 MaxTenuringThreshold 才能晉升老年代,如果在 Survivor 中相同年齡所有對(duì)象大小的總和大于 Survivor 空間的一半,則年齡大于或等于該年齡的對(duì)象可以直接進(jìn)入老年代,無(wú)需達(dá)到 MaxTenuringThreshold 年齡閾值。

空間分配擔(dān)保

在發(fā)生 Minor GC 之前,虛擬機(jī)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果條件成立的話(huà),那么 Minor GC 是安全的。如果不成立的話(huà)虛擬機(jī)會(huì)查看 HandlePromotionFailure 的值是否允許擔(dān)保失敗。如果允許,那么就會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,將嘗試著進(jìn)行一次 Minor GC;如果小于,或者 HandlePromotionFailure 的值為不允許擔(dān)保失敗,那么就要進(jìn)行一次 Full GC。

Full GC 的觸發(fā)條件?

對(duì)于 Minor GC,其觸發(fā)條件比較簡(jiǎn)單,當(dāng) Eden 空間滿(mǎn)時(shí),就將觸發(fā)一次 Minor GC。而 Full GC 觸發(fā)條件相對(duì)復(fù)雜,有以下情況會(huì)發(fā)生 full GC:

調(diào)用 System.gc()

只是建議虛擬機(jī)執(zhí)行 Full GC,但是虛擬機(jī)不一定真正去執(zhí)行。不建議使用這種方式,而是讓虛擬機(jī)管理內(nèi)存。

老年代空間不足

老年代空間不足的常見(jiàn)場(chǎng)景為前文所講的大對(duì)象直接進(jìn)入老年代、長(zhǎng)期存活的對(duì)象進(jìn)入老年代等。為了避免以上原因引起的 Full GC,應(yīng)當(dāng)盡量不要?jiǎng)?chuàng)建過(guò)大的對(duì)象以及數(shù)組、注意編碼規(guī)范避免內(nèi)存泄露。除此之外,可以通過(guò) -Xmn 參數(shù)調(diào)大新生代的大小,讓對(duì)象盡量在新生代被回收掉,不進(jìn)入老年代。還可以通過(guò) -XX:MaxTenuringThreshold 調(diào)大對(duì)象進(jìn)入老年代的年齡,讓對(duì)象在新生代多存活一段時(shí)間。

空間分配擔(dān)保失敗

使用復(fù)制算法的 Minor GC 需要老年代的內(nèi)存空間作擔(dān)保,如果擔(dān)保失敗會(huì)執(zhí)行一次 Full GC。

JDK 1.7 及以前的永久代空間不足

在 JDK 1.7 及以前,HotSpot 虛擬機(jī)中的方法區(qū)是用永久代實(shí)現(xiàn)的,永久代中存放的為一些 Class 的信息、常量、靜態(tài)變量等數(shù)據(jù)。當(dāng)系統(tǒng)中要加載的類(lèi)、反射的類(lèi)和調(diào)用的方法較多時(shí),永久代可能會(huì)被占滿(mǎn),在未配置為采用 CMS GC 的情況下也會(huì)執(zhí)行 Full GC。如果經(jīng)過(guò) Full GC 仍然回收不了,那么虛擬機(jī)會(huì)拋出 java.lang.OutOfMemoryError。

垃圾回收算法有哪些?

垃圾回收算法有四種,分別是標(biāo)記清除法、標(biāo)記整理法、復(fù)制算法、分代收集算法

標(biāo)記清除算法

首先利用可達(dá)性去遍歷內(nèi)存,把存活對(duì)象和垃圾對(duì)象進(jìn)行標(biāo)記。標(biāo)記結(jié)束后統(tǒng)一將所有標(biāo)記的對(duì)象回收掉。這種垃圾回收算法效率較低,并且會(huì)產(chǎn)生大量不連續(xù)的空間碎片。

復(fù)制清除算法

半?yún)^(qū)復(fù)制,用于新生代垃圾回收。將內(nèi)存分為大小相同的兩塊,每次使用其中的一塊。當(dāng)這一塊的內(nèi)存使用完后,就將還存活的對(duì)象復(fù)制到另一塊去,然后再把使用的空間一次清理掉。

特點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效,但可用內(nèi)存縮小為了原來(lái)的一半,浪費(fèi)空間。

標(biāo)記整理算法

根據(jù)老年代的特點(diǎn)提出的一種標(biāo)記算法,標(biāo)記過(guò)程仍然與標(biāo)記-清除算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存。

分類(lèi)收集算法

根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?/p>

一般將堆分為新生代和老年代。

  • 新生代使用復(fù)制算法
  • 老年代使用標(biāo)記清除算法或者標(biāo)記整理算法

在新生代中,每次垃圾收集時(shí)都有大批對(duì)象死去,只有少量存活,使用復(fù)制算法比較合適,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集。老年代對(duì)象存活率高,適合使用標(biāo)記-清理或者標(biāo)記-整理算法進(jìn)行垃圾回收。

有哪些垃圾回收器?

垃圾回收器主要分為以下幾種:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1

這7種垃圾收集器的特點(diǎn):

收集器 串行、并行or并發(fā) 新生代/老年代 算法 目標(biāo) 適用場(chǎng)景
Serial 串行 新生代 復(fù)制算法 響應(yīng)速度優(yōu)先 單CPU環(huán)境下的Client模式
ParNew 并行 新生代 復(fù)制算法 響應(yīng)速度優(yōu)先 多CPU環(huán)境時(shí)在Server模式下與CMS配合
Parallel Scavenge 并行 新生代 復(fù)制算法 吞吐量?jī)?yōu)先 在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)
Serial Old 串行 老年代 標(biāo)記-整理 響應(yīng)速度優(yōu)先 單CPU環(huán)境下的Client模式、CMS的后備預(yù)案
Parallel Old 并行 老年代 標(biāo)記-整理 吞吐量?jī)?yōu)先 在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)
CMS 并發(fā) 老年代 標(biāo)記-清除 響應(yīng)速度優(yōu)先 集中在互聯(lián)網(wǎng)站或B/S系統(tǒng)服務(wù)端上的Java應(yīng)用
G1 并發(fā) both 標(biāo)記-整理+復(fù)制算法 響應(yīng)速度優(yōu)先 面向服務(wù)端應(yīng)用,將來(lái)替換CMS

Serial 收集器

單線(xiàn)程收集器,使用一個(gè)垃圾收集線(xiàn)程去進(jìn)行垃圾回收,在進(jìn)行垃圾回收的時(shí)候必須暫停其他所有的工作線(xiàn)程( Stop The World ),直到它收集結(jié)束。

特點(diǎn):簡(jiǎn)單高效;內(nèi)存消耗??;沒(méi)有線(xiàn)程交互的開(kāi)銷(xiāo),單線(xiàn)程收集效率高;需暫停所有的工作線(xiàn)程,用戶(hù)體驗(yàn)不好。

ParNew 收集器

Serial收集器的多線(xiàn)程版本,除了使用多線(xiàn)程進(jìn)行垃圾收集外,其他行為、參數(shù)與 Serial 收集器基本一致。

Parallel Scavenge 收集器

新生代收集器,基于復(fù)制清除算法實(shí)現(xiàn)的收集器。特點(diǎn)是吞吐量?jī)?yōu)先,能夠并行收集的多線(xiàn)程收集器,允許多個(gè)垃圾回收線(xiàn)程同時(shí)運(yùn)行,降低垃圾收集時(shí)間,提高吞吐量。所謂吞吐量就是 CPU 中用于運(yùn)行用戶(hù)代碼的時(shí)間與 CPU 總消耗時(shí)間的比值(吞吐量 = 運(yùn)行用戶(hù)代碼時(shí)間 /(運(yùn)行用戶(hù)代碼時(shí)間 + 垃圾收集時(shí)間))。Parallel Scavenge 收集器關(guān)注點(diǎn)是吞吐量,高效率的利用 CPU 資源。CMS 垃圾收集器關(guān)注點(diǎn)更多的是用戶(hù)線(xiàn)程的停頓時(shí)間。

Parallel Scavenge收集器提供了兩個(gè)參數(shù)用于精確控制吞吐量,分別是控制最大垃圾收集停頓時(shí)間的-XX:MaxGCPauseMillis參數(shù)以及直接設(shè)置吞吐量大小的-XX:GCTimeRatio參數(shù)。

  • -XX:MaxGCPauseMillis參數(shù)的值是一個(gè)大于0的毫秒數(shù),收集器將盡量保證內(nèi)存回收花費(fèi)的時(shí)間不超過(guò)用戶(hù)設(shè)定值。

  • -XX:GCTimeRatio參數(shù)的值大于0小于100,即垃圾收集時(shí)間占總時(shí)間的比率,相當(dāng)于吞吐量的倒數(shù)。

Serial Old 收集器

Serial 收集器的老年代版本,單線(xiàn)程收集器,使用標(biāo)記整理算法。

Parallel Old 收集器

Parallel Scavenge 收集器的老年代版本。多線(xiàn)程垃圾收集,使用標(biāo)記整理算法。

CMS 收集器

Concurrent Mark Sweep ,并發(fā)標(biāo)記清除,追求獲取最短停頓時(shí)間,實(shí)現(xiàn)了讓垃圾收集線(xiàn)程與用戶(hù)線(xiàn)程基本上同時(shí)工作。

CMS 垃圾回收基于標(biāo)記清除算法實(shí)現(xiàn),整個(gè)過(guò)程分為四個(gè)步驟:

  • 初始標(biāo)記: 暫停所有用戶(hù)線(xiàn)程(Stop The World),記錄直接與 GC Roots 直接相連的對(duì)象 。
  • 并發(fā)標(biāo)記:從GC Roots開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,找出存活對(duì)象,耗時(shí)較長(zhǎng),但是不需要停頓用戶(hù)線(xiàn)程。
  • 重新標(biāo)記: 在并發(fā)標(biāo)記期間對(duì)象的引用關(guān)系可能會(huì)變化,需要重新進(jìn)行標(biāo)記。此階段也會(huì)暫停所有用戶(hù)線(xiàn)程。
  • 并發(fā)清除:清除標(biāo)記對(duì)象,這個(gè)階段也是可以與用戶(hù)線(xiàn)程同時(shí)并發(fā)的。

在整個(gè)過(guò)程中,耗時(shí)最長(zhǎng)的是并發(fā)標(biāo)記和并發(fā)清除階段,這兩個(gè)階段垃圾收集線(xiàn)程都可以與用戶(hù)線(xiàn)程一起工作,所以從總體上來(lái)說(shuō),CMS收集器的內(nèi)存回收過(guò)程是與用戶(hù)線(xiàn)程一起并發(fā)執(zhí)行的。

優(yōu)點(diǎn):并發(fā)收集,停頓時(shí)間短。

缺點(diǎn)

  • 標(biāo)記清除算法導(dǎo)致收集結(jié)束有大量空間碎片。
  • 產(chǎn)生浮動(dòng)垃圾,在并發(fā)清理階段用戶(hù)線(xiàn)程還在運(yùn)行,會(huì)不斷有新的垃圾產(chǎn)生,這一部分垃圾出現(xiàn)在標(biāo)記過(guò)程之后,CMS無(wú)法在當(dāng)次收集中回收它們,只好等到下一次垃圾回收再處理;

G1收集器

G1垃圾收集器的目標(biāo)是在不同應(yīng)用場(chǎng)景中追求高吞吐量和低停頓之間的最佳平衡。

G1將整個(gè)堆分成相同大小的分區(qū)(Region),有四種不同類(lèi)型的分區(qū):Eden、Survivor、Old和Humongous。分區(qū)的大小取值范圍為 1M 到 32M,都是2的冪次方。分區(qū)大小可以通過(guò)-XX:G1HeapRegionSize參數(shù)指定。Humongous區(qū)域用于存儲(chǔ)大對(duì)象。G1規(guī)定只要大小超過(guò)了一個(gè)分區(qū)容量一半的對(duì)象就認(rèn)為是大對(duì)象。

G1 收集器對(duì)各個(gè)分區(qū)回收所獲得的空間大小和回收所需時(shí)間的經(jīng)驗(yàn)值進(jìn)行排序,得到一個(gè)優(yōu)先級(jí)列表,每次根據(jù)用戶(hù)設(shè)置的最大回收停頓時(shí)間,優(yōu)先回收價(jià)值最大的分區(qū)。

特點(diǎn):可以由用戶(hù)指定期望的垃圾收集停頓時(shí)間。

G1 收集器的回收過(guò)程分為以下幾個(gè)步驟:

  • 初始標(biāo)記。暫停所有其他線(xiàn)程,記錄直接與 GC Roots 直接相連的對(duì)象,耗時(shí)較短 。
  • 并發(fā)標(biāo)記。從GC Roots開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,找出要回收的對(duì)象,耗時(shí)較長(zhǎng),不過(guò)可以和用戶(hù)程序并發(fā)執(zhí)行。
  • 最終標(biāo)記。需對(duì)其他線(xiàn)程做短暫的暫停,用于處理并發(fā)標(biāo)記階段對(duì)象引用出現(xiàn)變動(dòng)的區(qū)域。
  • 篩選回收。對(duì)各個(gè)分區(qū)的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶(hù)所期望的停頓時(shí)間來(lái)制定回收計(jì)劃,然后把決定回收的分區(qū)的存活對(duì)象復(fù)制到空的分區(qū)中,再清理掉整個(gè)舊的分區(qū)的全部空間。這里的操作涉及存活對(duì)象的移動(dòng),會(huì)暫停用戶(hù)線(xiàn)程,由多條收集器線(xiàn)程并行完成。

常用的 JVM 調(diào)優(yōu)的命令都有哪些?

jps:列出本機(jī)所有 Java 進(jìn)程的進(jìn)程號(hào)

常用參數(shù)如下:

  • -m 輸出main方法的參數(shù)
  • -l 輸出完全的包名和應(yīng)用主類(lèi)名
  • -v 輸出JVM參數(shù)
jps -lvm
//output
//4124 com.zzx.Application -javaagent:E:\IDEA2019\lib\idea_rt.jar=10291:E:\IDEA2019\bin -Dfile.encoding=UTF-8

jstack:查看某個(gè) Java 進(jìn)程內(nèi)的線(xiàn)程堆棧信息。使用參數(shù)-l可以打印額外的鎖信息,發(fā)生死鎖時(shí)可以使用jstack -l pid觀察鎖持有情況。

jstack -l 4124 | more

輸出結(jié)果如下:

"http-nio-8001-exec-10" #40 daemon prio=5 os_prio=0 tid=0x000000002542f000 nid=0x4028 waiting on condition [0x000000002cc9e000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000077420d7e8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

WAITING (parking)指線(xiàn)程處于掛起中,在等待某個(gè)條件發(fā)生,來(lái)把自己?jiǎn)拘选?/p>

jstat:用于查看虛擬機(jī)各種運(yùn)行狀態(tài)信息(類(lèi)裝載、內(nèi)存、垃圾收集等運(yùn)行數(shù)據(jù))。使用參數(shù)-gcuitl可以查看垃圾回收的統(tǒng)計(jì)信息。

jstat -gcutil 4124
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00   0.00  67.21  19.20  96.36  94.96     10    0.084     3    0.191    0.275

參數(shù)說(shuō)明:

  • S0Survivor0區(qū)當(dāng)前使用比例
  • S1Survivor1區(qū)當(dāng)前使用比例
  • EEden區(qū)使用比例
  • O:老年代使用比例
  • M:元數(shù)據(jù)區(qū)使用比例
  • CCS:壓縮使用比例
  • YGC:年輕代垃圾回收次數(shù)
  • FGC:老年代垃圾回收次數(shù)
  • FGCT:老年代垃圾回收消耗時(shí)間
  • GCT:垃圾回收消耗總時(shí)間

jmap:查看堆內(nèi)存快照。通過(guò)jmap命令可以獲得運(yùn)行中的堆內(nèi)存的快照,從而可以對(duì)堆內(nèi)存進(jìn)行離線(xiàn)分析。

查詢(xún)進(jìn)程4124的堆內(nèi)存快照,輸出結(jié)果如下:

>jmap -heap 4124
Attaching to process ID 4124, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.221-b11

using thread-local object allocation.
Parallel GC with 6 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 4238344192 (4042.0MB)
   NewSize                  = 88604672 (84.5MB)
   MaxNewSize               = 1412431872 (1347.0MB)
   OldSize                  = 177733632 (169.5MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 327155712 (312.0MB)
   used     = 223702392 (213.33922576904297MB)
   free     = 103453320 (98.66077423095703MB)
   68.37795697725736% used
From Space:
   capacity = 21495808 (20.5MB)
   used     = 0 (0.0MB)
   free     = 21495808 (20.5MB)
   0.0% used
To Space:
   capacity = 23068672 (22.0MB)
   used     = 0 (0.0MB)
   free     = 23068672 (22.0MB)
   0.0% used
PS Old Generation
   capacity = 217579520 (207.5MB)
   used     = 41781472 (39.845916748046875MB)
   free     = 175798048 (167.65408325195312MB)
   19.20285144484187% used

27776 interned Strings occupying 3262336 bytes.

jinfojinfo -flags 1。查看當(dāng)前的應(yīng)用JVM參數(shù)配置。

Attaching to process ID 1, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.111-b14
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=31457280 -XX:MaxHeapSize=480247808 -XX:MaxNewSize=160038912 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=10485760 -XX:OldSize=20971520 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
Command line:

查看所有參數(shù)java -XX:+PrintFlagsFinal -version。用于查看最終值,初始值可能被修改掉(查看初始值可以使用java -XX:+PrintFlagsInitial)。

[Global flags]
    uintx AdaptiveSizeDecrementScaleFactor          = 4                                   {product}
    uintx AdaptiveSizeMajorGCDecayTimeScale         = 10                                  {product}
    uintx AdaptiveSizePausePolicy                   = 0                                   {product}
    uintx AdaptiveSizePolicyCollectionCostMargin    = 50                                  {product}
    uintx AdaptiveSizePolicyInitializingSteps       = 20                                  {product}
    uintx AdaptiveSizePolicyOutputInterval          = 0                                   {product}
    uintx AdaptiveSizePolicyWeight                  = 10                                  {product}
    uintx AdaptiveSizeThroughPutPolicy              = 0                                   {product}
    uintx AdaptiveTimeWeight                        = 25                                  {product}
     bool AdjustConcurrency                         = false                               {product}
     bool AggressiveOpts                            = false                               {product}
     ....

對(duì)象頭了解嗎?

Java 內(nèi)存中的對(duì)象由以下三部分組成:對(duì)象頭、實(shí)例數(shù)據(jù)對(duì)齊填充字節(jié)。

而對(duì)象頭由以下三部分組成:mark word指向類(lèi)信息的指針數(shù)組長(zhǎng)度(數(shù)組才有)。

mark word包含:對(duì)象的哈希碼、分代年齡和鎖標(biāo)志位。

對(duì)象的實(shí)例數(shù)據(jù)就是 Java 對(duì)象的屬性和值。

對(duì)齊填充字節(jié):因?yàn)镴VM要求對(duì)象占的內(nèi)存大小是 8bit 的倍數(shù),因此后面有幾個(gè)字節(jié)用于把對(duì)象的大小補(bǔ)齊至 8bit 的倍數(shù)。

內(nèi)存對(duì)齊的主要作用是:

  1. 平臺(tái)原因:不是所有的硬件平臺(tái)都能訪(fǎng)問(wèn)任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類(lèi)型的數(shù)據(jù),否則拋出硬件異常。
  2. 性能原因:經(jīng)過(guò)內(nèi)存對(duì)齊后,CPU的內(nèi)存訪(fǎng)問(wèn)速度大大提升。

Object o = new Object()占用多少個(gè)字節(jié)?

答案是16個(gè)字節(jié)。

首先先分析對(duì)象的內(nèi)存布局。

在 JVM 中,Java對(duì)象保存在堆中時(shí),由以下三部分組成:

對(duì)象頭(Object Header):包括關(guān)于堆對(duì)象的布局、類(lèi)型、GC狀態(tài)、同步狀態(tài)和標(biāo)識(shí)哈希碼的基本信息。由兩個(gè)詞mark wordclasspointer組成,如果是數(shù)組對(duì)象的話(huà),還會(huì)有一個(gè)length field。

  • mark word:通常是一組位域,用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如hashCode、GC分代年齡、鎖同步信息等等。占用64個(gè)比特(64位系統(tǒng)),8個(gè)字節(jié)。
  • classpointer:類(lèi)指針,是對(duì)象指向它的類(lèi)元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例。占用64個(gè)比特(64位系統(tǒng)),8個(gè)字節(jié)。開(kāi)啟壓縮類(lèi)指針后,占用32個(gè)比特,4個(gè)字節(jié)。

實(shí)例數(shù)據(jù)(Instance Data):存儲(chǔ)了代碼中定義的各種字段的內(nèi)容,包括從父類(lèi)繼承下來(lái)的字段和子類(lèi)中定義的字段。如果對(duì)象無(wú)屬性字段,則這里就不會(huì)有數(shù)據(jù)。根據(jù)字段類(lèi)型的不同占不同的字節(jié),例如boolean類(lèi)型占1個(gè)字節(jié),int類(lèi)型占4個(gè)字節(jié)等等。為了提高存儲(chǔ)空間的利用率,這部分?jǐn)?shù)據(jù)的存儲(chǔ)順序會(huì)受到虛擬機(jī)分配策略參數(shù)和字段在Java源碼中定義順序的影響。

對(duì)齊填充(Padding):對(duì)象可以有對(duì)齊數(shù)據(jù)也可以沒(méi)有。默認(rèn)情況下,Java虛擬機(jī)堆中對(duì)象的起始地址需要對(duì)齊至8的整數(shù)倍。如果一個(gè)對(duì)象的對(duì)象頭和實(shí)例數(shù)據(jù)占用的總大小不到8字節(jié)的整數(shù)倍,則以此來(lái)填充對(duì)象大小至8字節(jié)的整數(shù)倍。

為什么要對(duì)齊填充?字段內(nèi)存對(duì)齊的其中一個(gè)原因,是讓字段只出現(xiàn)在同一CPU的緩存行中。如果字段不是對(duì)齊的,那么就有可能出現(xiàn)跨緩存行的字段。也就是說(shuō),該字段的讀取可能需要替換兩個(gè)緩存行,而該字段的存儲(chǔ)也會(huì)同時(shí)污染兩個(gè)緩存行。這兩種情況對(duì)程序的執(zhí)行效率而言都是不利的。其實(shí)對(duì)其填充的最終目的是為了計(jì)算機(jī)高效尋址。

經(jīng)過(guò)上面的分析之后,就可以知道Object o = new Object()具體占用多少內(nèi)存了(以64位系統(tǒng)為例)。

  • 在開(kāi)啟指針壓縮的情況下,markword占用8字節(jié),classpointer占用4字節(jié),Instance data無(wú)數(shù)據(jù),總共是12字節(jié),由于對(duì)象需要為8的整數(shù)倍,Padding會(huì)補(bǔ)充4個(gè)字節(jié),總共占用16字節(jié)。
  • 在沒(méi)有開(kāi)啟指針壓縮的情況下,markword占用8字節(jié),classpointer占用8字節(jié),Instance data無(wú)數(shù)據(jù),也是占用16字節(jié)。

main方法執(zhí)行過(guò)程

以下是示例代碼:

public class Application {
    public static void main(String[] args) {
        Person p = new Person("大彬");
        p.getName();
    }
}

class Person {
    public String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

執(zhí)行main方法的過(guò)程如下:

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

對(duì)象創(chuàng)建過(guò)程

  1. 類(lèi)加載檢查:當(dāng)虛擬機(jī)遇到一條 new 指令時(shí),首先檢查是否能在常量池中定位到這個(gè)類(lèi)的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類(lèi)是否已被加載過(guò)、解析和初始化過(guò)。如果沒(méi)有,那先執(zhí)行類(lèi)加載。
  2. 分配內(nèi)存:在類(lèi)加載檢查通過(guò)后,接下來(lái)虛擬機(jī)將為對(duì)象實(shí)例分配內(nèi)存。
  3. 初始化。分配到的內(nèi)存空間都初始化為零值,通過(guò)這個(gè)操作保證了對(duì)象的字段可以不賦初始值就直接使用,程序能訪(fǎng)問(wèn)到這些字段的數(shù)據(jù)類(lèi)型所對(duì)應(yīng)的零值。
  4. 設(shè)置對(duì)象頭。Hotspot 虛擬機(jī)的對(duì)象頭包括:存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)(哈希碼、分代年齡、鎖標(biāo)志等等)、類(lèi)型指針和數(shù)據(jù)長(zhǎng)度(數(shù)組對(duì)象才有),類(lèi)型指針就是對(duì)象指向它的類(lèi)信息的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例。
  5. 按照Java代碼進(jìn)行初始化。

如何排查 OOM 的問(wèn)題?

線(xiàn)上JVM必須配置-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/tmp/heapdump.hprof,當(dāng)OOM發(fā)生時(shí)自動(dòng) dump 堆內(nèi)存信息到指定目錄

排查 OOM 的方法如下:

  • 查看服務(wù)器運(yùn)行日志日志,捕捉到內(nèi)存溢出異常
  • jstat 查看監(jiān)控JVM的內(nèi)存和GC情況,評(píng)估問(wèn)題大概出在什么區(qū)域
  • 使用MAT工具載入dump文件,分析大對(duì)象的占用情況

什么是內(nèi)存溢出和內(nèi)存泄露?

內(nèi)存溢出指的是程序申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存供申請(qǐng)者使用,比如給了你一塊存儲(chǔ)int類(lèi)型數(shù)據(jù)的存儲(chǔ)空間,但是你卻存儲(chǔ)long類(lèi)型的數(shù)據(jù),那么結(jié)果就是內(nèi)存不夠用,此時(shí)就會(huì)報(bào)錯(cuò)OOM,即內(nèi)存溢出。

內(nèi)存泄露是指程序中間動(dòng)態(tài)分配了內(nèi)存,但在程序結(jié)束時(shí)沒(méi)有釋放這部分內(nèi)存,從而造成那部分內(nèi)存不可用的情況。這種情況重啟計(jì)算機(jī)可以解決,但也有可能再次發(fā)生內(nèi)存泄露。內(nèi)存泄露和硬件沒(méi)有關(guān)系,它是由軟件設(shè)計(jì)缺陷引起的。

像IO操作或者網(wǎng)絡(luò)連接等,在使用完成之后沒(méi)有調(diào)用close()方法將其連接關(guān)閉,那么它們占用的內(nèi)存是不會(huì)自動(dòng)被GC回收的,此時(shí)就會(huì)產(chǎn)生內(nèi)存泄露。

比如操作數(shù)據(jù)庫(kù)時(shí),通過(guò)SessionFactory獲取一個(gè)session:

Session session=sessionFactory.openSession();

完成后我們必須調(diào)用session.close()方法關(guān)閉,否則就會(huì)產(chǎn)生內(nèi)存泄露,因?yàn)閟essionFactory這個(gè)長(zhǎng)生命周期對(duì)象一直持有session這個(gè)短生命周期對(duì)象的引用。

那兩者有什么不同呢?

內(nèi)存泄露可以通過(guò)完善代碼來(lái)避免,內(nèi)存溢出可以通過(guò)調(diào)整配置來(lái)減少發(fā)生頻率,但無(wú)法徹底避免。

如何避免內(nèi)存泄露和溢出呢?

  1. 盡早釋放無(wú)用對(duì)象的引用。比如使用臨時(shí)變量的時(shí)候,讓引用變量在退出活動(dòng)域后自動(dòng)設(shè)置為null,暗示垃圾收集器來(lái)收集該對(duì)象,防止發(fā)生內(nèi)存泄露。
  2. 盡量少用靜態(tài)變量。因?yàn)殪o態(tài)變量是全局的,GC不會(huì)回收。
  3. 避免集中創(chuàng)建對(duì)象尤其是大對(duì)象,如果可以的話(huà)盡量使用流操作。
  4. 盡量運(yùn)用池化技術(shù)(數(shù)據(jù)庫(kù)連接池等)以提高系統(tǒng)性能。
  5. 避免在循環(huán)中創(chuàng)建過(guò)多對(duì)象。

參考資料文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-418764.html

  • 周志明. 深入理解 Java 虛擬機(jī) [M]. 機(jī)械工業(yè)出版社

到了這里,關(guān)于一天吃透JVM面試八股文的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 一天吃透SpringMVC面試八股文

    SpringMVC是一種基于 Java 的實(shí)現(xiàn)MVC設(shè)計(jì)模型的請(qǐng)求驅(qū)動(dòng)類(lèi)型的輕量級(jí)Web框架,屬于Spring框架的一個(gè)模塊。 它通過(guò)一套注解,讓一個(gè)簡(jiǎn)單的Java類(lèi)成為處理請(qǐng)求的控制器,而無(wú)須實(shí)現(xiàn)任何接口。同時(shí)它還支持RESTful編程風(fēng)格的請(qǐng)求。 MVC的全名是 Model View Controller ,是模型(model)-視圖

    2023年04月22日
    瀏覽(20)
  • 一天吃透SpringBoot面試八股文

    內(nèi)置servlet容器,不需要在服務(wù)器部署 tomcat。只需要將項(xiàng)目打成 jar 包,使用 java -jar xxx.jar一鍵式啟動(dòng)項(xiàng)目 SpringBoot提供了starter,把常用庫(kù)聚合在一起,簡(jiǎn)化復(fù)雜的環(huán)境配置,快速搭建spring應(yīng)用環(huán)境 可以快速創(chuàng)建獨(dú)立運(yùn)行的spring項(xiàng)目,集成主流框架 準(zhǔn)生產(chǎn)環(huán)境的運(yùn)行應(yīng)用監(jiān)控

    2024年02月01日
    瀏覽(18)
  • 一天吃透Java并發(fā)面試八股文

    內(nèi)容摘自我的學(xué)習(xí)網(wǎng)站:topjavaer.cn 分享50道Java并發(fā)高頻面試題 。 線(xiàn)程池:一個(gè)管理線(xiàn)程的池子。 嗯,手動(dòng)創(chuàng)建線(xiàn)程有兩個(gè)缺點(diǎn) 不受控風(fēng)險(xiǎn) 頻繁創(chuàng)建開(kāi)銷(xiāo)大 為什么不受控 ? 系統(tǒng)資源有限,每個(gè)人針對(duì)不同業(yè)務(wù)都可以手動(dòng)創(chuàng)建線(xiàn)程,并且創(chuàng)建線(xiàn)程沒(méi)有統(tǒng)一標(biāo)準(zhǔn),比如創(chuàng)建的

    2024年02月08日
    瀏覽(26)
  • 一天吃透操作系統(tǒng)八股文

    并發(fā):同一段時(shí)間內(nèi)多個(gè)程序執(zhí)行(與并行區(qū)分,并行指的是同一時(shí)刻有多個(gè)事件,多處理器系統(tǒng)可以使程序并行執(zhí)行) 共享:系統(tǒng)中的資源可以被內(nèi)存中多個(gè)并發(fā)執(zhí)行的進(jìn)線(xiàn)程共同使用 虛擬:通過(guò)分時(shí)復(fù)用(如分時(shí)系統(tǒng))以及空分復(fù)用(如虛擬內(nèi)存)技術(shù)把一個(gè)物理實(shí)體

    2023年04月15日
    瀏覽(25)
  • 一天吃透計(jì)算機(jī)網(wǎng)絡(luò)八股文

    計(jì)算機(jī)網(wǎng)絡(luò)體系大致分為三種,OSI七層模型、TCP/IP四層模型和五層模型。一般面試的時(shí)候考察比較多的是五層模型。最全面的Java面試網(wǎng)站 五層模型 :應(yīng)用層、傳輸層、網(wǎng)絡(luò)層、數(shù)據(jù)鏈路層、物理層。 應(yīng)用層 :為應(yīng)用程序提供交互服務(wù)。在互聯(lián)網(wǎng)中的應(yīng)用層協(xié)議很多,如域

    2023年04月09日
    瀏覽(28)
  • 吃透SpringMVC面試八股文

    SpringMVC是一種基于 Java 的實(shí)現(xiàn)MVC設(shè)計(jì)模型的請(qǐng)求驅(qū)動(dòng)類(lèi)型的輕量級(jí)Web框架,屬于Spring框架的一個(gè)模塊。 它通過(guò)一套注解,讓一個(gè)簡(jiǎn)單的Java類(lèi)成為處理請(qǐng)求的控制器,而無(wú)須實(shí)現(xiàn)任何接口。同時(shí)它還支持RESTful編程風(fēng)格的請(qǐng)求。 MVC的全名是 Model View Controller ,是模型(model)-視圖

    2023年04月20日
    瀏覽(18)
  • 吃透Redis面試八股文

    Redis連環(huán)40問(wèn),絕對(duì)夠全! Redis( Remote Dictionary Server )是一個(gè)使用 C 語(yǔ)言編寫(xiě)的,高性能非關(guān)系型的鍵值對(duì)數(shù)據(jù)庫(kù)。與傳統(tǒng)數(shù)據(jù)庫(kù)不同的是,Redis 的數(shù)據(jù)是存在內(nèi)存中的,所以讀寫(xiě)速度非常快,被廣泛應(yīng)用于緩存方向。Redis可以將數(shù)據(jù)寫(xiě)入磁盤(pán)中,保證了數(shù)據(jù)的安全不丟失,

    2023年04月24日
    瀏覽(27)
  • 三天吃透Java面試八股文(2023最新整理),面試通過(guò)率高達(dá)90%

    三天吃透Java面試八股文(2023最新整理),面試通過(guò)率高達(dá)90%

    什么樣的求職者能夠獲得面試官的青睞?求職者需要準(zhǔn)備哪些內(nèi)容來(lái)面對(duì)形形色色的面試官?這兩份資料是我在幾十場(chǎng)面試中被面試官問(wèn)到的問(wèn)題, 比其他復(fù)制粘貼的面試題強(qiáng)一百倍 ,堪稱(chēng) 全網(wǎng)最強(qiáng) (我不太喜歡“全網(wǎng)最強(qiáng)”這樣的字眼,但確實(shí)做到了全網(wǎng)最強(qiáng))。 寫(xiě)這

    2024年02月13日
    瀏覽(33)
  • java八股文面試[JVM]——JVM參數(shù)

    java八股文面試[JVM]——JVM參數(shù)

    參考:JVM學(xué)習(xí)筆記(一)_卷心菜不卷Iris的博客-CSDN博客 jdk1.7: jdk1.8: 面試題 :給定-Xms Xmx -Xmn 問(wèn) 最大的eden區(qū)域是多少M(fèi)。 常用JVM參數(shù) 怎么對(duì)jvm進(jìn)行調(diào)優(yōu)?通過(guò) 參數(shù)配置 參數(shù) 備注 -Xms 初始堆大小。只要啟動(dòng),就占用的堆大小,默認(rèn)是內(nèi)存的1/64 -Xmx 最大堆大小。默認(rèn)是內(nèi)存

    2024年02月11日
    瀏覽(30)
  • java八股文面試[JVM]——JVM內(nèi)存結(jié)構(gòu)

    java八股文面試[JVM]——JVM內(nèi)存結(jié)構(gòu)

    參考: JVM學(xué)習(xí)筆記(一)_卷心菜不卷Iris的博客-CSDN博客 JVM 是運(yùn)行在操作系統(tǒng)之上的,它與硬件沒(méi)有直接的交互 JVM內(nèi)存結(jié)構(gòu): ? 方法區(qū):存儲(chǔ)已被虛擬機(jī)加載的類(lèi)元數(shù)據(jù)信息(元空間) 堆:存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存 虛擬機(jī)棧:虛擬機(jī)棧描述的是

    2024年02月12日
    瀏覽(25)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包