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的方法~























