點擊上方"程序員歷小冰",選擇「置頂或者星標」css
你的關注意義重大!
java
JVM都是進階時必須邁過的坎。無論是工做仍是面試中,JVM都是必考題。
若是不懂JVM的話,薪酬會很是吃虧(近70%的面試者掛在JVM上了)。
請你談談你對JVM的理解?c++
JVM類加載器是怎麼樣的?有幾種?程序員
什麼是OOM,什麼是StackOverFlowError? 怎麼分析?web
JVM經常使用調優參數有哪寫?面試
GC有幾種算法?分別是怎麼執行的?算法
你知道JProfiler嗎,怎麼分析Dump文件?編程
Java虛擬機
!!
一、什麼是JVM?在哪?
JVM本質上是一個
程序
,它能識別.class
字節碼文件(裏面存放的是咱們對.java
編譯後產生的二進制代碼),而且可以解析它的指令,最終調用操做系統上的函數,完成咱們想要的操做!安全關於Java語言的
跨平臺性
,就是由於JVM,咱們能夠將其想象爲一個抽象層,只要這個抽象層JVM正確執行了.class
文件,就能運行在各類操做系統之上了!這就是一次編譯,屢次運行
微信
JVM是運行在操做系統之上的,它與硬件沒有直接的交互
二、JVM、JRE、JDK 的關係
JDK = JRE + javac/java/jar 等指令工具
JRE = JVM + Java基本類庫
三、JVM體系結構
類裝載器子系統
運行時數據區
執行引擎
本地方法接口
垃圾收集模塊
方法區是一種特殊的堆
棧裏面不會有垃圾,用完就彈出了,不然阻塞了main方法
垃圾幾乎都在堆裏,因此JVM性能調優%99都針對於堆
四、三種JVM(瞭解)
HotSpot
(咱們都用的這個)
JRockit
J9 VM
五、類加載器
.Class
字節碼文件
一、回顧new對象的過程
public class Student {
//私有屬性
private String name;
//構造方法
public Student(String name) {
this.name = name;
}
}
//運行時,JVM將Test的信息放入方法區
public class Test{
//main方法自己放入方法區
public static void main(String[] args){
//s一、s二、s3爲不一樣對象
Student s1 = new Student("zsr"); //引用放在棧裏,具體的實例放在堆裏
Student s2 = new Student("gcc");
Student s3 = new Student("BareTH");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
//class一、class二、class3爲同一個對象
Class<? extends Student> class1 = s1.getClass();
Class<? extends Student> class2 = s2.getClass();
Class<? extends Student> class3 = s3.getClass();
System.out.println(class1.hashCode());
System.out.println(class2.hashCode());
System.out.println(class3.hashCode());
}
}
s一、s二、s3的hashcode是不一樣的,由於是三個不一樣的對象,對象是具體的
class一、class二、class3的hashcode是相同的,由於這是類模板,模板是抽象的
首先Class Loader讀取字節碼
.class
文件,加載初始化生成Student模板類
經過
Student模板類
new出三個對象
那麼Class Loader具體是怎麼執行咱們的.class
字節碼文件呢,這就引出了咱們類加載器~
二、類加載器的類別
咱們編寫這樣一個程序
c++
編寫,加載java
核心庫java.*
,構造拓展類加載器
和應用程序加載器
。根加載器
加載拓展類加載器
,而且將拓展類加載器
的父加載器設置爲根加載器
,而後再加載
應用程序加載器
,應將應用程序加載器
的父加載器設置爲拓展類加載器
因爲引導類加載器涉及到虛擬機本地實現細節,咱們沒法直接獲取到啓動類加載器的引用;這就是上面那個程序咱們第三個結果爲
null
的緣由。加載文件存在位置
java
編寫,加載擴展庫,開發者能夠直接使用標準擴展類加載器。java9以前爲
ExtClassloader
,Java9之後更名爲PlatformClassLoader
加載文件存在位置
java
編寫,加載程序所在的目錄
2. 是Java默認
的類加載器
java
編寫,用戶自定義的類加載器,可加載指定路徑的
class
文件
六、雙親委派機制
一、什麼是雙親委派機制
類加載器收到類加載的請求
將這個請求向上委託給父類加載器去完成,一直向上委託,直到根加載器
BootstrapClassLoader
根加載器檢查是否可以加載當前類,能加載就結束,使用當前的加載器;不然就拋出異常,通知子加載器進行加載;自加載器重複該步驟。
二、做用
舉個例子:咱們重寫如下java.lang包下的String類
雙親委派機制
起的做用,當類加載器委託到
根加載器
的時候,
String類
已經被
根加載器
加載過一遍了,因此不會再加載,從必定程度上防止了危險代碼的植入!!
-
1. 防止重複加載同一個
.class
。經過不斷委託父加載器直到根加載器,若是父加載器加載過了,就不用再加載一遍。保證數據安全。
2. 保證系統核心.class
,如上述的String類
不能被篡改。經過委託方式,不會去篡改核心.class
,即便篡改也不會去加載,即便加載也不會是同一個.class
對象了。不一樣的加載器加載同一個.class
也不是同一個class
對象。這樣保證了class
執行安全。
七、沙箱安全機制
什麼是沙箱?
沙箱
(sandbox)
1. 沙箱是一個限制程序運行的環境。沙箱機制就是將 Java 代碼限定在虛擬機(JVM)特定的運行範圍中,而且嚴格限制代碼對本地系統資源訪問,經過這樣的措施來保證對代碼的有效隔離,防止對本地系統形成破壞。
沙箱主要限制系統資源訪問,系統資源包括CPU、內存、文件系統、網絡。不一樣級別的沙箱對這些資源訪問的限制也能夠不同。
java中的安全模型演進
本地代碼
和
遠程代碼
兩種
本地代碼
可信任
,能夠訪問一切本地資源。遠程代碼
不可信信
在早期的Java實現中,安全依賴於沙箱 (Sandbox) 機制。
Java1.1
版本中,針對安全機制作了改進,增長了
安全策略
,容許用戶指定代碼對本地資源的訪問權限。
Java1.2
版本中,再次改進了安全機制,增長了
代碼簽名
。
不論本地代碼或是遠程代碼,都會按照用戶的安全策略設定,由類加載器加載到虛擬機中權限不一樣的運行空間,來實現差別化的代碼執行權限控制。
域 (Domain)
的概念。
虛擬機會把全部代碼加載到不一樣的
系統域
和應用域
系統域
部分專門負責與關鍵資源進行交互應用域
部分則經過系統域的部分代理來對各類須要的資源進行訪問。虛擬機中不一樣的受保護域 (Protected Domain),對應不同的權限 (Permission)。存在於不一樣域中的類文件就具備了當前域的所有權限,以下圖所示
組成沙箱的基本組件
1. 字節碼校驗器
(bytecode verifier)
2. 類裝載器
(class loader)
它防止惡意代碼去幹涉善意的代碼;
它守護了被信任的類庫邊界;
它將代碼納入保護域,肯定了代碼能夠進行哪些操做。
從最內層JVM自帶類加載器開始加載,外層惡意同名類得不到加載從而沒法使用;
因爲嚴格經過包來區分了訪問域,外層惡意的類經過內置代碼也沒法得到權限訪問到內層類,破壞代碼就天然沒法生效。
存取控制器
(access controller):存取控制器能夠控制核心API對操做系統的存取權限,而這個控制的策略設定,能夠由用戶指定。安全管理器
(security manager):是核心API和操做系統之間的主要接口。實現權限控制,比存取控制器優先級高。安全軟件包
(security package):java.security下的類和擴展包下的類,容許用戶爲本身的應用增長新的安全特性,包括:安全提供者
消息摘要
數字簽名
加密
鑑別
八、Native本地方法接口
JNI:Java Native Interface
本地接口的做用是融合不一樣的編程語言爲Java所用,它的初衷是融合C/C++程序
native
:凡是帶native關鍵字的,說明java的做用範圍達不到了,會去調用底層c語言的庫!進入本地方法棧,調用
本地方法接口JNI
,拓展Java的使用,融合不一樣的語言爲Java所用
Java誕生的時候C、C++橫行,爲了立足,必需要能調用C、C++的程序
因而在內存區域中專門開闢了一塊標記區域:Native Method Stack,登記Native方法
最終在執行引擎執行的的時候經過JNI(本地方法接口)加載本地方法庫的方法
九、PC寄存器
程序計數器
:Program Counter Register
每一個線程都有一個程序計數器,是
線程私有
的,就是一個指針,指向方法區中的方法字節碼(用來存儲指向像一條指令的地址,也即將要執行的指令代碼),在執行引擎讀取下一條指令,是一個很是小的內存空間
,幾乎能夠忽略不計
十、方法區
方法區
:Method Area
方法區是被全部線程共享,全部字段和方法字節碼,以及一些特殊方法,如構造函數,接口代碼也在此定義,簡單說,全部定義的方法的信息都保存在該區域,此區域屬於
共享區間
;方法區與Java堆同樣,是各個線程共享的內存區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然Java 虛擬機規範把方法區描述爲堆的一個邏輯部分,可是它卻有一個別名叫作
Non-Heap(非堆)
,目的應該是與Java 堆區分開來。
1. 方法區中有啥?
靜態變量(static)
常量(final)
類信息(構造方法、接口定義)
運行時的
常量池
2. 建立對象內存分析
建立一個對象時,方法區中會生成對應類的抽象模板;還有對應的常量池、靜態變量、類信息、常量
咱們經過類模板去new對象的時候
堆中存放實例對象
棧中存放對象的引用,每一個對象對應一個地址指向堆中相同地址的實例對象
十一、棧
棧內存
,主管程序的運行,生命週期和線程同步,線程結束,棧內存就釋放了,
不存在垃圾回收
棧:先進後出
隊列:先進先出(FIFO)
一、棧中存放啥?
8大基本類型
對象引用
實例的方法
二、棧運行原理
棧表示Java方法執行的內存模型
每調用一個方法就會爲每一個方法生成一個
棧幀(Stack Frame)
,每一個方法被調用和完成的過程,都對應一個棧幀從虛擬機棧上入棧和出棧的過程。程序正在執行的方法必定在棧的頂部
三、堆棧溢出StackOverflowError
public class Test {
public static void main(String[] args) {
new Test().a();
}
public void a() {
b();
}
public void b() {
a();
}
}
十二、堆
Heap
,一個JVM只有一個堆內存(棧是線程級的),堆內存的大小是能夠調節的
一、堆中有啥?
二、堆內存詳解
一、Young 年輕代
對象誕生、成長甚至死亡的區
Eden Space(伊甸園區)
:全部的對象都是在此new出來的Survivor Space(倖存區)
倖存0區
(From Space
)(動態的,From和To會互相交換)倖存1區
(To Space
)
Eden區佔大容量,Survivor兩個區佔小容量,默認比例是8:1:1
。
二、Tenured 老年代
三、Perm 元空間
存儲的是Java運行時的一些環境或類信息,這個區域不存在垃圾回收!關閉虛擬機就會釋放這個區域內存!
這個區域常駐內存,用來存放JDK自身攜帶的Class對象、Interface元數據。
jdk1.6以前:
永久代
jdk1.7:
永久代
慢慢退化,去永久代
jdk1.8以後:
永久代
更名爲元空間
三、什麼是OOM?
內存溢出
java.lang.OutOfMemoryError
分配的太少
用的太多
用完沒釋放
四、GC垃圾回收
GC垃圾回收,主要在年輕代和老年代
伊甸園區
假設
伊甸園區
只能存必定數量的對象,則每當存滿時就會觸發一次輕GC(Minor GC)
輕GC
清理後,有的對象可能還存在引用,就活下來了,活下來的對象就進入倖存區
;有的對象沒用了,就被GC清理掉了;每次輕GC
都會使得伊甸園區
爲空若是
倖存區
和伊甸園
都滿了,則會進入老年代
,若是老年代
滿了,就會觸發一次重GC(FullGC)
,年輕代+老年代
的對象都會清理一次,活下的對象就進入老年代
若是
新生代
和老年代
都滿了,則OOM
OOM
1三、堆內存調優
一、查看並設置JVM堆內存
堆內存
public class Test {
public static void main(String[] args) {
//返回jvm試圖使用的最大內存
long max = Runtime.getRuntime().maxMemory();
//返回jvm的初始化內存
long total = Runtime.getRuntime().totalMemory();
//默認狀況下:分配的總內存爲電腦內存的1/4,初始化內存爲電腦內存的1/64
System.out.println("max=" + max / (double) 1024 / 1024 / 1024 + "G");
System.out.println("total=" + total / (double) 1024 / 1024 / 1024 + "G");
}
}
JVM最大分配內存爲電腦內存的1/4
JVM初始化內存爲電腦內存的1/64
VM options
中能夠指定
jvm試圖使用的最大內存
和
jvm初始化內存
大小
-Xms1024m -Xmx1024m -Xlog:gc*
-Xmx
用來設置jvm試圖使用的最大內存
,默認爲1/4-Xms
用來設置jvm初始化內存
,默認爲1/64-Xlog:gc*
用來打印GC垃圾回收信息
二、怎麼排除OOM錯誤?
1. 嘗試擴大堆內存看結果
jvm試圖使用的最大內存
和jvm初始化內存
大小
2. 利用內存快照工具JProfiler
MAT(Eclipse)
JProfiler
分析Dump內存文件,快速定位內存泄漏
得到堆中的文件
得到大的對象
…
3. 什麼是Dump文件?如何分析?
進程
的內存鏡像
,能夠把程序的執行狀態
經過調試器保存到dump文件中
import java.util.ArrayList;
public class Test {
byte[] array = new byte[1024 * 1024];//1M
public static void main(String[] args) {
ArrayList<Test> list = new ArrayList<>();
int count = 0;
try {
while (true) {
list.add(new Test());
count++;
}
} catch (Exception e) {
System.out.println("count=" + count);
e.printStackTrace();
}
}
}
OOM
接下來咱們設置如下堆內存,並附加生成對應的dump文件
的指令
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
-XX:+HeapDumpOnOutOfMemoryError
表示當JVM發生OOM時,自動生成DUMP文件。
再次點擊運行,下載了對應的Dump
文件
Show in Explorer
一直點擊上級目錄,直到找到.hprof
文件,與src
同級目錄下
咱們雙擊打開,能夠看到每塊所佔的大小,便於分析問題
點擊Thread Dump
,裏面是全部的線程,點擊對應的線程能夠看到相應的錯誤,反饋到具體的行,便於排錯
每次打開Dump文件查看完後,建議刪除
,能夠在idea中看到,打開文件後生成了不少內容,佔內存,建議刪除
附:安裝Jprofiler教程
2.下載客戶端 https://www.ej-technologies.com/download/jprofiler/files
打開IDEA的設置,找到Tools裏面的JProfiler,沒有設置位置則設置位置
此時則所有安裝完成!
1四、GC垃圾回收
一、回顧
Garbage Collection:垃圾回收
JVM在進行GC時,並非對
年輕代
、老年代
統一回收;大部分時候,回收都是在年輕代
GC分爲兩種:
輕GC(清理年輕代)
重GC(清理年輕代+老年代)
二、GC算法
一、引用計數算法(不多使用)
每一個對象在建立的時候,就給這個對象綁定一個計數器。
每當有一個引用指向該對象時,計數器加一;每當有一個指向它的引用被刪除時,計數器減一。
這樣,當沒有引用指向該對象時,該對象死亡,計數器爲0,這時就應該對這個對象進行垃圾回收操做。
二、複製算法
年輕代
(
倖存0區
和
倖存1區
)
當Eden區滿的時候,會觸發
輕GC
,每觸發一次,活的對象就被轉移到倖存區,死的就被GC清理掉了,因此每觸發輕GC
時,Eden區就會清空;對象被轉移到了倖存區,倖存區又分爲
From Space
和To Space
,這兩塊區域是動態交換的,誰是空的誰就是To Space,而後From Space
就會把所有對象轉移到To Space
去;那若是兩塊區域都不爲空呢?這就用到了
複製算法
,其中一個區域會將存活的對象轉移到令一個區域去,而後將本身區域的內存空間清空,這樣該區域爲空,又成爲了To Space
;因此每次觸發
輕GC
後,Eden區清空,同時To區也清空了,全部的對象都在From區
這也就是倖存0區
和倖存1區
總有一塊爲空的緣由
浪費了內存空間(浪費了倖存區一半空間)
對象存活率較高的場景下(好比老年代那樣的環境),須要複製的東西太多,效率會降低。
年輕代
三、標記–清除算法
標記階段:這個階段內,爲每一個對象更新標記位,檢查對象是否死亡;
清除階段:該階段對死亡的對象進行清除,執行 GC 操做。
四、標記–整理算法
標記-整理法
是
標記-清除法
的一個改進版。
標記-清楚-壓縮法
標記階段,該算法也將全部對象標記爲存活和死亡兩種狀態;
不一樣的是,在第二個階段,該算法並無直接對死亡的對象進行清理,而是將全部存活的對象整理一下,放到另外一處空間,而後把剩下的全部對象所有清除。
標記清除
,到達必定量的時候再壓縮.
總結
思考:有沒有最優的算法?
分代收集算法
對象存活率低
用複製算法
區域大,對象存活率高
用
標記清除+標記壓縮
混合實現
結束!
做者: Baret H
原文連接:http://i8n.cn/iWLG4r
-關注我
本文分享自微信公衆號 - 程序員歷小冰(gh_a1d0b50d8f0a)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。