Java Virtual Machine(JVM)是Java程序的運行環境(java二進制字節碼的運行環境)
好處:
- 一次編寫,到處運行
- 自動內存管理,垃圾回收機制
JVM由哪些部分組成,運行流程是什么?
1. 程序計數器
程序計數器:線程私有(不存在線程安全),
- 原理:內部記錄正在執行的字節碼指令的地址(行號)。
- 功能:保存當前線程的行號,如果時間片被搶占,再次執行會從記錄的行號繼續執行,不會從頭執行
2. Java堆
2.1 結構
線程共享的區域(存在線程安全)
主要用來保存對象實例,數組等,當堆中沒有內存空間可分配給實例,也無法再擴展時,則拋出OutOfMemoryError異常。
年輕代被劃分為三部分,Eden區和兩個大小嚴格相同的Survivor區,
根據JVM的策略,在經過幾次垃圾收集后,任然存活于Survivor的對象將被移動到老年代區間。老年代主要保存生命周期長的對象,一般是一些老的對象
2.2 Java 1.7與1.8堆的區別
- 1.7中有有一個永久代,存儲的是類信息、靜態變量、常量、編譯后的代碼
- 1.8移除了永久代,把數據存儲到了本地內存的元空間中,防止內存溢出
優化點:1.7堆的永久代在1.8更新成了本地內存中的元空間,解決永久代多容易OOM,少了會浪費內存的問題
3. 虛擬機棧
Java Virtual machine Stacks(java 虛擬機棧)
- 每個線程運行時所需要的內存,稱為虛擬機棧,先進后出(多個線程運行就會創建多個虛擬機棧——線程安全)
- 每個棧由多個棧幀(frame)組成,對應著每次方法調用時所占用的內存
- 每個線程只能有一個活動棧幀,對應著當前正在執行的那個方法
3.1 垃圾回收是否涉及棧內存
不涉及。垃圾回收主要指就是堆內存,當棧幀彈棧以后,內存就會釋放
3.2 棧內存分配越大越好嗎
視情況分析,默認的棧內存通常為1024k
棧幀過大會導致線程數變少,例如,機器總內存為512m,目前能活動的線程數則為512個,如果把棧內存改力2048k,那么能活動的棧幀就會減半
3.3 方法內的局部變量是否線程安全?
- 如果方法內局部變量沒有逃離方法的作用范圍,它是線程安全的
- 如果是局部變量引用了對象,并逃離方法的作用范圍,需要考慮線程安全
3.4 棧內存溢出情況
- 棧幀過多導致棧內存溢出,典型問題:遞歸調用
- 棧幀過大導致棧內存溢出
public static void m4(){m4(); //java.lang.StackOverflowError
}
3.5 堆棧的區別是什么
- 棧內存一般會用來存儲局部變量和方法調用,但堆內存是用來存儲Java對象和數組的的。堆會GC垃圾回收,而棧不會。
- 棧內存是線程私有的,而堆內存是線程共有的。
- 兩者異常錯誤不同,但如果棧內存或者堆內存不足都會拋出異常。
棧空間不足:java.lang.StackOverFlowError。
堆空間不足:javalang.OutOfMemoryError。
4. 方法區
- 方法區(Method Area)是各個線程共享的內存區域
- 主要存儲類的信息、運行時常量池
- 虛擬機啟動的時候創建,關閉虛擬機時釋放
- 如果方法區域中的內存無法滿足分配請求,則會拋出OutOfMemoryError: Metaspace
jdk1.7到1.8把這個區從堆移到元空間(本地空間)
4.1 常量池
可以看作是一張表,虛擬機指令根據這張常量表找到要執行的類名、方法名、參數類型、字面量等信息
javap -v Application.class
查看字節碼結構(類的基本信息、常量池、方法定義)
運行時常量池
常量池是*.class
文件中的,當該類被加載,它的常量池信息就會放入運行時常量池,并把里面的符號地址變為真實地址
5. 直接內存
- 直接內存并不屬于JVM中的內存結構,不由JVM進行管理。是虛擬機的系統內存
- 常見于NIO操作時,用于數據緩沖區,它分配回收成本較高,但讀寫性能高
5.1 IO數據拷貝流程(常規)
操作磁盤文件中需要存入兩次到緩存區(系統緩存區、java緩存區)
因為java代碼無法直接操作系統造成了java緩存區,這樣導致了一次不必要的復制,導致效率低
5.2 NIO數據拷貝流程
直接內存(共享的內存區域)讓系統和java代碼都可以直接訪問。
相較IO少了一次數據復制,效率成倍的提升。所以NIO適合文件操作