1.什么是JVM?
Java虛擬機,Java具有自動內存管理等一系列特性,為實現Java跨平臺,一次編譯處處執行。
2.JVM結構圖
3.類加載器-入口
加載class文件,將類信息存放到運行時數據區的方法區內存空間中
通過魔數和文件格式來判斷是否是class文件
類生命周期或加載過程
3.1 類加載器分類
啟動類加載器(BootstrapClassLoader):由C++實現。
擴展類加載器(ExtCla ssLoader/PlatformClassLoader):由Java實現,派生自ClassLoader類。
應用程序類加載器(AppClassLoader):也叫系統類加載器。由Java實現,派生自ClassLoader類。
自定義加載器 :程序員可以定制類的加載方式,派生自ClassLoader類。
3.2 雙生委派機制
類加載器收到加載請求,不是自己嘗試加載,請求委托父加載器,依次向上,自底向上避免重復加載,自頂向下進行加載,避免核心類被修改。
沙箱安全機制:包含核心類(啟動類,擴展類加載器中的類不被破壞),防止內存中出現多份同樣的字節碼。
4. 運行時數據區
4.1 方法區/非堆
被所有線程鎖共享{共享區間},類信息{接口、方法}+靜態變量+常量+運行時常量池。
4.2 堆
存放指向類元數據的地址,JVM啟動時創建,堆內存大小可以調節。
-Xms 512m 初始大小
-Xmx 512m 最大大小
非堆內存
# 設置元空間大小(JDK 8+ 替代永久代)
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m# 設置線程棧大小
-Xss1m # 每個線程棧大小為 1MB
4.2.1 分代
堆內存分為:新生代/老年代/永久代(在方法區,8之后變成元空間,只是邏輯上屬于堆內存)
年輕代分為:伊甸園區,幸存者區
4.2.2 分代空間工作流程
頻繁的垃圾回收成為Minor GC
1.生命周期短的對象,在新生代創建,在新生代被垃圾回收。
2.生命周期長的對象,在新生代創建,老年代回收
3.幾乎所以對象創建在伊甸園區,大多銷毀在新生代,大對象直接進入老年代
先創建在伊甸園區,滿后觸發垃圾回收器,回收不再被引用的對象,將剩余對象移動到幸存者0區,對象賦值年齡計數器為1。伊甸園區清空
再次滿后,對伊甸園區和幸存者0區進行銷毀不再被引用的對象,將剩余對象都移動到幸存者1區
對應對象的年齡計數器+1
依次類推,幸存者區0和1進行交替存放,對應對象年齡+1,年齡達到15晉升老年代
4.2.3 堆空間分配
新生代1/3堆空間,老年代2/3,新生代:8:1:1
老年代滿后產生Major GC,觸發Full GC 進行老年代垃圾回收。清理后仍然不能進行對象保存此時產生OOM異常
4.2.4 永久代
存放運行環境必須的類信息,fullgc觸發回收,若出現OOM:PermGen,永久代設置內存不足被占滿
Jdk 自身攜帶的class interface 等
jdk1.8 去永久代變元空間,元空間占用的是本地物理內存
4.2.5?內存溢出的時候:如何分析
內存溢出分析工具
MAT
eclipse推出的內存分析利器,支持解析hprof等格式的堆快照,能快速定位大對象、內存泄漏點,適合處理GB級快照文件
jvisualvm
通過OQL(對象查詢語言)在堆快照中查詢特定對象(如“所有未被回收的String對象”),精準定位內存問題。
arthas 阿爾薩斯 https://arthas.aliyun.com/doc/quick-start.html
JVM
啟動 java -jar arthas-boot.jar
按 Java 進程前面的 序號,然后回車
會展示當前進程的信息 dashboard
查看靜態的屬性 getstatic
查看當前 JVM 的信息 jvm
查看內存使用狀態 memory
常規命令
會打印線程 ID 的棧 thread ID
反編譯類 jad
監控方法的返回值 watch
退出 quit、exit
4.2.6?垃圾回收算法
復制算法
堆內存分割兩塊,遍歷from空間檢索存活對象搬運到to空間,清理from空間,名稱互換
實現簡單,不存在內存碎片。內存縮小一半
標記清除
使用可達性分析算法,標記可達對象,清除未標記對象。
充分利用內存。經過兩次掃描,產生內存碎片。
標記整理/標記壓縮
在標記清除基礎上進行壓縮空間。
充分利用內存,不會產生內存空間。經過兩次掃描耗費時間,存活對象對整理麻煩,算法效率降低
分代收集算法
三合一,年輕代復制算法,老年代標記混合實現。
4.2.7 判斷一個對象是否可回收
引用計數法
可達性分析算法
4.3 棧
線程創建時創建,聲明周期跟隨線程,線程私有,線程上執行的每個方法都各自對應一個棧幀。
棧存儲棧幀,棧幀是一個內存區塊,包含方法執行的數據信息
4.3.1 局部變量表
存儲方法參數和方法體內定義的局部變量:8種基本類型變量、對象引用變量、實例方法。
4.3.2 操作數棧
在方法執行過程中根據字節碼指令記錄當前操作的數據,將它們入棧或出棧。用于保存計算過程的中間結果,同時作為計算過程中變量的臨時存儲空間。
4.3.3?動態鏈接
可以知道當前幀執行的是哪個方法。指向運行時常量池中方法的符號引用。程序真正執行時,類加載到內存中后,符號引用會換成直接引用
4.3.4 方法返回地址
可以知道調用完當前方法后,上一層方法接著做什么,即“return”到什么位置去。存儲當前方法調用完畢
4.3.5 棧溢出
StackOverflowError
出現情況:代碼循環調用,棧空間不足 -Xss2M
垃圾回收不涉及棧內存。方法調用結束后自動彈出
方法內的局部變量線程安全嗎?
當方法內局部變量沒有逃離方法的作用范圍時線程安全,因為一個線程對應一個棧,每調用一個方法就會新產生一個棧楨,都是線程私有的局部變量,當變量是static時則不安全,因為是線程共享的。
4.4 本地方法棧
存儲本地方法地址
4.5 程序計數器
每個線程都有,線程私有,運行時的指針,存儲指向下一條命令的地址
5.本地方法接口
本地方法存儲了從Java代碼中調用本地方法時所需的信息。線程私有。
5.1 本地方法
由native修飾,c/c++編寫,通過Java編譯器看不到
5.2 本地方法庫
提供所有本地方法與接口的實現
6. 執行引擎-出口
負責解析命令,交給操作系統執行
6.1 解釋器
Java字節碼加載到內存中,解釋器逐條解析和執行字節碼指令轉換對應平臺的本地機器指令。無需等待編譯,執行速度較慢。
6.2 即時編譯器
為了提高執行速度,JVM還使用即時編譯器。即時編譯器將字節碼動態地編譯為本地機器碼,以便直接在底層硬件上執行。