仙桃網(wǎng)站設(shè)計網(wǎng)絡(luò)推廣的工作好做嗎
目錄
- JVM的位置
- 三種 JVM
- JVM體系結(jié)構(gòu)
- 類加載器
- 雙親委派機(jī)制
- 概念
- 例子
- 作用
- 沙箱安全機(jī)制
- 組成沙箱的基本組件
- Native
- JNI:Java Native Interface(本地方法接口)
- Native Method Stack(本地方法棧)
- PC寄存器(Program Counter Register)
- 方法區(qū)(Method Area)
- 棧(Java Stack)
- 棧 + 堆 + 方法區(qū):交互關(guān)系
- 堆(Heap)
- 新生區(qū) (伊甸園+幸存者區(qū)*2)
- 老年區(qū)
- 永久區(qū)
- 堆內(nèi)存調(diào)優(yōu)
- 報OOM怎么辦?
- GC(垃圾回收)
- 引用計數(shù)法
- 復(fù)制算法
- 標(biāo)記清除
- 標(biāo)記壓縮(標(biāo)記整理):再優(yōu)化
- 標(biāo)記清除壓縮:再優(yōu)化
- 分代收集算法
- 總結(jié)
JVM的位置
應(yīng)用程序(Java應(yīng)用程序)在JRE上運行(JRE包含JVM),JRE在操作系統(tǒng)(Windows、Mac)上運行,操作系統(tǒng)在硬件體系(Intel、Spac…)上運行。
三種 JVM
- Sun公司:HotSpot 用的最多(我們使用)
- BEA:JRockit
- IBM:J9VM
JVM體系結(jié)構(gòu)
JVM 調(diào)優(yōu):99%都是在方法區(qū)和堆,大部分時間調(diào)堆。 JNI(Java Native Interface):本地方法接口
類加載器
作用:加載class文件
例如:new Student();
(具體實例在堆里,引用變量名放棧里)
- 虛擬機(jī)自帶的加載器
- 啟動類(根)加載器
- 擴(kuò)展類加載器
- 應(yīng)用程序加載器
雙親委派機(jī)制
概念
當(dāng)某個類加載器需要加載某個.class文件時,它首先把這個任務(wù)委托給他的上級類加載器,遞歸這個操作,如果上級的類加載器沒有加載,自己才會去加載這個類。
例子
當(dāng)一個 Hello.class
這樣的文件要被加載時。
不考慮我們自定義類加載器,首先會在 AppClassLoader 中檢查是否加載過,如果有那就無需再加載了。如果沒有,那么會拿到父加載器,然后調(diào)用父加載器的 loadClass 方法。
父類中同理也會先檢查自己是否已經(jīng)加載過,如果沒有再往上。注意這個類似遞歸的過程,直到到達(dá) Bootstrap classLoader 之前,都是在檢查是否加載過,并不會選擇自己去加載。
直到 BootstrapClassLoader,已經(jīng)沒有父加載器了,這時候開始考慮自己是否能加載了,如果自己無法加載,會下沉到子加載器去加載,一直到最底層,如果沒有任何加載器能加載,就會拋出ClassNotFoundException
。
作用
- 1、防止重復(fù)加載同一個.class。通過委托去向上面問一問,加載過了,就不用再加載一遍。保證數(shù)據(jù)安全。
- 2、保證核心.class不能被篡改。通過委托方式,不會去篡改核心.class,即使篡改也不會去加載,即使加載也不會是同一個.class對象了。不同的加載器加載同一個.class也不是同一個Class對象。這樣保證了Class執(zhí)行安全。
比如:如果有人想替換系統(tǒng)級別的類:String.java。
篡改它的實現(xiàn),在這種機(jī)制下這些系統(tǒng)的類已經(jīng)被 Bootstrap classLoader 加載過了(為什么?因為當(dāng)一個類需要加載的時候,最先去嘗試加載的就是 BootstrapClassLoader ),所以其他類加載器并沒有機(jī)會再去加載,從一定程度上防止了危險代碼的植入。
沙箱安全機(jī)制
組成沙箱的基本組件
- 字節(jié)碼校驗器(bytecode verifier)
確保 Java 類文件 .Class 遵循 Java 語言規(guī)范。這樣可以幫助 Java 程序?qū)崿F(xiàn)內(nèi)存保護(hù)。但并不是所有的類文件都會經(jīng)過字節(jié)碼校驗,比如核心類。 - 類裝載器(class loader)
其中類裝載器在3個方面對 Java 沙箱起作用:- 它防止惡意代碼去干涉善意的代碼; //雙親委派模式
- 它守護(hù)了被信任的類庫邊界;
- 它將代碼歸入保護(hù)域,確定了代碼可以進(jìn)行哪些操作。
虛擬機(jī)為不同的類加載器載入的類提供不同的命名空間,命名空間由一系列唯一的名稱組成,每一個被裝載的類將有一個名字,這個命名空間是由 Java 虛擬機(jī)為每一個類裝載器維護(hù)的,它們互相之間甚至不可見。
類裝載器采用的機(jī)制是雙親委派模式。
1、從最內(nèi)層 JVM 自帶類加載器開始加載,外層惡意同名類得不到加載從而無法使用;
2、由于嚴(yán)格通過包來區(qū)分了訪問域,外層惡意的類通過內(nèi)置代碼也無法獲得權(quán)限訪問到內(nèi)層類,破壞代碼就自然無法生效。
- 存取控制器(access controller):存取控制器可以控制核心 API 對操作系統(tǒng)的存取權(quán)限,而這個控制的策略設(shè)定,可以由用戶指定。
- 安全管理器(security manager):是核心 API 和操作系統(tǒng)之間的主要接口。實現(xiàn)權(quán)限控制,比存取控制器優(yōu)先級高。
- 安全軟件包(security package):java.security 下的類和擴(kuò)展包下的類,允許用戶為自己的應(yīng)用增加新的安全特性,包括:
- 安全提供者
- 消息摘要
- 數(shù)字簽名 keytools https(需要證書)
- 加密
- 鑒別
Native
凡是帶了 native 關(guān)鍵字的,說明 Java 的作用范圍達(dá)不到了,得回去調(diào)用底層C語言的庫
凡是帶了 native 關(guān)鍵字的方法會進(jìn)入本地方法棧,其它的是 Java棧
JNI:Java Native Interface(本地方法接口)
調(diào)用本地方法接口(JNI)作用:
擴(kuò)展 Java 的使用,融合不同的編程語言為 Java 所用
Java 誕生的初衷是融合C/C++程序,C、C++橫行,想要立足,必須要有調(diào)用C、C++的程序,它在內(nèi)存區(qū)城中專門開辟了塊標(biāo)記區(qū)城: Native Method Stack
Native Method Stack(本地方法棧)
登記 native 方法,在執(zhí)行引擎(Execution Engine)執(zhí)行的時候。通過JNI (本地方法接口)加載**本地方法庫(Native Libraies)**中的方法。
在企業(yè)級應(yīng)用中少見,與硬件有關(guān)應(yīng)用:Java程序驅(qū)動打印機(jī),系統(tǒng)管理生產(chǎn)設(shè)備等
PC寄存器(Program Counter Register)
程序計數(shù)器: Program Counter Register
每個線程都有一個程序計數(shù)器,是線程私有的,就是一個指針, 指向方法區(qū)中的方法字節(jié)碼 ( 用來存儲指向下一條指令的地址, 也即將要執(zhí)行的指令代碼 ), 在執(zhí)行引擎讀取下一條指令,是一個非常小的內(nèi)存空間,幾乎可以忽略不計。
方法區(qū)(Method Area)
方法區(qū)是被所有線程共享,所有字段和方法字節(jié)碼,以及一些特殊方法,如構(gòu)造函數(shù),接口代碼也在此定義,簡單說:所有定義的方法的信息都保存在該區(qū)域,此區(qū)域?qū)儆诠蚕韰^(qū)間;
靜態(tài)變量、常量、類信息(構(gòu)造方法、接口定義)、運行時的常量池(如:static,final,,Class(類模板), 常量池)存在方法區(qū)中,但是實例變量存在堆內(nèi)存中,和方法區(qū)無關(guān)。
棧(Java Stack)
為什么 main() 先執(zhí)行,最后結(jié)束:(因為一開始 main() 先壓入棧)
棧:棧內(nèi)存,主管程序的運行,生命周期和線程同步。
線程結(jié)束,棧內(nèi)存也就釋放,對于棧來說,不存在垃圾回收問題。
棧存放:8大基本類型+對象引用+實例的方法。
棧運行原理:棧幀(局部變量表+操作數(shù)棧)每調(diào)用一個方法都有一個棧幀。
棧滿了 main() 無法結(jié)束,會拋出錯誤:棧溢出 StackOverflowError
棧 + 堆 + 方法區(qū):交互關(guān)系
堆(Heap)
一個 JVM 只有一個堆內(nèi)存,堆的大小是可以調(diào)節(jié)的。
類加載器讀取了類文件后,一般會把 類,方法,常量,變量,保存所有引用類型的真實對象放到堆中。
堆內(nèi)存細(xì)分3個區(qū)域:
- 新生區(qū)(伊甸園區(qū)) Young / new
- 養(yǎng)老區(qū) old
- 永久區(qū) Perm ,在JDK8以后,永久存儲區(qū)改了個名字 (元空間)
GC 垃圾回收,主要是在 伊甸園區(qū) 和 養(yǎng)老區(qū)。
假設(shè)內(nèi)存滿了,報錯 OOM:堆內(nèi)存不夠 OutOfMemoryError:Java heap space
//-Xms8m -Xmx8m -XX:+PrintGCDetails
public static void main(String[] args) {String str = "javajavajavajava";while (true){str += str + new Random().nextInt(888888888)+ new Random().nextInt(21_0000_0000);}
}
//OutOfMemoryError:Java heap space 堆內(nèi)存滿了
新生區(qū) (伊甸園+幸存者區(qū)*2)
- 類誕生和成長甚至死亡的地方
- 伊甸園,所有對象都是在伊甸園區(qū) new 出來的
- 幸存者區(qū)(from, to),輕GC定期清理伊甸園,活下來的放入幸存者區(qū),幸存者區(qū)滿了之后重GC 清理伊甸園+幸存者區(qū),活下來的放入養(yǎng)老區(qū)。都滿了就報 OOM。
注:經(jīng)過研究,99%的對象都是臨時對象!直接被清理了
老年區(qū)
新生區(qū)剩下來的,輕GC殺不死了
永久區(qū)
這個區(qū)域常駐內(nèi)存,用來存放 JDK 自身攜帶的 Class 對象,Interface 元數(shù)據(jù),存儲的是 Java 運行時的一些環(huán)境或類信息,該區(qū)域不存在垃圾回收GC。關(guān)閉虛擬機(jī)就會釋放這個內(nèi)存。
- jdk1.6之前:永久代,常量池在方法區(qū)。
- jdk1.7:永久代,但是慢慢退化了(去永久代)常量池在堆中。
- jdk1.8之后:無永久代,常量池在元空間。
常量池一直在方法區(qū),其中的字符串池 JDK1.7之后保存到了堆中。
永久區(qū) OOM 例子:一個啟動類,加載了大量的第三方j(luò)ar包。Tomcat 部署了太多的應(yīng)用,大量動態(tài)生成的反射類。不斷的被加載。直到內(nèi)存滿,就會出現(xiàn) OOM。
方法區(qū)又稱非堆 (non-heap),本質(zhì)還是堆,只是為了區(qū)分概念。
元空間邏輯上存在,物理上并不存在。
堆內(nèi)存調(diào)優(yōu)
public static void main(String[] args) {//返回虛擬機(jī)試圖使用的最大內(nèi)存long max = Runtime.getRuntime().maxMemory(); //字節(jié) 1024*1024//返回jvm初始化的總內(nèi)存long total = Runtime.getRuntime().totalMemory();System.out.println("max="+max+"字節(jié)\t"+(max/(double)1024/1024+"MB"));System.out.println("total="+total+"字節(jié)\t"+(total/(double)1024/1024+"MB"));/* 運行后:max=1866465280字節(jié) 1780.0MBtotal=126877696字節(jié) 121.0MB*///默認(rèn)情況下,分配的總內(nèi)存占電腦內(nèi)存1/4 初始化1/64
}
報OOM怎么辦?
-
1.嘗試擴(kuò)大堆內(nèi)存,如果還報錯,說明有死循環(huán)代碼 或垃圾代碼
Edit Configration>add VM option> 輸入:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
新生區(qū)+養(yǎng)老區(qū):305664K+699392K=1005056K = 981.5M ,說明元空間物理并不存在。 -
2.分析內(nèi)存,看一下哪個地方有問題(專業(yè)工具)
能夠看到代碼第幾行出錯:內(nèi)存快照分析工具,MAT,Jprofiler
MAT,Jprofiler作用:- 分析Dump內(nèi)存文件,快速定位內(nèi)存泄漏;
- 獲得堆中的數(shù)據(jù)
- 獲得大的對象
//-Xms 設(shè)置初始化內(nèi)存分配大小 默認(rèn)1/64
//-Xmx 設(shè)置最大分配內(nèi)存,默認(rèn)1/4
//-XX:+PrintGCDetails 打印GC垃圾回收信息
//-XX:+HeapDumpOnOutOfMemoryError //oom DUMP
//-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
public class Demo03 {byte[] array = new byte[1*1024*1024]; //1mpublic static void main(String[] args) {ArrayList<Demo03> list = new ArrayList<>();int count = 0;try {while (true){list.add(new Demo03()); //不停地把創(chuàng)建對象放進(jìn)列表count = count + 1;}} catch (Exception e) {System.out.println("count: "+count);e.printStackTrace();}}
}
GC(垃圾回收)
JVM在進(jìn)行GC時,并不是對新生代、幸存區(qū)、老年區(qū),這三個區(qū)域統(tǒng)一回收。大部分時候回收的是新生代
GC兩種:輕GC,重GC (Full GC,全局GC)
引用計數(shù)法
一般 JVM 不用,大型項目對象太多了
復(fù)制算法
-XX:MaxTenuringThreshold=15
設(shè)置進(jìn)入老年代的存活次數(shù)條件。
好處:沒有內(nèi)存的碎片,內(nèi)存效率高
壞處:浪費了內(nèi)存空間(一個幸存區(qū)永遠(yuǎn)是空的);假設(shè)對象100%存活,復(fù)制成本很高。
復(fù)制算法最佳使用場景:對象存活度較低的時候,新生區(qū)。
標(biāo)記清除
優(yōu)點:不需要額外空間,優(yōu)化了復(fù)制算法。
缺點:兩次掃描,嚴(yán)重浪費時間,會產(chǎn)生內(nèi)存碎片。
標(biāo)記壓縮(標(biāo)記整理):再優(yōu)化
三部曲:標(biāo)記–清除–壓縮
標(biāo)記清除壓縮:再優(yōu)化
每標(biāo)記清除幾次就壓縮一次,或者內(nèi)存碎片積累到一定程度就壓縮。
分代收集算法
根據(jù)內(nèi)存對象的存活周期不同,將內(nèi)存劃分成幾塊,JVM一般將內(nèi)存分成新生代和老生代。
在新生代中,有大量對象死去和少量對象存活,所以采用復(fù)制算法,只需要付出少量存活對象的復(fù)制成本就可以完成收集;
老年代中因為對象的存活率極高,沒有額外的空間對他進(jìn)行分配擔(dān)保,所以采用標(biāo)記清理或者標(biāo)記整理算法進(jìn)行回收;
總結(jié)
內(nèi)存效率:復(fù)制算法 > 標(biāo)記清除算法 > 標(biāo)記壓縮算法(時間復(fù)雜度)
內(nèi)存整齊度:復(fù)制算法 = 標(biāo)記壓縮算法 > 標(biāo)記清除算法
內(nèi)存利用率:標(biāo)記壓縮算法 = 標(biāo)記清除算法 > 復(fù)制算法
沒有最好的算法,只有合適的算法(GC也被稱為分代收集算法)。
- 年輕代:存活率低,用復(fù)制算法。
- 老年代:存活率高,區(qū)域大,用標(biāo)記-清除-壓縮。