《深入理解Java虛擬機》 Java對象的生命周期


  • Java虛擬機運行時數據區
    • 方法區:存儲 類信息、常量、靜態變量、即使編譯器編譯后的代碼等數據,也有別名叫做非堆。  方法區其中有包含有 運行時常量池,用於存放編譯期生成的各種字面量和符號引用。其中,可通過String.intern()方法將字符串放入運行時常量池中。
    • 堆:存儲的是類實例對象,數組。  JVM 所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。  從內存回收的角度來看,由於現在收集器基本都采用 分代收集算法,所以堆可以細分為 新生代 和老年代;再細分 新生代可以分為:Eden空間,From Survivor空間和To Survivor空間等。
    • 虛擬機棧:每個方法在執行的同時都會創建一個棧幀用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧和出棧的過程。
    • 本地方法棧: 本地方法棧服務於虛擬機執行Native方法服務。作用與虛擬機棧相似。
    • 程序計數器:程序計數器是一塊較小的內存空間,它可以看作是當前線程所執行的字節碼的行號指示器,字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。每條線程都需要有一個獨立的程序計數器  
  • Java類加載機制
    • 裝載
      • 加載方式:
        • 從本地系統中直接加載
        • 通過網絡下載class文件
        • 從歸檔文件中加載class文件
        • 從專有數據庫中提取class文件
        • 將Java源文件 動態編譯為class文件,也就是運行時計算而成
        • 從加密文件中獲取
    • 連接
      • 驗證
      •  驗證java版本號,文件格式,
            元數據校驗(是否有父類,是否繼承了final類等java 語法) 
            字節碼驗證(運行檢查,棧數據類型和操作碼操作參數是否吻合)
      • 准備
        •   private static final int a =1;  constantValue 通知虛擬機生成常量賦值,不需要開辟內存。  基於final static 修飾的 基本數據類型和String起作用
      • 解析
        •   將常量池內的符號引用轉變成直接引用
    • 初始化
      • 初始化什么時候被觸發?    類 主動使用到的時候
                      1 創建類的實例,也就是new
                      2 訪問某個類或者接口的靜態變量,給該靜態變量賦值
                      3 調用類的靜態方法
                      4.反射 (class.forname("..."))
                      5.初始化某個類的子類,則其父類也會被初始化
                      6.java 虛擬機啟動時被標明為啟動類的類(如springboot啟動類)
    • 使用
    • 卸載
      •   1.該類所有的實例都已經被回收,即java堆中不存在該類的任何實例
             2. 加載該類的classloader  已經被回收
             3. 該類對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法
  • 類加載器
    • 類加載器的加載特性:
      •  全盤類型機制 
      • 父類委托   
      • 緩存機制    
    • 加載完之后開始使用類,此時需要運行時數據區
      1. PC寄存器
      2. 本地方法棧
      3. 虛擬機棧
      4. 堆:裝載的時候,存儲所有class實例  空間不足拋出oom
      5. 方法區 :
        1. 線程共享區域,class結構信息,運行時常量池,方法,構造器,方法數據,靜態定義
        2. 內存不夠時,拋出OOM
      6. 運行時常量池(在方法區中):包含字符串常量池

    • 動態鏈接:
       符號引用變成直接引用會改變這個動態鏈接屬性
  • Java對象內存布局
    •   Java對象內存分為三部分: 對象頭,實例數據,對齊填充
    • 對象頭
      •   Mark Word : 哈希碼,分代年齡,線程持有的鎖,偏向鎖ID,偏向時間戳,鎖狀態,還有1bit的占位符
      •   class Pointer: 指的是類型指針,對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例
    •  實例數據

      •   對象真正存儲的有效信息,代碼中所定義的各種類型的字段內容。無論是從父類繼承下來的,還是在子類中定義的,都需要記錄起來。這部分的存儲順序會受到虛擬機分配策略參數和字段在Java遠嗎中定義順序的影響。HotSpot虛擬機默認的分配策略為longs/doubles、ints、shorts/chars、bytes/booleans、oops,從分配策略中可以看出,先攻寬度的字段總是被分配到一起的。在滿足這個前提條件的情況下,在父類中定義的變量會出現在子類之前。
    • 對齊填充
      •   HotSpot VM 要求對象大小必須是8字節的整數倍
  • 對象定位方式:
    •   建立對象是為了使用對象,我們的Java程序需要通過棧上的reference數據來操作對上的具體對象。目前主流的訪問方式有兩種:使用句柄和直接指針兩種方式
    •  使用句柄訪問的最大好處就是reference中存儲的是穩定的句柄地址,在對象被移動(垃圾回收)時,只會改變句柄中的實例數據指針,而reference本身不修改。

    • 使用直接指針訪問方式的好處是速度更快,節省了中間轉發訪問的步驟。
  • Java對象的生命周期
    • 創建階段
    • 應用階段
    • 不可見階段
    • 不可達階段
    • 收集階段
    • 終結階段
    • 空間重分配階段
    • 對象創建過程:
    • 判斷對象是否已經“死”了的算法有兩種: 
      •   引用計數算法: 給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的情況,都是不可能再被使用的。
        •   引用計數算法的缺陷就是它很難解決對象之間相互循環引用的問題
      •        可達性分析算法:通過一系列的成為“GC Roots"的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑成為引用鏈,當一個對象到GC roots沒有任何引用鏈相連時,則證明此對象是不可用的。
      • 在Java中,可作為GC roots的對象包括下面幾種:
        •   虛擬機棧(棧幀中的本地變量表)中引用的對象
        •   方法區中類靜態屬性引用的對象
        •        方法區中常量引用的對象
        •       本地方法棧中JNI(即一般說的Native方法)引用的對象。
    • 引用分類:

      •  強引用:只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象 

      • 軟引用:對於軟引用關聯着的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收范圍之中進行第二次回收
      • 弱引用: 被弱引用關聯的對象只能生存到下一次GC發生之前。
      • 虛引用: 為一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。
    • 垃圾收集算法
      •   標記-清除算法
        •   不足:
          •   效率問題:標記和清除兩個過程的效率不高
          •        空間問題:標記清除后會產生大量不連續的內存碎片,碎片太多導致以后再程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
      •        復制算法:將可用的內存按容量划分大小相等的涼快,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象復制到另一塊上面,然后再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,簡單高效。
        •      不足:
          •   當對象存活率較高時,要進行較多的復制操作,效率會降低。
      •   標記-整理算法:標記過程仍然與”標記-清除算法一樣,后續讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存。
      •        分代收集算法:
      • 這種算法並沒有什么新的思想,只是根據對象存活周期的不同將內存划分為幾塊。一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適合的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成。而老年代中因為對象存活率高、沒有額外空間對他進行分配擔保,就必須使用“標記-清理”或者“標記-整理”算法來進行回收。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM