您好,登錄后才能下訂單哦!
這篇文章主要介紹“JVM的知識點有哪些”,在日常操作中,相信很多人在JVM的知識點有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”JVM的知識點有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
JVM本質上是一個程序
,它能識別.class
字節碼文件(里面存放的是我們對.java
編譯后產生的二進制代碼),并且能夠解析它的指令,最終調用操作系統上的函數,完成我們想要的操作!
關于Java語言的跨平臺性
,就是因為JVM,我們可以將其想象為一個抽象層,只要這個抽象層JVM正確執行了.class
文件,就能運行在各種操作系統之上了!這就是一次編譯,多次運行
JVM是運行在操作系統之上的,它與硬件沒有直接的交互
JDK = JRE + javac/java/jar 等指令工具
JRE = JVM + Java基本類庫
?類裝載器子系統
運行時數據區
執行引擎
本地方法接口
垃圾收集模塊
?
方法區是一種特殊的堆
棧里面不會有垃圾,用完就彈出了,否則阻塞了main方法
垃圾幾乎都在堆里,所以JVM性能調優%99都針對于堆
HotSpot
(我們都用的這個)JRockit
J9 VM
.Class
字節碼文件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){
//s1、s2、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());
//class1、class2、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());
}
}
?s1、s2、s3的hashcode是不同的,因為是三個不同的對象,對象是具體的
class1、class2、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實現中,安全依賴于沙箱 (Sandbox) 機制。
Java1.1
版本中,針對安全機制做了改進,增加了
安全策略
,允許用戶指定代碼對本地資源的訪問權限。Java1.2
版本中,再次改進了安全機制,增加了
代碼簽名
。不論本地代碼或是遠程代碼,都會按照用戶的安全策略設定,由類加載器加載到虛擬機中權限不同的運行空間,來實現差異化的代碼執行權限控制。
域 (Domain)
的概念。虛擬機會把所有代碼加載到不同的系統域
和應用域
系統域
部分專門負責與關鍵資源進行交互
應用域
部分則通過系統域的部分代理來對各種需要的資源進行訪問。
虛擬機中不同的受保護域 (Protected Domain),對應不一樣的權限 (Permission)。存在于不同域中的類文件就具有了當前域的全部權限,如下圖所示
字節碼校驗器
(bytecode verifier)類裝載器
(class loader)它防止惡意代碼去干涉善意的代碼;
它守護了被信任的類庫邊界;
它將代碼歸入保護域,確定了代碼可以進行哪些操作。
從最內層JVM自帶類加載器開始加載,外層惡意同名類得不到加載從而無法使用;
由于嚴格通過包來區分了訪問域,外層惡意的類通過內置代碼也無法獲得權限訪問到內層類,破壞代碼就自然無法生效。
存取控制器
(access controller):存取控制器可以控制核心API對操作系統的存取權限,而這個控制的策略設定,可以由用戶指定。
安全管理器
(security manager):是核心API和操作系統之間的主要接口。實現權限控制,比存取控制器優先級高。
安全軟件包
(security package):java.security下的類和擴展包下的類,允許用戶為自己的應用增加新的安全特性,包括:
安全提供者
消息摘要
數字簽名
加密
鑒別
JNI:Java Native Interface
本地接口的作用是融合不同的編程語言為Java所用,它的初衷是融合C/C++程序
native
:凡是帶native關鍵字的,說明java的作用范圍達不到了,會去調用底層c語言的庫!進入本地方法棧,調用
本地方法接口JNI
,拓展Java的使用,融合不同的語言為Java所用Java誕生的時候C、C++橫行,為了立足,必須要能調用C、C++的程序
于是在內存區域中專門開辟了一塊標記區域:Native Method Stack,登記Native方法
最終在執行引擎執行的的時候通過JNI(本地方法接口)加載本地方法庫的方法
程序計數器
:Program Counter Register每個線程都有一個程序計數器,是線程私有
的,就是一個指針,指向方法區中的方法字節碼(用來存儲指向像一條指令的地址,也即將要執行的指令代碼),在執行引擎讀取下一條指令,是一個非常小的內存空間
,幾乎可以忽略不計
方法區
:Method Area方法區是被所有線程共享,所有字段和方法字節碼,以及一些特殊方法,如構造函數,接口代碼也在此定義,簡單說,所有定義的方法的信息都保存在該區域,此區域屬于共享區間
;
方法區與Java堆一樣,是各個線程共享的內存區域,用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。雖然Java 虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆)
,目的應該是與Java 堆區分開來。
靜態變量(static)
常量(final)
類信息(構造方法、接口定義)
運行時的常量池
創建一個對象時,方法區中會生成對應類的抽象模板;還有對應的常量池、靜態變量、類信息、常量
我們通過類模板去new對象的時候
堆中存放實例對象
棧中存放對象的引用,每個對象對應一個地址指向堆中相同地址的實例對象
棧內存
,主管程序的運行,生命周期和線程同步,線程結束,棧內存就釋放了,
不存在垃圾回收
棧:先進后出
隊列:先進先出(FIFO)
8大基本類型
對象引用
實例的方法
棧表示Java方法執行的內存模型
每調用一個方法就會為每個方法生成一個棧幀(Stack Frame)
,每個方法被調用和完成的過程,都對應一個棧幀從虛擬機棧上入棧和出棧的過程。
程序正在執行的方法一定在棧的頂部
public class Test {
public static void main(String[] args) {
new Test().a();
}
public void a() {
b();
}
public void b() {
a();
}
}
Heap
,一個JVM只有一個堆內存(棧是線程級的),堆內存的大小是可以調節的
對象誕生、成長甚至死亡的區
Eden Space(伊甸園區)
:所有的對象都是在此new出來的
Survivor Space(幸存區)
幸存0區
(From Space
)(動態的,From和To會互相交換)
幸存1區
(To Space
)
Eden區占大容量,Survivor兩個區占小容量,默認比例是8:1:1
。
存儲的是Java運行時的一些環境或類信息,這個區域不存在垃圾回收!關閉虛擬機就會釋放這個區域內存!
這個區域常駐內存,用來存放JDK自身攜帶的Class對象、Interface元數據。
jdk1.6之前:永久代
jdk1.7:永久代
慢慢退化,去永久代
jdk1.8之后:永久代
改名為元空間
內存溢出
java.lang.OutOfMemoryError分配的太少
用的太多
用完沒釋放
GC垃圾回收,主要在年輕代和老年代
伊甸園區
假設伊甸園區
只能存一定數量的對象,則每當存滿時就會觸發一次輕GC(Minor GC)
輕GC
清理后,有的對象可能還存在引用,就活下來了,活下來的對象就進入幸存區
;有的對象沒用了,就被GC清理掉了;每次輕GC
都會使得伊甸園區
為空
如果幸存區
和伊甸園
都滿了,則會進入老年代
,如果老年代
滿了,就會觸發一次重GC(FullGC)
,年輕代+老年代
的對象都會清理一次,活下的對象就進入老年代
如果新生代
和老年代
都滿了,則OOM
OOM
堆內存
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垃圾回收信息
jvm試圖使用的最大內存
和jvm初始化內存
大小MAT(Eclipse)
JProfiler
分析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中看到,打開文件后生成了很多內容,占內存,建議刪除
2.下載客戶端 https://www.ej-technologies.com/download/jprofiler/files
打開IDEA的設置,找到Tools里面的JProfiler,沒有設置位置則設置位置
此時則全部安裝完成!
Garbage Collection:垃圾回收
JVM在進行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 操作。
標記-整理法
是
標記-清除法
的一個改進版。標記-清楚-壓縮法
標記階段,該算法也將所有對象標記為存活和死亡兩種狀態;
不同的是,在第二個階段,該算法并沒有直接對死亡的對象進行清理,而是將所有存活的對象整理一下,放到另一處空間,然后把剩下的所有對象全部清除。
標記清除
,到達一定量的時候再壓縮.
到此,關于“JVM的知識點有哪些”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。