您好,登錄后才能下訂單哦!
這篇文章主要講解了“java中JVM內存模型的介紹”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“java中JVM內存模型的介紹”吧!
首先你應該知道,運行一個 Java 應用程序,我們必須要先安裝 JDK 或者 JRE 。這是因為 Java 應用在編譯后會變成字節碼,然后通過字節碼運行在 JVM 中,而 JVM 是 JRE 的核心組成部分。
JVM 不僅承擔了 Java 字節碼的分析(JIT compiler)和執行(Runtime),同時也內置了自動內存分配管理機制。這個機制可以大大降低手動分配回收機制可能帶來的內存泄露和內存溢出風險,使 Java 開發人員不需要關注每個對象的內存分配以及回收,從而更專注于業務本身。
這個機制在提升 Java 開發效率的同時,也容易使 Java 開發人員過度依賴于自動化,弱化對內存的管理能力,這樣系統就很容易發生 JVM 的堆內存異常、垃圾回收(GC)的不合適以及 GC 次數過于頻繁等問題,這些都將直接影響到應用服務的性能。
JVM 內存模型共分為5個區:堆(Heap)
、方法區(Method Area)
、程序計數器(Program Counter Register)
、虛擬機棧(VM Stack)
、本地方法棧(Native Method Stack)
。
其中,堆(Heap)
、方法區(Method Area)
為線程共享
,程序計數器(Program Counter Register)
、虛擬機棧(VM Stack)
、本地方法棧(Native Method Stack)
為線程隔離
。
堆是 JVM 內存中最大的一塊內存空間,該內存被所有線程共享,幾乎所有對象和數組都被分配到了堆內存中。
堆被劃分為新生代和老年代,新生代又被進一步劃分為 Eden 區和 Survivor 區,最后 Survivor 由 From Survivor 和 To Survivor 組成。
隨著 Java 版本的更新,其內容又有了一些新的變化: >在 Java6 版本中,永久代在非堆內存區;到了 Java7 版本,永久代的靜態變量和運行時常量池被合并到了堆中;而到了 Java8,永久代被元空間
(處于本地內存)取代了。
為什么要用元空間
替換永久代呢?
為了融合 HotSpot JVM 與 JRockit VM,因為 JRockit 沒有永久代,所以不需要配置永久代。
永久代內存經常不夠用或發生內存溢出(應該是 JVM 中占用內存最大的一塊),產生異常 java.lang.OutOfMemoryError: PermGen
。在 JDK1.7 版本中,指定的 PermGen 區大小為 8M,由于 PermGen 中類的元數據信息在每次 FullGC 的時候都可能被收集,回收率都偏低,成績很難令人滿意;還有,為 PermGen 分配多大的空間很難確定,PermSize 的大小依賴于很多因素,比如,JVM 加載的 class 總數、常量池的大小和方法的大小等。
看到這兒,自然就想到了 GC 回收算法,不用急,我會在之后的文章中進行講解,現在還是以 JVM 內存模型為主。
什么是方法區? >方法區主要是用來存放已被虛擬機加載的類相關信息,包括類信息
、常量池
(字符串常量池以及所有基本類型都有其相應的常量池)、運行時常量池
。這其中,類信息又包括了類的版本、字段、方法、接口和父類等信息。
JVM 在執行某個類的時候,必須經過加載、連接、初始化,而連接
又包括驗證、準備、解析三個階段。
在加載類的時候,JVM 會先加載 class 文件,而在 class 文件中便有類的版本、字段、方法和接口等描述信息,這就是類信息
。
在 class 文件中,除了類信息
,還有一項信息是常量池 (Constant Pool Table),用于存放編譯期間生成的各種字面量
和符號引用
。
那字面量
和符號引用
又是什么呢?
字面量包括字符串(String a=“b”)、基本類型的常量(final 修飾的變量),符號引用則包括類和方法的全限定名(例如 String 這個類,它的全限定名就是 Java/lang/String)、字段的名稱和描述符以及方法的名稱和描述符。
當類加載到內存后,JVM 就會將 class 文件常量池
中的內容存放到運行時常量池
中;在解析階段,JVM 會把符號引用替換為直接引用(對象的索引值)。
例如: >類中的一個字符串常量在 class 文件中時,存放在 class 文件常量池中的。 > >在 JVM 加載完類之后,JVM 會將這個字符串常量
放到運行時常量池
中,并在解析階段,指定該字符串對象的索引值。
運行時常量池
是全局共享的,多個類共用一個運行時常量池,因此,class 文件中常量池多個相同的字符串在運行時常量池只會存在一份。
講到這里,大家是不是有些頭暈了,說實話,我在看到這些內容的時候,也是云里霧里的,這里舉個例子幫助大家理解:
public static void main(String[] args) { String str = "Hello"; System.out.println((str == ("Hel" + "lo"))); String loStr = "lo"; System.out.println((str == ("Hel" + loStr))); System.out.println(str == ("Hel" + loStr).intern()); }
其運行結果為:
true false true
第一個為 true,是因為在編譯成 class 文件時,能夠識別為同一字符串的, JVM 會將其自動優化成字符串常量,引用自同一 String 對象。
第二個為 false,是因為在運行時創建的字符串具有獨立的內存地址,所以不引用自同一 String 對象。
最后一個為 true,是因為 String 的 intern() 方法會查找在常量池中是否存在一個相等(調用 equals() 方法結果相等)的字符串,如果有則返回該字符串的引用,如果沒有則添加自己的字符串進入常量池。
OutOfMemoryError
出現在方法區無法滿足內存分配需求的時候,比如一直往常量池中加入數據,運行時常量池
就會溢出,從而報錯。
程序計數器是一塊很小的內存空間,主要用來記錄各個線程執行的字節碼的地址,例如,分支、循環、跳轉、異常、線程恢復等都依賴于計數器。
由于 Java 是多線程語言,當執行的線程數量超過 CPU 數量時,線程之間會根據時間片輪詢爭奪 CPU 資源。如果一個線程的時間片用完了,或者是其它原因導致這個線程的 CPU 資源被提前搶奪,那么這個退出的線程就需要單獨的一個程序計數器,來記錄下一條運行的指令。
由此可見,程序計數器和上下文切換有關。
>虛擬機棧是線程私有的內存空間,它和 Java 線程一起創建。 > >當創建一個線程時,會在虛擬機棧中申請一個線程棧,用來保存方法的局部變量、操作數棧、動態鏈接方法和返回地址等信息,并參與方法的調用和返回。 > >每一個方法的調用都伴隨著棧幀的入棧操作,方法的返回則是棧幀的出棧操作。
可以這么理解,虛擬機棧針對當前 Java 應用中所有線程,都有一個其相應的線程棧,每一個線程棧都互相獨立、互不影響,里面存儲了該線程中獨有的信息。
StackOverflowError
出現在棧內存設置成固定值的時候,當程序執行需要的棧內存超過設定的固定值時會拋出這個錯誤。
OutOfMemoryError
出現在棧內存設置成動態增長的時候,當JVM嘗試申請的內存大小超過了其可用內存時會拋出這個錯誤。
>本地方法棧跟虛擬機棧的功能類似,虛擬機棧用于管理 Java 方法的調用,而本地方法棧則用于管理本地方法的調用。 > >但本地方法并不是用 Java 實現的,而是由 C 語言實現的。
也就是說,本地方法棧中并沒有我們寫的代碼邏輯,其由native
修飾,由 C 語言實現。
感謝各位的閱讀,以上就是“java中JVM內存模型的介紹”的內容了,經過本文的學習后,相信大家對java中JVM內存模型的介紹這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。