內存結構
內存結構
程序計數器(寄存器)
作用:記住下一條 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()