JVM1 內存模型/對象訪問定位

前言:
介紹JVM的內存模型和內存溢出異常

.java .class(字節碼文件)

  1. javac : 編譯指令
  2. Javadoc 指令:它從程序源代碼中抽取類、方法、成員等註釋形成一個和源代碼配套的API幫助文檔。也就是說,只要在編寫程序時以一套特定的標籤作註釋,在程序編寫完成後,通過Javadoc就可以同時形成程序的開發文檔了。
    javadoc命令是用來生成自己API文檔的,類或者方法上通過標記進行解釋如:@Author。javadoc 會抽取這些標記,形成和源代碼配套的API幫助文檔。
    使用方式:使用命令行在目標文件所在目錄輸入javadoc +文件名.java。
  3. javap :反彙編指令,可以進行反彙編查看編譯之後的字節碼文件
  4. jar :java archive java 歸檔,包含了大量的Java類文件和相關數據文件

Java虛擬機向多語言虛擬機發展,可以在JVM上面運行的語言越來越多,並不只是Java

Java8 中添加了Lambda 更加適合於函數式編程(函數式編程更加適合於並行)


一:JVM 的內存模型

JVM在內存中有一塊自己的管理區域,執行時會將這塊區域劃分爲不同的數據區,有些區域隨JVM進程的啓動而存在,有些區域則依賴用戶線程的啓動/結束而創建/銷燬

  1. 程序計數器(Program counter Register): 線程私有,相互獨立,指向下一條將要執行的字節碼
  2. 虛擬機棧(VM stock) : 線程私有,與線程的生命週期相同。描述Java方法執行的內存模型:每個方法執行時會創建一個棧幀:用於存儲局部變量表,操作數棧,動態鏈接。方法的調用執行到結束就是一個棧幀入棧到出棧的過程
    局部變量表存儲了編譯時期可知的基本數據類型
  3. 本地方法棧(Native Method stock):與虛擬機棧作用相似,VMStock 中是虛擬機執行的java 方法,Native Method Stock 是虛擬機執行的Native 方法。
  4. Java 堆(java Heap):虛擬機啓動時創建,所有的對象實例,數組都要在對上面進行分配,也是垃圾收集管理的主要區域,GC堆(Garbage Collection Heap),要求在內存中不一定物理連續,但是一定要邏輯連續
  5. 方法區(Method Area):存儲被加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼
    這個區域的內存回收主要針對常量池的回收會對類型的卸載。

JVM的內存模型:
在這裏插入圖片描述
方法區中有一個重要的部分: 運行時常量池
JDK1.7 以前字符串常量池在Pergem區中(永久帶),大小固定,不能被垃圾回收,大量使用字符串native 方法intern 會造成(JDK1.6 以前常量池也在永久帶中)
JDK1.7 以後虛擬機去永久化發展
JDK1.7 之後字符串常量區就在堆中,可以被垃圾回收

對象創建

虛擬機意見new 指令時,會先檢查是否能在常量池中定義到一個類的符號引用,檢查這個符號引用是否被加載,如果沒有加載,要先加載類。
爲對象實例分配內存空間,類加載之後就可以確定對象所需的內存空間

內存分配的方式

如果java 堆內存是絕對規整的,那麼所有用過的內存放在一邊,沒有用過的放在一邊,中間用一個指針作爲分界指示器,分配內存就只是將指針向空閒的區域移動對象大小的空間,這叫 “指針碰壁”
如果Java堆內存不是規整的,那麼虛擬機要維護一個列表,記錄Java堆的內存使用情況,當爲對象實例分配內存的時候,從列表中找一塊適合的空間分配給對象,這叫 “空閒列表”
Java堆空間是否規整取決於垃圾收集器是否帶有壓縮整理功能
內存分時還要考慮,分配是否安全,以防出現,爲A分配空間,指針還沒來得及修改,B就使用了那片空間。解決方案,一是: 保證更新方式對的原子性;二是: 把內存的分配動作按照線程劃分在不同的空間中進行,每個線程預先在Java堆中分配一個內存(TLBA),每個線程在自己的空間中完成對象的內存分配,只有當TLBA 用完並分配新的TLBA時,保證操作的原子性
內存分配之後,內存要將分配的內存空間進行初始化,如果使用TLBA,這個擦歐總可以提前到TLBA進行內存分配時執行,對象中的字段,進行對應類型的零值初始化。
在虛擬機的角度,對象的創建已經完成,但是在程序員的角度對象的創建纔開始,new 指令之後,執行 方法,按照程序員的意願對實例進行初始化。

對象的內存佈局

對象自己的存儲佈局:對象頭,實例數據,對齊填空

  1. 對象頭包含兩部分:一是對象自己的運行時數據(哈希表,GC年齡帶,鎖狀態標誌,線程持有的鎖…) 對象頭中的數據是與對象自身定義的數據無關的數據。
    另一部分信息是類型指針,JVM 通過這個來確定這個對象是那個類的實例
  2. 實例數據纔是纔是對象存儲的真正有意義的數據,也是程序中定義的各種字段,包括自己的,從父類繼承的。

JVM通過對象頭的信息得到對象的大小。

對象的訪問定位

JVM通過虛擬機棧中的reference 對對象進行訪問。reference 只規定了一個引用但是並沒有規定JVM如何通過這個引用去訪問Java堆中的對象,

  1. 帶有句柄池,直接指針。reference 存儲對象句柄的地址。
    在這裏插入圖片描述
  2. reference直接存儲Java堆中對象的地址
    在這裏插入圖片描述
    reference 直接指向對象實例數據,當的對象移動時(這在堆中十分常見)就需要修改reference 的值,但是這樣的訪問速度快
    使用句柄池,對象移動之後就只需要修改句柄池中指向對象實例數據的指針

二:內存溢出異常:OutOfMenoryError

介紹內存溢出的另一個原因是爲了,高清楚一些數據到底保存在JVM的那塊區域

  1. Java堆溢出
    堆中的對象太多,並且JVM沒有進行垃圾回收就會出現溢出
  2. 虛擬棧和本地方法棧的溢出
    1. 線程請求的深度大於JVM所允許的最大深度(StackOverFlower),操作系統爲每個線程分配的內存空間是有限制的,當該線程請求的棧內存大於這個限制就會拋出StackOverFlower
    2. 當有多個線程,虛擬機擴展棧時無法申請到空間(OutOfMenoryError)
    這雖是兩種情況,但本質上是描述的一個問題。在單線程的情況下只會出現第一種情況的異常
  3. 方法區溢出:方法去存放CLAss相關信息,類名,訪問修飾符,運行時常量池,字段描述,方法描述等,大量的運行時產生的類填滿這個區域就會產生溢出
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章