一、概述
網上有許多介紹jvm的文章和視訊,但大多枯燥、晦澀難懂,很難讓小白堅持下去;如果你是一個小白,不用擔心,在本文中,我們将從0到1,一步步地介紹JVM的基本概念和工作原理。無論你是想深入了解Java語言,還是準備參加Java開發的面試,這篇文章都将為你提供有用的知識和技能。讓我們一起開始這段有趣的旅程吧!
二、正文
我們編寫的.java檔案,通過編譯生成對應.class位元組碼檔案,由類加載器加載,經曆驗證、準備、解析、初始化階段,每個階段前兩篇文章我們已經介紹過了,那麼接下來我們正式進入JVM記憶體區域這一主戰場。
JVM 記憶體共分為:虛拟機棧、堆、方法區、程式計數器、本地方法棧五個部分。
名稱 | 描述 | 用途 | 配置參數 | 異常 |
程式計數器 | 線程私有;生命周期與線程相同;占用記憶體小 | 位元組碼行号訓示器(程式運作到哪個位置了) | 無 | 無 |
虛拟機棧 | 線程私有;生命周期與線程相同;使用連續的記憶體空間 | 存儲棧幀(一個方法對應一個棧幀),棧幀中存儲:局部變量表、操作棧、動态連結、方法出口等資訊 | -xss (256k、512k等) | StackOverflowError/OutOfMemoryError |
堆 | 線程共享,生命周期與虛拟機相同,可不使用連續的記憶體位址 | 儲存對象執行個體,所有對象執行個體(new)、數組都在堆上配置設定 | 初始堆大小:-Xms 最大堆大小:-Xmx 年輕代的堆大小:-Xmn | OutOfMemoryError |
方法區(jdk1.8之前)/元空間(jdk1.8之後) | 線程共享,生命周期與虛拟機相同,可不使用連續的記憶體位址 | 存儲已被虛拟機加載的類元資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料 | 初始永久代大小:-XX:PermSize 最大永久代大小:-XX:MaxPermSize 初始元空間大小:-XX:MetaspaceSize 最大元空間大小:-XX:MaxMetaspaceSize | OutOfMemoryError |
本地方法棧 | 線程私有 | 為虛拟機使用到的Native方法服務 | 無 | StackOverflowError/OutOfMemoryError |
線程私有:程式計數器、虛拟機棧、本地方法棧
線程共享:堆、方法區/元空間
程式計數器
線程私有,目前線程所執行的位元組碼的行号訓示器(程式運作到哪了),例如:當線程擷取到CPU時間片,代碼正常執行到一半,這時CPU切到另一個線程中去執行,當CPU再次切回來的時候如何能保證代碼能正确執行而不會錯亂?此時就需要靠程式計數器去完成。
虛拟機棧
線程私有,生命周期和線程相同,用于存儲棧幀。每個方法在運作時,都會建立一個棧幀,棧幀中存儲局部變量表、操作數棧、動态連結、方法出口等資訊,每個方法從調用到執行完成的過程就對應着一個棧幀在虛拟機棧中從入棧到出棧的過程。
1、棧幀中為什麼不存在垃圾回收
棧幀(方法)每次執行結束自動出棧,是以不會涉及到垃圾的産生,也就不會對棧記憶體進行垃圾回收
2、什麼情況下會導緻棧記憶體溢出(Stack Overflow)
棧幀過多(方法過多),沒有足夠的棧記憶體空間來存儲更多的棧幀,導緻記憶體溢出, 将抛出StackOverflowError異常
比如:遞歸調用,不斷産生新的棧幀,前面的棧幀不釋放,很容易導緻記憶體溢出
棧幀過大,無法配置設定足夠的記憶體,也會導緻記憶體溢出
3、棧記憶體配置設定越大越好嗎?
不是的,假設配置設定的實體記憶體是100MB,每個線程棧大小是1MB,那麼可以配置設定100個線程;
但是如果提升了線程棧大小,那可以配置設定的對應線程數就變少了。Linux系統上預設就是1MB,當然我們可以通過-Xss進行大小的更改
4、方法内的局部變量是否線程安全?
不一定;如果方法内局部變量沒有逃離【方法的作用通路】,它是線程安全的;如果是局部變量引用了對象,并逃離【方法的作用範圍】,需要考慮線程安全
本地方法棧
線程私有,儲存native方法進入區域的位址,他是用來調用第三方庫所需要的一片棧空間,這裡的第三方庫指的是其他語言比如:c開發的庫;與虛拟機棧所發揮的作用是非常相似的,其差別隻是虛拟機棧為虛拟機執行Java方法(也就是位元組碼)服務,而本地方法棧則是為虛拟機使用到的本地(Native)方法服務。
堆
線程共享,通過 new 關鍵字建立對象、以及數組都會使用堆記憶體,堆是垃圾回器主要回收的區域。堆中對象都需要考慮線程安全的問題,預設情況下,初始堆記憶體大小(-Xms):電腦實體記憶體大小/64;最大堆記憶體大小(-Xmx):電腦實體記憶體大小/4;通常會将-Xms和-Xmx兩個參數配置相同的值,其目的是為了能夠在java垃圾回收機制清理完堆區後不需要重新分隔計算堆區的大小,進而提高性能
方法區
存儲類的資訊以及運作時常量池,在JDK8以後被元空間代替。方法區主要存放的是(class),而堆中主要存放的是(執行個體化的對象)