一文帶你看懂jvm虛擬機內存管理

運行時數據區

  • 程序計數器(線程私有)
    • 一塊較小的內存空間,可以看作是當前線程所執行的字節碼的行號指示器。如果線程正在執行的是一個java方法,記錄的是正在執行的虛擬機字節碼指令的地址;如果執行的是native方法,這個計數器值則爲空。注意:此內存區域是唯一 一個在java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域
  • java虛擬機棧(線程私有)
    • 虛擬機棧描述的是java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀(stack frame)用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。
    • 局部變量表存放了編譯期可知的各種基本數據類型、對象引用和returnAddress類型(指向了一條字節碼指令的地址) 注意:其中64位長度的long和double類型的數據會佔用2個局部變量空間(slot),其餘數據只佔用一個。
    • 棧區域的兩種異常:StackOverflowError 和 OutOfMemoryError異常
  • 本地方法棧(線程私有)
    • 本地方法棧是虛擬機使用到的native方法服務
  • java堆
    • 堆是虛擬機管理的內存中最大的一塊,java堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建
    • java堆細分:新生代和老年代。細緻一點Eden空間、from survivor空間、To survivor空間等。從內存分配的角度來看,線程共享的java堆中可能劃分出多個線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)
    • -Xmx和-Xms設置虛擬機堆內存是否可擴展
  • 方法區(別名Non-Heap 非堆)
    • 用於存儲已被虛擬機加載的類信息、常量、靜態變量、及時編譯器編譯後的代碼等數據表。
    • 運行時常量池是方法區的一部分。class文件中除了有類的版本、字段、方法、接口等描述信息,還有一項信息是常量池,用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載後進入方法區的運行時常量池中存放。
  • 直接內存
    • 在jdk1.4中新加入了nio類,引入了一種基於通道與緩衝區的i/o方式,它可以使用native函數庫直接分配堆外內存,然後通過一個存儲在java堆中directByteBuffer對象作爲這塊內存的引用進行操作。這樣可以在一些場景中顯著提高性能,因爲避免了在java堆和native堆中來回複製數據。
    • 本機直接內存的分配不受java堆大小的限制,但是如果在設置 -Xmx等參數信息時,忽略掉直接內存,可能導致總的內存大於了物理內存限制,從而導致動態擴展時出現OutOfMemoryError異常。

幾種常見的OutOfMemoryError

  • java堆溢出
    • java.lang.OutOfMemoryError : java heap space
    • 當出現堆溢出時一般手段是先通過內存映像分析工具(如:eclipse memory analyzer)對dump出來的堆轉存快照進行分析。首先要清楚是內存泄漏還是內存溢出
  • 虛擬機棧和本地方法棧溢出
    • 棧容量只由 -Xss 參數設定
    • 關於虛擬機棧和本地方法棧,在java虛擬機規範中描述了兩種異常:
      • 如果線程請求的棧深度大於虛擬機所允許的最大深度,將拋出StackOverflowerror
      • 如果虛擬機在擴展棧時無法申請到足夠的空間,則拋出OutOfMemoryError
  • 方法區和運行時常量池溢出
    • VM Args:-XX:PermSize = 10M -XX:MaxPermSize=10M
    • java.lang.OutOfMemoryError : PermGen space
  • 本機直接內存溢出
    • DirectMemory容量可以通過-XX : MaxDirectMemorySize指定,如果不指定,則默認與java堆最大值一樣
    • 由directMemory導致的內存溢出,一個明顯的特徵是在Heap Dump文件中不會看見明顯的異常,如果發現OOM之後dump文件很小,而程序中又直接或間接使用了NIO,則可以考慮是不是這方面問題

內存分配和回收策略

對象優先在Eden分配

大多數情況,對象在新生代Eden去分配。當Eden區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC。

大對象直接進入老年代

所謂大對象是指—需要大量連續內存空間的java對象(最典型的大對象就是那種很長的字符串以及數組)。虛擬機提供了一個 -XX:PretenureSizeThreshold參數,令大於這個設置值的對象直接在老年代分配。這樣做的目的是避免在Eden區及兩個survivor區之間發生大量的內存複製。

長期存活的對象將進入老年代

在Eden中新生的對象,經歷第一次Minor GC後存在,且能被survivor容納,將被移動到survivor中,並年齡設爲1。每經過一次,年齡+1,直到達到閾值(默認15,可以通過-XX:MaxTenuringThreshold設置)

動態年齡判斷

如果survivor空間中相同年齡所有對象大小總和大於survivor空間的一半,年齡大於或者等於該年齡的對象直接進入老年代,無需等年齡到達閾值。

垃圾收集算法

  • 標記-清除算法
  • 複製算法
  • 標記-整理算法
  • 分代收集算法

虛擬機性能監控與故障處理工具

jdk的命令行工具
  • jps:虛擬機進程狀況工具
    • 可以列出正在運行的虛擬機進程,並顯示虛擬機執行主類(Main Class,main()函數所在的類)名稱以及這些進程的本地虛擬機唯一id(Local Virtual Machine Identifier , LVMID)
    • -q : 只輸出LVMID,省略主類的名稱
    • -m : 輸出虛擬機進程啓動時傳遞給主類main()函數的參數
    • -l : 輸出主類的全名,如果進程執行的是jar包,輸出jar的路徑
    • -v : 輸出虛擬機啓動時jvm參數
  • jstat:虛擬機統計信息監視工具
    • JVM Statistics Monitoring Tool是用於監視虛擬機各種運行狀態信息的命令行工具,他可以顯示本地或者遠程虛擬機進程中的類轉載、內存、垃圾收集、jit編譯等運行數據,在服務器上它是運行期定位虛擬機性能問題的首選工具
    • -gc : 監視java堆,包括Eden區,兩個survivor區,老年代、永久代等的容量、已用空間、gc時間合計等信息
    • -class : 監視類裝載、卸載數量、總空間以及類裝載所耗費的時間
    • -gcutil : 監視內容與-gc基本相同,但輸出主要關注已使用空間佔總空間的百分比
  • jinfo:java配置信息工具
    • Configuration Info for Java 的作用是實時地查看和調整虛擬機各項參數。
  • jmap:java內存映射工具
    • Memory Map for Java命令用於生成堆轉儲快照(一般稱爲heapdump或dump文件)。
    • -dump:生成java堆轉儲快照
      • -dump:[live,]format=b,file= live子參數說明是否只dump出存活的對象
    • -heap:顯示java堆詳細信息,如使用哪種回收器、參數配置、分代狀況等
    • -histo:顯示堆中對象統計信息,包括類、實例數量、合計容量
    • -F:當虛擬機進程對-dump選項沒有響應時,可使用這個選項強制生成dump快照。
  • jhat:虛擬機堆轉儲快照分析工具
    • sun JDK提供了jhat(JVM Heap Analysis Tool)命令與jmap搭配使用,來分析生成的堆轉儲快照。jhat內置了一個微型的HTTP/HTML服務器,生成dump文件的分析結果後,可以在瀏覽器查看。
    • 注意:一般不要在生產服務器上用該命令分析dump文件
      • 分析工具是一個耗時而且消耗硬件資源的過程
      • jhat的分析功能相對來說比較簡陋
  • jstack:java堆棧跟蹤工具
    • Stack Trace for Java 命令用於生成虛擬機當前時刻的線程快照(一般稱爲threaddump或者Javacore文件)
    • -l :除堆棧外,顯示關於鎖的附加信息
    • -m :如果調用本地方法的話,可以顯示C/C++的堆棧
    • -F :當正常輸出的請求不被響應時,強制輸出線程堆棧
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章