Java学习笔记12——JVM入门
文章目錄
- JVM簡介和體系結構
- JVM的位置
- JVM的體系結構
- 類加載器
- 雙親委派機制
- 沙箱安全機制(了解即可)
- Native
- PC寄存器
- 方法區
- 棧
- 三種JVM
- 堆
- 新生區
- 永久區
- 堆內存調優
- Jprofiler的使用
- GC(垃圾回收)
- 引用計數法
- 復制算法
- 標記清除法
- 標記壓縮法
- GC算法總結
面試常見:
- 請你談談你對JVM的理解?
- java8虛擬機和之前的變化更新?
- 什么是OOM,什么是棧溢出StackOverFlowError? 怎么分析?
- JVM的常用調優參數有哪些?
- 內存快照如何抓取,怎么分析Dump文件?
- 談談JVM中,類加載器你的認識
JVM簡介和體系結構
JVM的位置
JVM上層就是一個個程序
JRE:Java運行環境,包含了JVM
操作系統、JRE、JVM本質上還是軟件
JVM的體系結構
Java棧、本地方法棧、程序計數器不會有垃圾產生,自然不存在垃圾回收。因為棧里的東西一用就出棧,不會有垃圾產生。
垃圾在堆中產生(方法區是特殊的堆),絕大部分的JVM調優都是對于堆而言的。
類加載器
作用:加載Class文件
類是模板,是抽象的,類實例化得到的對象是具體的。所有的對象反射回去得到的是同一個類模板。
代碼演示:
由此可知,由類加載器加載出來的Car Class是全局唯一的,這個Car Class所new出來的具體的Car對象各不相同,但是各種不相同的對象通過getClass獲得的.Class都是同一個Car Class
再看一段代碼:
public class Car {public static void main(String[] args) {Car car1 = new Car();Class<? extends Car> aClass1 = car1.getClass();ClassLoader classLoader = aClass1.getClassLoader();System.out.println(classLoader);//輸出:sun.misc.Launcher$AppClassLoader@18b4aac2System.out.println(classLoader.getParent());//輸出:sun.misc.Launcher$ExtClassLoader@677327b6System.out.println(classLoader.getParent().getParent());//輸出:null 當輸出為null:1.不存在 2.java程序獲取不到} }可見,這里的classLoader是AppClassLoader,它的父加載器是ExtClassLoader,而ExtClassLoader的父加載器并不是沒有才null,而是因為根加載器是C寫的,Java無法獲取。
需要了解的是:
Java程序在JRE中運行,分析jre文件下的包
- BootstrapClassLoader:加載jre/lib下的rt.jar包
- ExtClassLoader擴展類加載器:加載/jre/lib/ext下的所有包
- AppClassLoader應用(系統)類加載器:加載CLASSPATH路徑下的包
類在經過Class Loader之后的變化:
- 虛擬機自帶的加載器
- 啟動類(根)加載器 BootstrapClassLoader
- 擴展類加載器 ExtClassLoader
- 應用程序加載器 AppClassLoader
雙親委派機制
代碼演示:
自定義創建包java.lang,然后在包里創建String.java,然后寫toString方法,返回類型為自定義的String類
輸出:
錯誤: 在類 java.lang.String 中找不到 main 方法, 請將 main 方法定義為:
public static void main(String[] args)
否則 JavaFX 應用程序類必須擴展javafx.application.Application
分析:
App—>Ext—>Bootstrap (底——>頂層)
(雙親委派機制是為了保證安全)
雙親委派機制會從AppClassLoader開始檢查String類有沒有被加載,發現AppClassLoader有加載,但并不就此停止檢查,繼續自底向上到BootstrapClassLoader根加載器檢查,發現根加載器也加載了這個類,然后自頂向下加載類,最終執行了BootstrapClassLoader的String類。所以自定義String類報錯無法運行。
1、AppClassLoader收到類加載的請求;
2、將這個請求向上委托給父類加載器去完成,一 直向上委托,直到啟動BootstrapClassLoader;
3、啟動加載器檢查是否能夠加載當前這個類,能加載就結束, 使用當前的加載器。否則, 拋出異常,通知子加載器進行加載;
4、重復步驟3。
Class Not Found異常就是這么來的
Null:Java調用不到。?Java = C++去掉繁瑣的東西:指針,內存管理(Java交給JVM去做)~
- Java語言保留了C的接口,這些方法就是用native(本地)修飾的,Java通過native方法調用操作系統的方法。比如Thread的start方法,它是由C/C++語言實現的
沙箱安全機制(了解即可)
Java安全模型的核心就是Java沙箱(sandbox)。沙箱是一個限制程序運行的環境。沙箱機制就是將Java代碼限定在虛擬機 (JVM) 特定的運行范圍中,并且嚴格限制代碼對本地系統資源訪問,通過這樣的措施來保證對代碼的有效隔離,防止對本地系統造成破壞。沙箱主要限制系統資源訪問,那系統資源包括什么? CPU、內存、文件系統、網絡。不同級別的沙箱對這些資源訪問的限制也可以不一樣。
所有的Java程序運行都可以指定沙箱,可以定制安全策略。
在Java中將執行程序分成本地代碼和遠程代碼兩種,本地代碼默認視為可信任的,而遠程代碼則被看作是不受信的。對于授信的本地代碼,可以訪問一切本地資源。而對于非授信的遠程代碼在早期的Java實現中,安全依賴于沙箱機制。如下圖所示JDK1.0安全模型
但如此嚴格的安全機制也給程序的功能擴展帶來障礙,比如當用戶希望遠程代碼訪問本地系統的文件時候,就無法實現。因此在后續的Java1.1版本中,針對安全機制做了改進,增加了安全策略,允許用戶指定代碼對本地資源的訪問權限。如下圖所示JDK1.1安全模型
在Java1.2版本中,再次改進了安全機制,增加了代碼簽名。不論本地代碼或是遠程代碼,都會按照用戶的安全策略設定,由類加載器加載到虛擬機中權限不同的運行空間,來實現差異化的代碼執行權限控制。如下圖所示
當前最新的安全機制實現,則引入了域(Domain)的概念。虛擬機會把所有代碼加載到不同的系統域和應用域,系統域部分專門負責與關鍵資源進行交互,而各個應用域部分則通過系統域的部分代理來對各種需要的資源進行訪問。虛擬機中不同的受保護域,對應不一樣的權限。存在于不同域中的類文件就具有了當前域的全部權限,如下圖所示最新的安全模型(jdk 1.6)
組成沙箱的基本組件
- 字節碼校驗器(bytecode
verifier):確保Java類文件遵循Java語言規范。這樣可以幫助Java程序實現內存保護。但并不是所有的類文件都會經過字節碼校驗,比如核心類。 - 類裝載器(class loader) :其中類裝載器在3個方面對Java沙箱起作用
它防止惡意代碼去干涉善意的代碼;
它守護了被信任的類庫邊界;
它將代碼歸入保護域,確定了代碼可以進行哪些操作。
虛擬機為不同的類加載器載入的類提供不同的命名空間,命名空間由一系列唯一的名稱組成, 每一個被裝載的類將有一個名字,這個命名空間是由Java虛擬機為每一個類裝載器維護的,它們互相之間甚至不可見。 ?
類裝載器采用的機制是雙親委派模式。
-
從最內層JVM自帶類加載器開始加載,外層惡意同名類得不到加載從而無法使用
-
由于嚴格通過包來區分了訪問域,外層惡意的類通過內置代碼也無法獲得權限訪問到內層類,破壞代碼就自然無法生效。
-
存取控制器(access controller):存取控制器可以控制核心API對操作系統的存取權限,而這個控制的策略設定,可以由用戶指定。
-
安全管理器(security manager):是核心API和操作系統之間的主要接口。實現權限控制,比存取控制器優先級高。
-
安全軟件包(security package):java.security下的類和擴展包下的類,允許用戶為自己的應用增加新的安全特性,包括:安全提供者、消息摘要、數字簽名、加密、鑒別
Native
-
凡是帶了native關鍵字的,說明java的作用范圍達不到了,會去調用底層c語言的庫
會進入本地方法棧,調用本地方法本地接口 JNI (Java Native Interface) -
JNI作用:開拓Java的使用,融合不同的編程語言為Java所用,最初: C、C++
-
Java誕生的時候C、C++橫行,想要立足,必須要有調用C、C++的程序
-
它在內存區域中專門開辟了一塊標記區域: Native Method Stack,登記native方法
-
在最終執行的時候,加載本地方法庫中的方法通過JNI
例如:Java程序驅動打印機,管理計算機系統,掌握即可,在企業級應用比較少
目前該方法使用的越來越少了,除非是與硬件有關的應用,比如通過Java程序驅動打印機或者Java系統管理設備,在企業級應用中已經比較少見。因為現在的異構領域間通信很發達,比如可以使用Socket通信,也可以使用Web Service等等,不多做介紹
Native Method Stack
它的具體做法是Native Method Stack 中登記native方法,在 ( Execution Engine ) 執行引擎 執行的時候加載Native Libraies。【本地庫】
PC寄存器
程序計數器:Program Counter Register
每個線程都有一個程序計數器,是線程私有的,就是一個指針, 指向方法區中的方法字節碼(用來存儲指向像一條指令的地址, 也即將要執行的指令代碼),在執行引擎讀取下一條指令, 是一個非常小的內存空間,幾乎可以忽略不計
方法區
方法區是被所有線程共享,所有字段和方法字節碼,以及一些特殊方法,如構造函數,接口代碼也在此定義,簡單說,所有定義的方法的信息都保存在該區域,此區域屬于共享區間;
靜態變量、常量、類信息(構造方法、接口定義)、運行時的常量池存在方法區中,但是實例變量存在堆內存中,和方法區無關。
棧
棧:數據結構
程序 = 數據結構+算法︰持續學習~
程序 = 框架+業務邏輯︰吃飯~
棧:先進后出、后進先出,桶
隊列:先進先出(FIFO)
棧:棧內存
主管程序的運行,生命周期和線程同步;線程結束,棧內存也就釋放,對于棧來說,不存在垃圾回收的問題。
一旦線程結束,棧就Over
棧存放的內容:8大基本類型+對象引用+實例的方法
棧運行原理:棧幀
棧幀:局部變量表+操作數棧
每執行一個方法,就會產生一個棧幀。程序正在運行的方法永遠都會在棧的頂部
棧滿了,棧溢出錯誤: StackOverflowError
棧+堆+方法區:交互關系
三種JVM
Sun公司HotSpot java Hotspot?64-Bit server vw (build 25.181-b13,mixed mode)
BEA JRockit
IBM 39 VM
我們學習都是:Hotspot
堆
堆(Heap),一個JVM只有一個堆內存,堆內存的大小是可以調節的。
類加載器讀取了類文件后,一般會把什么東西放到堆中?
類,方法,常量,變量~,保存我們所有引用類型的真實對象;
堆內存中還要細分為三個區域:
- 新生區(伊甸園區) Young/New
- 老年區 Old
- 永久區 Perm
GC:Garbage recycling
輕GC:輕量級垃圾回收,主要是在新生區
重GC(Full GC):重量級垃圾回收,主要是養老區,重GC就說明內存都要爆了
GC垃圾回收,主要是在伊甸園區和養老區~
假設內存滿了,OOM(Out Of memory),堆內存不夠!java.lang.OutOfMemoryError: Java heap space
在JDK8以后,永久存儲區改了個名字(元空間)
經過研究,99%的對象都是臨時對象
新生區
- 類:誕生和成長的地方,甚至死亡
- 伊甸區:所有的對象都是在伊甸區new出來的
- 幸存者區(0區和1區)
例圖,假如Eden區域可以放10個對象,當Eden區域滿的時候,進行一次輕GC,此時假設有一個對象到了service 0區域。如果service 0 區域也滿了,那么就會調用一次重GC,將對象放入老年代中。
但是,如果新生代與老年代區域都滿了,那么就會報OOM錯誤
永久區
這個區域常駐內存的。用來存放JDK自身攜帶的Class對象。Interface元數據,存儲的是Java運行時的一些環境或類信息~
這個區域不存在垃圾回收!
關閉虛擬機就會釋放這個區域的內存~
一個啟動類,加載了大量的第三方jar包。Tomcat部署了太多的應用,大量動態生成的反射類。不斷地被加載。直到內存滿,就會出現OOM
jdk1.6之前︰永久代,常量池是在方法區;
jdk1.7:永久代,但是慢慢的退化了,去永久代,常量池在堆中
jdk1.8之后:無永久代,常量池在元空間
方法區中那個小框是:常量池
(方法區和堆內存物理上連續,邏輯上分開,方法區是共享的,所以有人認為它不屬于堆)
元空間又被稱為非堆,但它也是堆,也有人認為右側區域是堆空間,只是個人理解不同。
元空間:邏輯上存在,物理上不存在 (因為存儲在本地磁盤內) 而并不算在JVM虛擬機內存中,也就是并沒有占堆內存。
堆內存調優
在默認情況下:分配給JVM試圖使用的最大總內存是電腦內存的 1/4,而JVM初始化的總內存是電腦內存的 1/64
可以通過調整這個參數(Edit Configuration—>VM options)控制Java虛擬機初始內存和分配的總內存的大小。
測試:
// 在VM options里面設置如下參數 //-Xms8m -Xmx8m -XX:+PrintGCDetails public class Hello{public static void main( String[ ] args) istring str = "kuangshensayjava";while (true){str += str + new Random( ) .nextInt( bound: 888888888)+new Random( ) .nextInt( bound: 99999999);} }可見新生代、老年代、元空間都滿了,然后報OOM錯誤
嘗試應對OOM錯誤:
1、嘗試擴大堆內存
2、分析內存和代碼,看看哪個地方出現問題使用Debug或專業工具(如內存快照分析工具:MAT,Jprofiler)
MAT,Jprofiler作用
- 分析Dump內存文件,快速定位內存泄露
- 獲得堆中的數據
- 獲得大的對象
- ……
MAT最早集成于Eclipse中,IDEA中可以使用Jprofiles插件,在Settings—>Plugins中搜索Jprofiler,安裝改插件即可使用
Jprofiler的使用
1、在idea中下載 jprofile 插件
2、百度搜索官網下載 jprofile 客戶端 ,安裝路徑要求:沒有中文沒有空格
3、安裝破解完之后,在IDEA的Settings—>Tools下找到 jprofiles,然后綁定安裝目錄bin下的.exe文件
4、在idea中VM參數中寫參數 -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError (假如堆內存heap出現了OOM則dump出這個異常)
5、運行程序后在jprofile客戶端中打開(dump出的文件應該在src目錄下)找到錯誤 告訴哪個位置報錯
命令參數詳解
- -Xms(設置初始化內存分配大小,默認計算機內存的1/64)
- -Xmx(設置最大分配內存,默以計算機內存的1/4)
- -XX: +PrintGCDetails (打印GC垃圾回收信息)
- -XX: +HeapDumpOnOutOfMemoryError (OOM DUMP)
GC(垃圾回收)
JVM在進行GC時,并不是對這三個區域統一回收。大部分時候,回收都是新生代~
- 新生代
- 幸存區(form , to)
- 老年區
GC兩種類:輕GC(普通的GC),重GC(全局GC)
幸存0區和幸存1區兩者是會交替的,from和to的關系會交替變化。
建議參考:為什么JVM新生代需要兩個Survivor區
題目:
JVM的內存模型和分區,詳細到每個區放什么?
堆里面的分區有哪些? Eden,from,to,老年區,
說說這些分區的特點。
GC的算法有哪些? 標記清除法,標記壓縮(標記整理)法,復制算法,引用計數法
這些GC算法怎么用的?
輕GC和重GC分別在什么時候發生?
引用計數法
復制算法
漫畫演示復制算法:
第一次GC后,Eden區和to區空了,經過15次垃圾回收后依然存活下來的對象就會去養老區
- 好處:沒有內存的碎片
- 壞處:浪費了內存空間(一個幸存區的空間永遠是空:to)。假設對象100%存活(極端情況)
復制算法最佳使用場景:對象存活度較低的時候;新生區~
標記清除法
- 優點:不需要額外的空間
- 缺點:兩次掃描,嚴重浪費時間,會產生內存碎片
標記壓縮法
優化標記清除法:先標記清除幾次之后,再壓縮1次。
GC算法總結
- 內存效率:復制算法>標記清除算法>標記壓縮算法(時間復雜度)
- 內存整齊度:復制算法=標記壓縮算法>標記清除算法
- 內存利用率:標記壓縮算法=標記清除算法>復制算法
思考一個問題:難道沒有最優算法嗎?
答案:沒有,沒有最好的算法,只有最合適的算法 —> GC:分代收集算法
新生代:
- 存活率低
- 復制算法
老年代:
- 區域大:存活率
- 標記清除 (內存碎片不是太多) + 標記壓縮混合實現
一天時間學JVM,不現實,要深究,必須要下去花時間,和多看面試題,以及《深入理解JVM》
但是,我們可以掌握一個學習JVM的方法~
總結
以上是生活随笔為你收集整理的Java学习笔记12——JVM入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于R语言的地理探测器实现与问题研究
- 下一篇: [Leetcode][第1392题][J