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