JVM 進階知識


Java的跨平台性

    Java具有跨平台性,無疑是JVM底層翻譯出來的匯編指令的不同,Unix和Window系統的匯編指令是不同的,Windows派系采用的是Intel匯編,Unix派系采用的是AT&T匯編。無論在哪個平台上編寫的Java文件,編譯后的class文件,放在哪個平台上都可以執行,只要下載平台相對應的JDK,都可以執行這個class文件,從而翻譯出跟平台相對應的匯編指令


class文件、class content、class對象、對象四個基礎概念

    class文件: 這個應該不用多說,就是java文件編譯后的class文件

    class content: 首先來看一張類加載的圖

    我們的class文件是存在在硬盤上的,需要將文件從硬盤讀取到內存中,通過類加載將class文件內容加載到內存中,則會在類加載子系統中形成一塊class content表示class 文件內容

    class對象:用過反射的網友應該清楚Class<T>這個對象,這就是class對象,類加載器通過解析class content內容則會在方法區(永久代)中形成Class<T>對象,像這個class對象的字段,方法的描述信息都會放在方法區中

    對象:對象則就是指我們new出來生成的對象


JDK1.8后元空間替代永久代

    1. 元空間使用的是直接內存,操作系統的內存。永久代會產生OOM

    2. 硬件的發展,之前的計算機硬件比較落后,沒多少內存使用。如果還讓JVM使用直接內存的話,會導致沒有多少內存供其它程序使用。隨着硬件的發展,現在的機器能夠達到32G 64G這樣的,有足夠的內存使用。32位的機器最大的內存就是4G,64位的機器最大的內存可以達到2的48次方,256T。64bit = 16(保留位) + 48


元空間默認大小

    最小20.75M,最大256T,相當於無限大。元空間調優技巧:最小最大設置成一樣大,防止忽大忽小,動態擴展。一般設置成物理內存的1 / 32。


虛擬機棧

    局部變量表:存放方法中聲明的局部變量

    操作數棧:變量需要進行運算所要用到的操作數棧

    動態鏈接:指向方法在方法區的內存地址,也就是指向下圖中的main方法對象或者add方法對象的內存地址

    返回地址:恢復現場。如果main方法調用了add方法,add方法執行完后,需要返回到main方法前面執行到的指令,則add方法中的返回地址則記錄了main方法中程序計數器所執行到字節碼指令


一個方法執行完JVM需要做的事情

    1. 恢復局部變量表指針

    2. 恢復操作數棧的指針

    3. 恢復程序計數器

    4. 如果方法有返回地址,則需要返回

    5. 清理棧幀(程序計數器做的)


new操作不是原子操作

    右邊的圖中我們可以看到new Test()所生成的字節碼指令,並不是只有一條指令,而是由四條指令組成的,所以它不是一個原子操作。

0 new 
3 dup
4 invokespecial #調用構造函數,初始化
7 astore_1

    這就是為什么在單例模式的雙檢查鎖DCL中,為什么要加volatile關鍵字了,防止這四個指令的重排序問題


每個非靜態方法,局部變量表index=0的位置永遠存放的都是this指針,有點類似於python里面每個方法的第一個參數是self的意思,this指針在執行dup指令時完成賦值,調用構造init方法需要用到


堆的默認大小

    默認大小是物理內存的1 / 64,最大是1 / 4


虛擬機棧 -> 方法區的聯系

    動態鏈接

堆 -> 方法區

    klass Pointer類型指針:對象執行類的Class對象的內存地址

方法區 -> 堆

    靜態變量:例如public static Test test = new Test(), 靜態屬性在方法區,對象在堆中


指針壓縮

    未開啟指針壓縮:內存地址占8字節。開啟指針壓縮:內存地址占4位


如何計算一個對象的大小

    要計算一個對象的大小,首先需要了解一下對象在JVM中的一個內存布局

    Mark Word:

        32位機:4個字節

        64位機:8個字節

    指針壓縮:

        開啟:4個字節

        未開啟:8個字節

    對齊填充:按8個字節對齊填充

    首先看一個簡單的對象,計算它的對象大小(引入jol-core包,可以查看對象大小)

    開啟指針壓縮的情況下,像這種沒有普通屬性的對象被稱為空對象,來看看它的對象大小16B是怎么算出來的

        首先是Mark Word = 8B,因為是64位的機器上

        其次是類型指針,默認開啟指針壓縮,則類型指針 = 4B

        數組長度 = 0B

        實例數據 = 0B

        對齊填充:8B + 4B不是8B對齊,需要填充4B,對齊填充= 4B

     最后,Test對象大小 = 8B + 4B + 0B + 0B + 4B = 16B

    關閉指針壓縮的情況下:Test對象大小還是16B = 8B + 8B + 0B + 0B + 0B

    來看一個具有普通屬性的對象

    我們可以看到該Test類開啟了指針壓縮,具有一個普通屬性a,short占2B,最后得出來的對象大小為16B = 8B(Mark Word) + 4B(類型指針) + 0B(數組長度) + 2B(實例數據) + 2B(對齊填充)

    關閉指針壓縮

    發現該Test對象大小為24B = 8B + 8B + 0B + 2B + 6B

    來看一下數組對象所占的內存大小

    我們可以看到開啟指針壓縮的情況下,arr數組對象占32B

    我們可以看到arr數組對象關閉指針壓縮的情況下,對象大小占40B,並且數組對象會有2個padding(對齊填充).

    什么情況下對象的內存布局會產生2個padding

        對象是數組對象並且關閉指針壓縮的情況下,會產生2個padding


開啟指針壓縮的優勢

    1. 節省JVM空間

    2. 提升JVM運行效率


JVM怎么做到在指針壓縮的情況下還能運行

    假設有三個對象test1 = 16B、test2 = 32B、test3 = 24B。

    在內存中的地址分別為:test1 = 0x00000、test2 = 0x10000、test3 = 0x30000

    因為JVM采用8字節對齊1000,后面三位一定是000。所以可以將test1, test2, test3后面三位抹掉,那么在儲存時就會變為test1= 0x00、test2 = 0x10、 test3 = 0x30,而在使用時,則將地址后面的三位000補充回來


開啟指針壓縮的情況下,一個oop(對象指針)能表示最大堆空間是多少

    我們知道開啟指針壓縮的情況下,類型指針占4字節,也就是32位,上一小節說到JVM儲存時會抹掉后面的3位,也就是可以保存32 + 3 = 35位,最大內存空間也就是2的35次方,32G


oop(對象指針)如何擴容

    前面說到JVM采用8字節對齊,會抹掉后面的3位,如果我們讓它采用16字節對齊,那么是不是可以抹掉最后面的4位,oop(對象指針)所能表示的堆空間則為2的32 + 4次方


JVM為什么不是16字節對齊

    如果采用16字節對齊的話,在使用時對齊填充浪費內存空間


虛擬機棧默認大小

    可以通過命令java -XX:+PrintFlagsFinal -version | grep ThreadStack棧大小為1024KB


虛擬機棧最小為160KB

    JVM對虛擬機棧做了最小的限制,限制為160KB

    我們可以看到通過-Xss參數設置虛擬機棧大小為100KB,發現控制台報錯,最小為160KB


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM