記憶體結構
程式計數器(暫存器)
作用:記住下一條 jvm 指令的執行地址
特點:
-
是執行緒私有的
為了執行緒切換後能恢復到原來的執行位置,每條執行緒都需要有一個獨立的程式計數器,各個計數器之間互不影響。
-
不會存在記憶體溢位
虛擬機器棧
定義
Java Virtual Machine Stacks (Java 虛擬機器棧)
-
每一個執行緒都會開闢一個虛擬機器棧,用於存放棧幀;棧幀用於儲存區域性變數表、運算元棧、動態連線和方法出口等資訊。
區域性變數表存放了編譯期可見的各種基本資料型別,這些資料型別在表中的儲存空間以區域性變數槽(slot)表示,
-
每個執行緒只能有一個活動棧幀,對應著當前正在執行的那個方法。
-
如果執行緒請求的棧深度大於虛擬機器所允許的深度,丟擲SO異常;如果棧擴充套件無法申請到足夠的記憶體丟擲OOM異常。
問題 ?
-
垃圾回收是否涉及棧記憶體?
不是,棧記憶體隨著方法執行的結束而釋放,並不會被垃圾回收釋放,垃圾回收釋放的堆記憶體。
-
棧記憶體分配越大越好嗎?
不是,棧記憶體分配越大,會導致執行緒數量減少。
-
方法內的區域性變數是否執行緒安全?
- 如果方法內區域性變數沒有逃離方法的作用範圍,它是執行緒安全的
- 如果是區域性變數引用了物件,或逃離方法的作用範圍,需要考慮執行緒安全
棧記憶體溢位
- 棧幀過多導致棧記憶體溢位
- 棧幀過大導致棧記憶體溢位
執行緒執行診斷
1、cpu 佔用過多
-
用top定位哪個程式對cpu的佔用過高
-
ps H -eo pid,tid,%cpu | grep 程式id (用ps命令進一步定位是哪個執行緒引起的cpu佔用過高)
-
jstack 程式id
可以根據執行緒id 找到有問題的執行緒,進一步定位到問題程式碼的原始碼行號
2、程式執行很長時間沒有結果
本地方法棧
呼叫本地方法和作業系統互動,例如Object 裡的方法,方法前有 native 修飾。
堆
定義
通過 new 關鍵字,建立物件都會使用堆記憶體
特點
- 它是執行緒共享的,堆中物件都需要考慮執行緒安全的問題
- 有垃圾回收機制
堆記憶體溢位
垃圾回收會對無用的物件進行回收,但如果一直有物件被建立,並且物件一直被使用,最終會導致堆記憶體溢位。
堆記憶體診斷
1、 jps 工具
檢視當前系統中有哪些 java 程式
2、map 工具
檢視堆記憶體佔用情況
jhsdb jmap --heap --pid 程式id
3、jconsole 工具
圖形介面的,多功能的監測工具,可以連續監測
案例
-
垃圾回收後,記憶體佔用仍然很高
開啟jdk按照目錄:jvisualvm.exe
方法區
方法區記憶體溢位
- 1.8前會導致永久代溢位
- 1.8後會導致元空間溢位
執行時常量池
- 常量池就是一張表,虛擬機器指令根據這張表找到要執行的類名、方法名、引數型別、字面量等資訊
- 執行時常量池,當該類被載入時,它的常量池資訊會放入執行時常量池,並把裡面的符號地址變為真實地址
StringTable
- 常量池中的字串僅僅是符號,只有第一次用到時才變為物件
- 利用串池的機制,避免重複建立字串物件
- 字串變數拼接的原理是StringBuilder
- 字串常量拼接的原理是編譯器優化
- 可以使用intern 方法,主動將串池中還沒有的字串物件加入串池
- 1.8 將這個字串物件嘗試放入串池,如果有則不放入,沒有則放入,返回串池中的物件
- 1.6 將這個字串物件嘗試放入串池,如果有則不放入,沒有則複製一份此物件,放入串池,返回串池中的物件
StringTable 調優:
-
-Xms500m -Xmx500m -XX:PrintStringTableStatistics -XX:StringTableSize =
-jvm啟動時分配的最大記憶體 -jvm執行過程中分配的最大記憶體 -列印串池中的統計資訊 -設定串池中桶的個數
-
有大量的重複的字串,可以讓字串入池,減少字串的個數,減少堆記憶體的使用。
直接記憶體
- 常見於NIO操作,用於資料緩衝區
- 分配回收成本高,但讀寫效能高
- 不受jvm記憶體回收管理
直接記憶體釋放原理
- 使用了Unsafe 物件完成直接記憶體的分配回收,並且回收需要主動呼叫freeMemory方法
- ByteBuffer 的實現類內部,使用了 Cleaner(虛引用) 來監測 ByteBuffer 物件,一旦 ByteBuffer 物件被垃圾回收,那麼就會由ReferenceHandler 執行緒通過 Cleaner 的 clean 方法呼叫 freeMemory 來釋放直接記憶體
-XX:+DisableExplictGC 關閉顯示的gc:System.gc()