JVM探究
1、JVM探究
- 請你談談你對JVM的理解?java8虛擬機和之前的變化更新?
- 什麼是OOM,什麼是棧溢出StackOverFlowError?怎麼分析?
- JVM的常用調優參數有哪些?
- 記憶體快照如何抓取,怎麼分析Dump文件?
- 談談JVM中,類載入器你的認識?
-
JVM的位置
-
JVM的體系結構
-
類載入器
-
雙親委派機制
/* 1.類載入器收到類載入的請求! 2.將這個請求向上委託給父類載入器去完成,一直向上委託,直到啟動類載入器 3.啟動載入器檢查是否能夠載入當前這個類,能載入就結束,使用當前的載入器,狗則,拋出異常,通知子載入器進行載入 4.重複步驟 3 Class Not Found null: java調用不到,C\C++ */
-
沙箱安全機制
Java安全模型的核心就是Java沙箱(sandbox),什麼是沙箱?沙箱是一個限制程式運行的環境。沙箱機制就是將Java程式碼限定在虛擬機(JVM)特定的運行範圍中,並且嚴格限制程式碼對本地系統資源訪問,通過這樣的措施來保證對程式碼的有效隔離,防止對本地系統造成破壞。沙箱主要限制系統資源訪問,那系統資源包括什麼?CPU、記憶體、文件系統、網路。不同級別的沙箱對這些資源訪問的限制也可以不一樣。
所有的Java程式運行都可以指定沙箱,可以訂製安全策略。
在Java中將執行程式分成本地程式碼和遠程程式碼兩種,本地程式碼默認為可信任的,而遠程程式碼則被看作是不受信任的。對於授信的本地程式碼,可以訪問一切本地資源。而對於非授信的遠程程式碼在早期的Java實現中,安全依賴於沙箱(sandbox)機制。如下圖所示JDK1.0安全模型
但如此嚴格的安全機制也給程式的功能擴展帶來障礙,比如當用戶希望遠程程式碼訪問本地系統的文件時候,就無法實現。因此在後續的Java1.1版本中,針對安全機製做了改進,增加了
安全策略
,允許用戶指定程式碼對本地資源的訪問許可權。如下圖所示JDK1.1安全模型
在Java1.2版本中,再次改進了安全機制,增加了
程式碼簽名
。不論本地程式碼或是遠程程式碼,都會按照用戶的安全策略設定,由類載入器載入到虛擬機中許可權不同的運行空間,來實現差異化的程式碼執行許可權控制。如下圖所示JDK1.2安全模型
當前最新的安全機制實現,則引入了域(Domain)的概念。虛擬機會把所有程式碼載入到不同的系統域和應用域,系統域部分專門負責與關鍵資源進行交互,而各個應用域部分則通過系統域的部分代理來對各種需要的資源進行訪問。虛擬機中不同的受保護域(Protected Domain),對應不一樣的許可權(Permission)。存在於不同域中的類文件就具有了當前域的全部許可權,如下圖所示最新的安全模型(jdk1.6)
組成沙箱的基本組件:
位元組碼校驗器
(bytecode verifier):確保Java類文件遵循Java語言規範。這樣可以幫助Java程式實現記憶體保護。但並不是所有的類文件都會經過位元組碼校驗,比如核心類。- 類載入器(class loader):其中類裝載在三個方面對Java沙箱起作用
- 它防止惡意程式碼去干涉善意的程式碼;
- 它守護了被信任的類庫邊界;
- 它將程式碼歸入保護域,確定了程式碼可以進行哪些操作。
虛擬機為不同的類載入器載入的類提供不同的命名空間,命名空間由一系列唯一的名稱組成,每一個被裝載的類將有一個名字,這個命名空間是由Java虛擬機為每一個類裝載器維護的,它們互相之間甚至不可見。
類裝載器採用的機制是雙親委派模式。
- 從最內層JVM自帶類載入器開始載入,外層惡意同名類得不到載入從而無法使用;
- 由於嚴格通過包來區分了訪問域,外層惡意的類通過內置程式碼也無法獲得許可權訪問到內層類,破壞程式碼就自然無法生效。
存取控制器
(access controller):存取控制器可以控制核心API對作業系統的存取許可權,而這個控制的策略設定,可以由用戶指定。安全管理器
(security manager):是核心API和作業系統之間的主要介面。實現許可權控制,比存取控制器優先順序高。安全軟體包
(security package):java.security下的類和擴展包下的類,允許用戶為自己的應用增加新的安全特性,包括:- 安全提供者
- 消息摘要
- 數字簽名
- 加密
- 鑒別
-
Native
//native : 凡是帶了native關鍵字的,說明java的作用範圍打不到了,會去調用底層C語言的庫! //會進入本地方法棧 Native Method Stack //調用本地方法本地介面 JNI //JNI的作用:擴展Java的使用,融合不同的程式語言為Java所用!最初:C\C++。 //Java誕生的時候 C/C++橫行,想要立足,必須要有調用C/C++的程式~ //它在記憶體區域中專門開闢了一塊標記區域:Native Method Stack,登記native方法 //在最終執行的時候,載入本地方法庫中的方法通過JNI //Java程式驅動印表機,管理系統,掌握即可,企業級應用中較為少見!
-
PC暫存器
PC暫存器(計數器)
程式計數器:Program Counter Register
每個執行緒都有一個程式計數器,是執行緒私有的,就是一個指針,指向方法區中的方法位元組碼(用來存儲指向下一條指令的地址,也即將要執行的指令程式碼),在執行引擎讀取下一條指令,是一個非常小的記憶體空間,幾乎可以忽略不計
-
方法區
方法區
Method Area 方法區
方法區是被所有執行緒共享,所有欄位和方法位元組碼,以及一些特殊方法,如構造函數,介面程式碼也在此定義,簡單說,所有定義的方法的資訊都保存在該區域,此區域屬於共享區間;
靜態變數、常量、類資訊(構造方法、介面定義)、運行時的常量池存在方法區中,但是實例變數存在堆記憶體中,和方法區無關
static final Class模板 字元串常量池;
jdk1.8後static,字元串常量池保存在堆中
-
棧
棧:數據結構
棧:先進後出,後進先出:桶
隊列:先進先出(FIFO:First Inpu First Output)
為什麼main()先執行,最後結束~ 棧:棧記憶體,主管程式的運行,生命周期和執行緒同步;
執行緒結束,棧記憶體也就釋放,對於棧來說,不存在垃圾回收問題,一旦執行緒結束,棧就Over
棧:8大基本類型 + 對象引用 + 實例的方法
棧 + 堆 + 方法區 :交互關係
-
三種JVM
- Sun公司 HotSpot
Java HotSpot(TM) 64-Bit Server VM (build 25.301-b09, mixed mode)
- BEA
JRockit
- IBM
J9 VM
我們學習的都是:
HotSpot
- Sun公司 HotSpot
-
堆
Heap,一個JVM只有一個堆記憶體,堆記憶體的大小是可以調節的。
類載入器讀取了類文件後,一般會把什麼東西放到堆中? 類,方法,常量,變數~,保存我們所有引用類型的真實對象;
堆記憶體中還要細分為三個區域:
- 新生區(伊甸園區) Young/New
- 養老區 old
- 永久區 Perm
GC 垃圾回收,主要是在伊甸園區和養老區~
假設記憶體滿了,OOM,堆記憶體不夠! java.lang.OutOfMemoryError: Java heap space
在JDK8以後,永久存儲區改了個名字(元空間);
-
新生區、老年區
新生區
- 類:誕生和成長的地方,甚至死亡
- 伊甸園,所有的對象都是在伊甸園區new出來的!
- 倖存者區(0,1)
老年區
-
永久區
永久區:這個區域常駐記憶體的,用來存放JDK自身攜帶的Class對象。Interface元數據,存儲的是Java運行時的一些環境或類資訊,這個區域不存在垃圾回收!關閉虛擬機(VM)就會釋放這個區域的記憶體
一個啟動類,載入了大量的第三方jar包。Tomcat部署了太多的應用,大量動態生成的反射類。不斷地被載入。直到記憶體滿,就會出現OOM(OutOfMemoryError);
// 默認情況下:分配的總記憶體是電腦記憶體的1/4,而初始化的記憶體:1/64 // OOM異常處理方法: // 1.嘗試擴大堆記憶體看結果 // 2.分析記憶體,看一下哪個地方出了問題(專業工具) // -Xms1024m -Xmx1024m -XX:+PrintGCDetails
- jdk1.6之前 :永久代,常量池是在方法區;
- jdk1.7 :永久代,但是慢慢退化了,
去永久代
,常量池在堆中 - jdk1.8之後 :無永久代,常量池在元空間
元空間:邏輯上存在,物理上不存在
在一個項目中,突然出現了OOM故障,那麼該如何排除研究為什麼出錯
- 能夠看到程式碼第幾行出錯:記憶體快照分析工具,MAT,Jprofiler
- Dubug,一行行分析程式碼!
MAT,JProfiler作用:
- 分析Dump記憶體文件,快速定位記憶體泄漏;
- 獲得堆中的數據
- 獲得大的對象~
- …
JProfiler介面:
-
堆記憶體調優
-
GC
JVM在進行GC時,並不是對這三個區域統一回收。大部分時候,回收都是新生代~
- 新生代
- 倖存區(from ,to)
- 老年區
GC兩種類:輕GC(普通的GC),重GC(全局GC)
題目:
- JVM的記憶體模型和分區~詳細到每個區放什麼?
- 堆裡面的分區有哪些?Eden,from,to,老年區,說說他們的特點!
- GC的演算法有哪些?標記清除法,標記壓縮,複製演算法,引用計數法,怎麼用的?
- 輕GC和重GC分別在什麼時候發生?
引用計數法:
複製演算法:
- 好處:沒有記憶體的碎片
- 壞處:浪費了記憶體空間~,多了一半空間永遠是空to。假設對象100%存活(極端情況)
複製演算法最佳使用場景:對象存活度較低的時候;新生區~
標記清除演算法:
- 優點:不需要額外的空間!
- 缺點:兩次掃描,嚴重浪費時間,會產生記憶體碎片。
標記壓縮演算法
再優化:
標記清除壓縮演算法
先對要掃描的對象進行幾次標記清除演算法處理
再壓縮
-
JMM:Java Memory Model
-
什麼是JMM?
JMM:(Java Memory Model的縮寫)
-
它幹嘛的? 官方,部落格,對應的影片
作用:快取一致性協議,用於定義數據讀寫的規則(遵守,找到這個規則)。
JMM定義了執行緒工作記憶體和主記憶體之間的抽象關係:執行緒之間的共享變數存儲在主記憶體(Main Memory)中,每個執行緒都有一個私有的本地記憶體(Local Memory)
解決共享對象可見性這個問題:volatile
-
它該如何學習?
JMM:抽象的概念,理論
JMM對這八種指令的使用,制定了如下規則:
- 不允許read和load、store和write操作之一單獨出現。即使用了read必須,使用了store必須write
- 不允許執行緒丟棄它最近的assign操作,即工作變數的數據改變了之後,必須告知主存
- 不允許一個執行緒將沒有assign的數據從工作記憶體同步回主記憶體
- 一個新的變數必須在主記憶體中誕生,不允許工作記憶體直接使用一個未被初始化的變數。就是對變數實施use、store操作之前,必須經過assign和load操作
- 一個變數同一時間只有一個執行緒能對其進行lock。多次lock後,必須執行相同次數的unlock才能解鎖
- 如果對一個變數進行lock操作,會清空所有工作記憶體中此變數的值,在執行引擎使用這個變數前,必須重新load或assign操作初始化變數的值
- 如果一個變數沒有被lock,就不能對其進行unlock操作。也不能unlock一個被其他執行緒鎖住的變數
- 對一個變數進行unlock操作之前,必須把此變數同步回主記憶體
JMM對這八種操作規則和對volatile的一些特殊規則就能確定哪些操作是執行緒安全,哪些操作是執行緒不安全的了。但是這些規則實在複雜,很難在實踐中直接分析。所以一般我們也不會通過上述規則進行分析。更多的時候,使用java的happen-before規則來進行分析。
volatile:解決一致性
-
-
總結
記憶體效率:複製演算法>標記清除演算法 >標記壓縮演算法(時間複雜度)
記憶體整齊度:複製演算法=標記壓縮演算法>標記清除演算法
記憶體利用率:標記壓縮演算法=標記清除演算法>複製演算法
思考一個問題:難道沒有最優演算法嗎?
答案:沒有,沒有最好的演算法,只有最合適的演算法 ——> GC :分代收集演算法
年輕代:
- 存活率低
- 複製演算法!
老年代:
- 區域大:存活率高
- 標記清除(記憶體碎片不是太多) + 標記壓縮 混合實現
一天時間學JVM不現實,要深究必須要下去花時間和多看《深入理解JVM虛擬機》及面試題
但是我們可以掌握一個學習JVM的方法~