JVM學習筆記之JVM內存結構

JVM組成

在這裏插入圖片描述

  • Java源代碼編譯成Java Class文件後通過類加載器ClassLoader加載到JVM中
    • 類存放在方法區中
    • 類創建的對象存放在堆中
    • 堆中對象的調用方法時會使用到虛擬機棧,本地方法棧,程序計數器
    • 方法執行時每行代碼由解釋器逐行執行
    • 熱點代碼由JIT編譯器即時編譯
    • 垃圾回收機制回收堆中資源
    • 和操作系統打交道需要調用本地方法接口

JVM內存結構

  • 程序計數器(通過移位寄存器實現)
    在這裏插入圖片描述

    • 程序計數器是線程私有的,每個線程單獨持有一個程序計數器
    • 程序計數器不會內存溢出
  • 虛擬機棧

    • 棧:線程運行需要的內存空間

    • 棧幀:每一個方法運行需要的內存(包括參數,局部變量,返回地址等信息)

    • 每個線程只有一 個活動棧幀(棧頂的棧幀),對應着正在執行的代碼
      在這裏插入圖片描述

    • 常見問題解析

      • 垃圾回收是否涉及棧內存:不涉及,垃圾回收只涉及堆內存

      • 棧內存分配越大越好嗎:內存一定時,棧內存越大,線程數就越少,所以不應該過大

      • 方法內的局部變量是否是線程安全的:

        • 普通局部變量是安全的
        • 靜態的局部變量是不安全的
        • 對象類型的局部變量被返回了是不安全的
        • 基本數據類型局部變量被返回時安全的
        • 參數傳入對象類型變量是不安全的
        • 參數傳入基本數據類型變量時安全的
    • 棧內存溢出(StackOverflowError)

      • 棧幀過多

        • 如遞歸調用沒有正確設置結束條件
      • 棧幀過大

        • json數據轉換 對象嵌套對象 (用戶類有部門類屬性,部門類由用戶類屬性)
      • 線程運行診斷

        • CPU佔用過高(定位問題)

          • ‘top’命令獲取進程編號,查找佔用高的進程
          • ‘ps H -eo pid,tid,%cpu | grep 進程號’ 命令獲取線程的進程id,線程id,cpu佔用
          • 將查看到的佔用高的線程的線程號轉化成16進制的數 :如6626->19E2
          • ‘ jstack 進程id ’獲取進程棧信息, 查找‘nid=0X19E2’的線程
          • 問題線程的最開始‘#數字’表示出現問題的行數,回到代碼查看
        • 程序運行很長時間沒有結果(死鎖問題)

          • ‘ jstack 進程id ’獲取進程棧信息
          • 查看最後20行左右有無‘Fount one Java-level deadlock’
          • 查看下面的死鎖的詳細信息描述和問題定位
          • 回到代碼中定位代碼進行解決
  • 本地方法棧

    • 本地方法棧爲虛擬機使用到的 Native 方法服務
    • Native 方法是 Java 通過 JNI 直接調用本地 C/C++ 庫,可以認爲是 Native 方法相當於 C/C++ 暴露給 Java 的一個接口
    • 如notify,hashcode,wait等都是native方法
    • 通過new關鍵字創建的對象都會使用堆內存

    • 堆是線程共享的

    • 堆中有垃圾回收機制

    • 堆內存溢出(OutOfMemoryError)

      • 死循環創建對象
    • 堆內存診斷

      • 命令行方式

        • ‘jps’獲取運行進程號
        • ‘jmap -heap 進程號’查看當前時刻的堆內存信息
      • jconsole

        • 命令行輸入jconsole打開可視化的界面連接上進程
        • 可視化的檢測連續的堆內存信息
      • jvisualvm

        • 命令行輸入jvisualvm打開可視化界面選擇進程
        • 可視化的查看堆內存信息
  • 方法區

    • 方法區只是一種概念上的規範,具體的實現各種虛擬機和不同版本不相同
      • HotSpot1.6 使用永久代作爲方法區的實現
      • HotSpot1.8使用本地內存的元空間作爲方法區的實現(但StringTable還是放在堆中)
        在這裏插入圖片描述
  • StringTable特性

    • 常量池中的字符串僅是字符,第一次使用時才變爲對象

    • 利用串池機制,避免重複創建字符串

    • 字符串常量拼接原理是StringBuilder(1.8)

    • 字符串常量拼接原理是編譯器優化

    • StringTable在1.6中存放在永久代,在1.8中存放在堆空間

    • intern方法主動將串池中沒有的字符串對象放入串池

      • 1.8中:嘗試放入串池,如果有就不放入,只返回一個引用;如果沒有就放入串池,同時返回常量池中對象引用
      • 1.6中:嘗試放入串池,如果有就不放入,只返回一個引用;如果沒有就複製一個放進去(本身不放入),同時返回常量池中的對象引用
  • 字符串常量池分析(1.8環境)

String s1 = "a";
String s2 = "b";
String s3 = "a"+"b";
String s4 = s1+s2;
String s5 = "ab";
String s6 = s4.intern();


System.out.println(s3==s4);// s3在常量池中,s4在堆上(intern嘗試s4放入常量池,因爲ab存在了就拒絕放入返回ab引用給s6,s4還是堆上的)
System.out.println(s3==s5);// s3在常量池中,s4也在常量池中(字符串編譯期優化)
System.out.println(s3==s6);// s3在常量池中,s6是s4的intern返回常量池中ab的引用,所以也在常量池中


String x2 = new String("c")+new String("d");
String x1 = "cd";
x2.intern();

System.out.println(x1==x2);//x2調用intern嘗試放入常量池,但常量池中已經有cd了,所以只是返回一個cd的引用,而x2還是堆上的引用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章