記憶體結構
記憶體結構
程式計數器(暫存器)
作用:記住下一條 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()