初識JVM
JVM
JVM的位置
JVM在作業系統之上,是用C語言寫的
JVM的體系結構
類載入器
作用:載入Class檔案類,載入機制是將⼀個類從位元組碼⽂件轉化為虛擬機器可以直接使⽤類的過程
public class Car {
public int age;
public static void main(String[] args) {
//類是模板,物件則是具體的
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
System.out.println(car1.hashCode());
System.out.println(car2.hashCode());
System.out.println(car3.hashCode());
Class<? extends Car> aClass1 = car1.getClass();
ClassLoader classLoader = aClass1.getClassLoader();
System.out.println(classLoader);//AppClassLoader
System.out.println(classLoader.getParent());//ExtClassLoader
System.out.println(classLoader.getParent().getParent());//null 1.不存在 2.獲取不到(許可權不夠)
}
}
- 虛擬機器自帶的載入器
- 啟動類載入器
- 擴充套件類載入器
- 應用程式載入器
雙親委派機制
雙親委派模型的⼯作機制是:當類載入器接收到類載入的請求時,它不會⾃⼰去嘗試載入這個類,⽽是
把這個請求委派給⽗載入器去完成,只有當⽗類載入器反饋⾃⼰⽆法完成這個載入請求時,⼦載入器才
會嘗試⾃⼰去載入類。
類載入器收到類載入請求,將這個請求向上委託父類載入器去完成,一直向上委託,直到啟動類載入器,啟動載入器檢查是否能夠載入這個類。App–>EXC----->BOOT(最終執行)
好處
- 基於雙親委派模型規定的這種帶有優先順序的層次性關係,虛擬機器運⾏程式時就能夠避免類的重複載入。
- 雙親委派模型能夠避免核⼼類篡改。⼀般我們描述的核⼼類是 rt.jar、tools.jar 這些由啟動類載入器載入的類,這些類庫在⽇常開發中被⼴泛運⽤,如果被篡改,後果將不堪設想。
##沙箱安全機制
沙箱是一個限制程式執行的環境,將程式碼限定在JVM特定執行範圍中,並嚴格限制程式碼對本地系統資源訪問,通過這樣的措施來保證對程式碼的 有效隔離,防止對本地系統造成破壞
##Native
package com;
public class Demo {
public static void main(String[] args) {
new Thread(()->{
},"my thread name").start();
}
//nation:凡是帶了nation 關鍵字的,說明java的作用範圍達不到了,呼叫底層C語言庫
//會進入本地方法棧
//呼叫本地方法本地介面JNI
//JNI:擴充套件java使用,融合不同的程式語言為java所用,例如C,C++
//在記憶體區域內專門開闢了一塊標記區域:native Method Stack,登記native
private native void hello();
//呼叫其他介面:Socket,WebServer。。http
}
##PC暫存器
程式技術器
每個執行緒都有一個程式計數器,是執行緒私有的,就是一個指標,指向方法區中的方法位元組碼(用來儲存指向像一條指令的地址,也即將要執行的指令程式碼),在執行引擎讀取下一條指令,是一個非常小的記憶體空間,幾乎可以忽略不計。
##方法區
Method Area方法區
方法區也叫靜態區,是被所有執行緒共享,所有欄位和方法位元組碼以及一些特殊方法,如建構函式,介面程式碼也在此定義,簡單說,所有定義的方法資訊都儲存在該區域,此區域屬於共享區間。
方法區包含的都是整個程式中永遠唯一的元素, class 資訊 和 static修飾的變數
靜態變數,常量,類資訊,執行時的常量池存在方法區中,但是例項變數存在堆記憶體中,和方法去無關
static final Class 常量池:方法區
##棧
棧是一種資料結構
程式=資料結構+演算法:持續學習
程式=框架+業務邏輯 簡易工作理解
棧:先進後出
佇列:先進先出,(FIFO:First Input First Output)都能操作
main方法先執行
- 第一步把main方法壓入棧中
- 再壓入其他方法
- 彈出main方法結束
簡易理解:棧也可以叫棧記憶體,主管程式的執行,生命週期和執行緒同步;執行緒結束,棧記憶體也就被釋放,對於棧來說,不存在垃圾回收問題 一旦執行緒結束,棧也就over
棧:八大基本型別+物件引用+例項的方法
棧幀的理解:
棧+堆+方法區:互動關係
畫出一個物件在記憶體中例項化的過程
##三種JVM
- Sun公司 HotSpot
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)
- BEA
JRockit
- IBM
J9 vm
我們學的都是HotSpot
##堆
Heap,一個jvm只有一個堆記憶體堆記憶體大小可以調節
類載入器讀取了類檔案後,一般會把類,方法,常量放到堆中,儲存我們所有引用型別的物件。
堆記憶體中還要細分為三個區域
- 新生區(伊甸園區eden)Yong/New
- 老年區 old
- 永久區
GC垃圾回收,主要是在伊甸區和養老區
假設記憶體滿了,OOM,堆記憶體不夠 java.lang.OutOfMemoryError:java heap space
##新生區,老年區
- 類:誕生和成長的地方,甚至是死亡
- 伊甸區,所有的物件都是在伊甸區new出來的
- 存活區
經過研究,99%的物件都是臨時物件,活不到老年區。
新生代又分為伊甸區(Eden) 存活區(Survivor),其中存活區又分為兩個大小空間一樣的s0、s1,而且s0 和 s1 可以互相轉化,存活區儲存的一定是在伊甸區儲存了很久的,並且經過好幾次小的GC還存活下來的物件,存活區一定會有兩塊大小相等的空間。目的是一塊存活區未來的晉升,另一塊存活區是為了物件的回收。需要注意的是:這兩塊存活區一定有一塊是空的。
##永生區
這個區域常駐記憶體,用來存放JDK自身攜帶的Class物件,Interface後設資料,儲存的是java執行時的一些環境或者類資訊,這個區域不存在垃圾回收!關閉VM虛擬機器的時候會釋放這個區域的記憶體
一個啟動類,載入了大量的第三方jar包。Tomcat部署了太多的應用,大量動態生成的反射類,不斷的被載入。直到記憶體滿,就會出現OOM(OutOfMemoryError);
- jdk1.6:永久代,常量池是在方法區中;
- jdk1.7:永久代,但是慢慢的退化了,常量池在堆中
- jdk1.8:無永久代,常量池在元空間
邏輯上存在,實際不存在
##堆記憶體調優
堆記憶體調優程式碼:
設定VM大小 //-Xms1024m -Xmx1024m -XX:+PrintGCDetails
package com;
public class Demo02 {
public static void main(String[] args) {
//返回虛擬機器試圖使用的最大記憶體
long max = Runtime.getRuntime().maxMemory(); //位元組 1024*1024
//返回jvm的初始化總記憶體
long total = Runtime.getRuntime().totalMemory();
System.out.println("max ="+max+"位元組\t"+(max/(double)1024/1024)+"MB");
System.out.println("total ="+max+"位元組\t"+(total/(double)1024/1024)+"MB");
}
//OOM
//1.嘗試擴大堆記憶體檢視結果
//2.分析記憶體,看一下那個地方出錯了
//-Xms1024m -Xmx1024m -XX:+PrintGCDetails
}
在一個專案中,突然出現了OOM故障,那麼該如何排除,研究為什麼出錯
- 能夠看到程式碼第幾行出錯:記憶體快照分析工具,MAT,Jprofile
- Dubug,一行行區分析
MAT,Jprofile作用
- 分析Dump記憶體檔案,快速定位記憶體洩露
- 獲得堆中的資料
- 獲得大的物件
- …
通過Jprofile工具解決問題:狂神JVM視訊p9
##GC
JVM在進行GC時,並不是堆這三個區域統一回收,大部分回收的都是新生代。
- 新生代
- 倖存區(from和to兩個區域)
- 老年區
GC兩種分類:輕GC(普通的GC)和重GC(全域性GC)
GC的演算法:標記清除法,標記壓縮法,複製演算法,引用計數器
複製演算法
- 好處:沒有記憶體的碎片
- 壞處:浪費了記憶體空間:多了一般空間永遠時空的(to)
複製物件最佳使用的場景:物件存活度低的時候(及新生區)
標記清除法
- 優點:不需要額外空間
- 缺點:兩次掃描,嚴重浪費時間,會產生記憶體碎片
標記壓縮
GC總結:
記憶體效率:複製演算法>標記清除演算法>標記壓縮演算法
記憶體整齊度:複製演算法=標記清除演算法>標記壓縮演算法
記憶體利用率:標記壓縮演算法>標記清除演算法>複製演算法
沒有最好的演算法,只有最合適的演算法。GC:分代收集演算法
年輕代:
- 存活率低
- 複製演算法
老年代:
- 區域大,存活率高
- 標記清除+標記壓縮混合實現
多找時間學習JVM
- 優點:不需要額外空間
- 缺點:兩次掃描,嚴重浪費時間,會產生記憶體碎片
標記壓縮
[外鏈圖片轉存中…(img-M9KKuZur-1602681619630)]
GC總結:
記憶體效率:複製演算法>標記清除演算法>標記壓縮演算法
記憶體整齊度:複製演算法=標記清除演算法>標記壓縮演算法
記憶體利用率:標記壓縮演算法>標記清除演算法>複製演算法
沒有最好的演算法,只有最合適的演算法。GC:分代收集演算法
年輕代:
- 存活率低
- 複製演算法
老年代:
- 區域大,存活率高
- 標記清除+標記壓縮混合實現
多找時間學習JVM
推薦書《深入理解JVM》
相關文章
- JVM效能優化 (一) 初識JVMJVM優化
- JVM學習筆記——初識JVMJVM筆記
- 「入門篇」初識JVM (下下) - GCJVMGC
- JVM(一):久識你名,初居我心JVM
- JVM-初見JVM
- Java初識Java
- srpingboot 初識boot
- 初識JavaJava
- 初識htmlHTML
- 初識Kubernetes
- Spring 初識Spring
- 初識WebWeb
- 初識WebAssemblyWeb
- 初識Fink
- AsterixDB初識AST
- 初識PostgreSqlSQL
- 初識DevOpsdev
- 初識 NodejsNodeJS
- 初識RedisRedis
- 初識 Shell
- 初識 reduxRedux
- 初識 SpringMVCSpringMVC
- rocketmq初識MQ
- 初識 DockerDocker
- 初識:LevelDB
- 初識Proxy
- 初識JavaScriptJavaScript
- 初識MybatisMyBatis
- 初識AngularJSAngularJS
- Kafka 初識Kafka
- 初識AJAX
- Hash——初識
- 初識GOGo
- 初識dockerDocker
- 初識 “HTML”HTML
- 初識GolangGolang
- 初識jQueryjQuery
- Nodejs初識NodeJS