1、深入理解JVM內存區域

一、爲什麼要了解虛擬機

JVM 不單單隻支持 Java 語言,也支持其他語言(Scala、Kotlin、Groovy 等等)區塊鏈 2.0–以太坊(比特幣是區塊鏈 1.0) 中提供了 EVM 的虛擬機,它的實現和 JVM 類似,基於棧、生成腳本編譯成字節碼來執行。知 識通用。(理論大於實際)

二、虛擬機的內存區域

虛擬機內存區域
從線程的角度看
在這裏插入圖片描述

運行時數據區

這個是抽象概念,內部實現依賴寄存器、高速緩存、主內存(具體要分析 JVM 源碼 C++語言實現,沒必要看) 計算機的運行=指令+數據,指令用於執行方法的,數據用於存放數據和對象的。個人理解爲java進程運行的地址空間和指令加數據

線程私有的區域

1、程序技術器

較小的內存空間,當前線程執行的字節碼的行號指示器;各線程之間獨立存儲,互不影響。
如果線程正在執行的是一個 Java 方法,則指明當前線程執行的代字節碼行數
如果正在執行的是 Natvie 方法,這個計數器值則爲空(Undefined)
此內存區域是唯一一個不會出現 OutOfMemoryError 情況的區域。

2、虛擬機棧

概念:每個線程私有的,線程在運行時,在執行每個方法的時候都會打包成一個棧幀,存儲了局部變量表,操作數棧,動態鏈接,方法出口等信息,然後放入 棧。每個時刻正在執行的當前方法就是虛擬機棧頂的棧楨。方法的執行就對應着棧幀在虛擬機棧中入棧和出棧的過程。

棧的大小缺省爲 1M,可用參數 –Xss 調整大小,例如-Xss256k

在編譯程序代碼的時候,棧幀中需要多大的局部變量表,多深的操作數棧都已經完全確定了,並且寫入到方法表的 Code 屬性之中,因此一個棧幀需要分 配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決於具體的虛擬機實現。

**局部變量表:**顧名思義就是局部變量的表,用於存放我們的局部變量的。首先它是一個 32 位的長度,主要存放我們的 Java 的八大基礎數據類型,一般 32 位就可以存放下,如果是 64 位的就使用高低位佔用兩個也可以存放下,如果是局部的一些對象,比如我們的 Object 對象,我們只需要存放它的一個引用 地址即可。(基本數據類型、對象引用、returnAddress 類型)

操作數據棧:存放我們方法執行的操作數的,它就是一個棧,先進後出的棧結構,操作數棧,就是用來操作的,操作的的元素可以是任意的 java 數據類 型,所以我們知道一個方法剛剛開始的時候,這個方法的操作數棧就是空的,操作數棧運行方法是會一直運行入棧/出棧的操作

動態連接:Java 語言特性多態(需要類加載、運行時才能確定具體的方法,後續有詳細的講解)

返回地址:正常返回(調用程序計數器中的地址作爲返回)

3、本地方法棧

各虛擬機自由實現,本地方法棧 native 方法調用 JNI 到了底層的 C/C++(c/c++可以觸發彙編語言,然後驅動硬件)

線程共享的區域

方法區

用於存儲已經被虛擬機加載的類信息,常量(“zdy”,"123"等),靜態變量(static 變量)等數據,可用以下參數調整: jdk1.7 及以前:-XX:PermSize;-XX:MaxPermSize;
jdk1.8 以後:-XX:MetaspaceSize; -XX:MaxMetaspaceSize
jdk1.8 以後大小就只受本機總內存的限制
如:-XX:MaxMetaspaceSize=3M

用於存儲已經被虛擬機加載的類信息,常量(“zdy”,"123"等),靜態變量(static 變量)等數據,可用以下參數調整: jdk1.7 及以前:-XX:PermSize;-XX:MaxPermSize;
jdk1.8 以後:-XX:MetaspaceSize; -XX:MaxMetaspaceSize
jdk1.8 以後大小就只受本機總內存的限制
如:-XX:MaxMetaspaceSize=3M

運行時常量池

存儲符號引用等數據。
一個 java 類(假設爲 People 類)被編譯成一個 class 文件時,如果 People 類引用了 Tool 類,但是在編譯時 People 類並不知道引用類的實際內存地址,因 此只能使用符號引用來代替。

而在類裝載器裝載 People 類時,此時可以通過虛擬機獲取 Tool 類的實際內存地址,因此便可以既將符號 org.simple.Tool 替換爲 Tool 類的實際內存地址, 及直接引用地址。

即在編譯時用符號引用來代替引用類,在加載時再通過虛擬機獲取該引用類的實際地址. 以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現的內存佈局是無關的, 引用的目標不一定已經加載到內存中
字面量
文本字符串 String a = “abc”,這個 abc 就是字面量 八種基本類型 int a = 1; 這個 1 就是字面量 聲明爲 final 的常量

三、直接內存

在這裏插入圖片描述
使用 Native 函數庫直接分配堆外內存(NIO)
並不是 JVM 運行時數據區域的一部分,但是會被頻繁使用(可以通過-XX:MaxDirectMemorySize 來設置(默認與堆內存最大值一樣,也會 出現 OOM 異常)

避免了在 Java 堆和 Native 堆中來回複製數據,能夠提高效率
測試用例 JavaStack:設置 JVM 參數-Xmx100m,運行異常,因爲如果沒設置-XX:MaxDirectMemorySize,則默認與-Xmx 參數值相同,分 配 128M 直接內存超出限制範圍

四、堆、棧辨析

  • 站在線程角度來看
    虛擬機棧、本地方法棧、程序計數器三個區域的生命週期和線程相同。
    線程共享區域:就複雜多了

  • 功能
     以棧幀的方式存儲方法調用的過程,並存儲方法調用過程中基本數據類型的變量(int、short、long、byte、float、
    double、boolean、char 等)以及對象的引用變量,其內存分配在棧上,變量出了作用域就會自動釋放;
     而堆內存用來存儲 Java 中的對象。無論是成員變量,局部變量,還是類變量,它們指向的對象都存儲在堆內
    存中;

  • 線程獨享還是共享
    棧內存歸屬於單個線程,每個線程都會有一個棧內存,其存儲的變量只能在其所屬線程中可見,即棧內存可
    以理解成線程的私有內存。

    堆內存中的對象對所有線程可見。堆內存中的對象可以被所有線程訪問

棧溢出
java.lang.StackOverflowError
指的是單個線程的棧數據被壓滿,例如死遞歸,會不斷的創建棧幀壓入虛擬機棧,直到虛擬機棧被打滿。

虛擬機棧帶給我們的啓示:方法的執行因爲要打包成棧楨,所以天生要比實現同樣功能的循環慢,所以樹的遍歷算 法中:遞歸和非遞歸(循環來實現)都有存在的意義。遞歸代碼簡潔,非遞歸代碼複雜但是速度較快。

OutOfMemoryError
整個棧區域被打滿,不斷的創建線程就可以達到。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章