JVM笔记(JVM内存+垃圾回收器)详解
一:java代碼的執(zhí)行流程(引出JVM)
- 首先由程序員編寫成.java文件
- 然后由javac(java編輯器)將.java文件編譯成.class文件
- .class文件可以在不同平臺(tái)/操作系統(tǒng)上的JVM上執(zhí)行
- 再由JVM編譯成可供不同操作系統(tǒng)識(shí)別的機(jī)器碼(0,1二進(jìn)制)
二:JVM來(lái)源
我們?cè)谙螺dJDK的時(shí)候,也就把JVM給下載下來(lái)了 (因?yàn)镴DK包含JRE,JRE包含JVM)
1:JRE: Java Runtime Environment
JRE顧名思義是java運(yùn)行時(shí)環(huán)境,包含了java虛擬機(jī),java基礎(chǔ)類庫(kù)。是使用java語(yǔ)言編寫的程序運(yùn)行所需要的軟件環(huán)境,是提供給想運(yùn)行java程序的用戶使用的。
2:JDK:Java Development Kit
顧名思義是java開發(fā)工具包,是程序員使用java語(yǔ)言編寫java程序所需的開發(fā)工具包,是提供給程序員使用的。JDK包含了JRE,同時(shí)還包含了編譯java源碼的編譯器javac,還包含了很多java程序調(diào)試和分析的工具:jconsole,jvisualvm等工具軟件,還包含了java程序編寫所需的文檔和demo例子程序。
3.JVM虛擬機(jī)
JVM(JAVA虛擬機(jī))是運(yùn)行Java字節(jié)碼的虛擬機(jī),通過(guò)編譯.java文件為.class文件得到字節(jié)碼文件 . .class文件包含JVM可以理解的字節(jié)碼。
在現(xiàn)實(shí)世界中,JVM是一種規(guī)范,它提供可以執(zhí)行Java字節(jié)碼的運(yùn)行時(shí)環(huán)境。
如果你需要運(yùn)行java程序,只需安裝JRE就可以了。如果你需要編寫java程序,需要安裝JDK。
三:JVM在Java程序運(yùn)行當(dāng)中的位置
JVM之所以稱為虛擬機(jī),是因?yàn)樗峁┝艘粋€(gè)不依賴于底層操作系統(tǒng)和機(jī)器硬件體系結(jié)構(gòu)的機(jī)器接口。這種與硬件和操作系統(tǒng)的獨(dú)立性使得Java程序“寫一次,到處運(yùn)行”(write-once-run-anywhere).
四:JVM的體系結(jié)構(gòu)
1:圖示
那么我們分層來(lái)解析這個(gè)體系結(jié)構(gòu)的重要的部分
2:類加載器系統(tǒng)
(1):類加載機(jī)制
a:首先要知道 java程序運(yùn)行的幾個(gè)階段
b:累加載機(jī)制
-
類加載顧名思義就是把類加載到 JVM 中,而輸出一段二進(jìn)制流到內(nèi)存,之后經(jīng)過(guò)一番解析、處理轉(zhuǎn)化成可用的 class 對(duì)象,這就是類加載要做的事情。
-
二進(jìn)制流可以來(lái)源于 class 文件,或者通過(guò)字節(jié)碼工具生成的字節(jié)碼或者來(lái)自于網(wǎng)絡(luò)都行,只要符合格式的二進(jìn)制流,JVM 來(lái)者不拒
-
類加載流程分為加載、連接、初始化三個(gè)階段,連接還能拆分為:驗(yàn)證、準(zhǔn)備、解析三個(gè)階段。
-
加載:JVM在該階段的主要目的使將字節(jié)碼從不同的數(shù)據(jù)源(可能是class文件,也可能是jar包,甚至使網(wǎng)絡(luò))轉(zhuǎn)化為二進(jìn)制字 節(jié)流加載到內(nèi)存中方法區(qū)中,并在內(nèi)存中生成一個(gè)代表該類的Class對(duì)象
-
驗(yàn)證:主要是驗(yàn)證加載進(jìn)來(lái)的二進(jìn)制流是否符合一定格式,是否規(guī)范,是否符合當(dāng)前 JVM 版本等等之類的驗(yàn)證。
-
準(zhǔn)備:為靜態(tài)變量(類變量)賦初始值,也即為它們?cè)诜椒▍^(qū)劃分內(nèi)存空間。這里注意是靜態(tài)變量,并且是初始值,比如 int 的初始值是 0。
-
解析:將常量池的符號(hào)引用轉(zhuǎn)化成直接引用。符號(hào)引用可以理解為只是個(gè)替代的標(biāo)簽,比如你此時(shí)要做一個(gè)計(jì)劃,暫時(shí)還沒有人選,你設(shè)定了個(gè) A 去做這個(gè)事。然后等計(jì)劃真的要落地的時(shí)候肯定要找到確定的人選,到時(shí)候就是小明去做一件事。 解析就是把 A(符號(hào)引用) 替換成小明(直接引用)。符號(hào)引用就是一個(gè)字面量,沒有什么實(shí)質(zhì)性的意義,只是一個(gè)代表。直接引用指的是一個(gè)真實(shí)引用,在內(nèi)存中可以通過(guò)這個(gè)引用查找到目標(biāo)。
-
初始化:這時(shí)候就執(zhí)行一些靜態(tài)代碼塊,為靜態(tài)變量賦值,這里的賦值才是代碼里面的賦值,準(zhǔn)備階段只是設(shè)置初始值占個(gè)坑。
c:類加載的補(bǔ)充
那么這個(gè)類加載中 加載階段 我們從不同的數(shù)據(jù)源找這個(gè).class文件是如何找到的呢?
-加載類,JVM有三種類加載方式:Bootstrap(啟動(dòng)類加載器),extension(擴(kuò)展類加載器),application(系統(tǒng)類加載器)
-當(dāng)加載類文件時(shí),JVM會(huì)找到某個(gè)任意類XYZ.class的依賴項(xiàng)。
- 第一個(gè)啟動(dòng)類載入器試圖查找類,它會(huì)掃描lib文件夾下的rt.jar文件
- 如果沒有找到類,則extension類加載器會(huì)在jre\lib\ext文件夾下查找該類
- 同樣沒有找到類,application類加載器會(huì)在系統(tǒng)CLASSPATH環(huán)境變量中查詢所有的Jar文件和類.
- 如果類被任何加載器發(fā)現(xiàn),則被類加載器載入,否則拋出異常:ClassNotFoundException。
(2):類加載器
首先這個(gè)說(shuō)到類加載器肯定是要講到雙親委派模型的
- 類加載器:講到類加載不得不講到類加載的順序和類加載器。Java 中大概有四種類加載器,分別是:啟動(dòng)類加載器(Bootstrap ClassLoader),擴(kuò)展類加載器(Extension ClassLoader),系統(tǒng)類加載器(System ClassLoader),自定義類加載器(Custom ClassLoader),依次屬于繼承關(guān)系(注意這里的繼承不是 Java 類里面的 extends)
- 啟動(dòng)類加載器(Bootstrap ClassLoader):主要負(fù)責(zé)加載存放在Java_Home/jre/lib下,或被-Xbootclasspath參數(shù)指定的路徑下的,并且能被虛擬機(jī)識(shí)別的類庫(kù)(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader加載),啟動(dòng)類加載器是無(wú)法被Java程序直接引用的。
- 擴(kuò)展類加載器(Extension ClassLoader):主要負(fù)責(zé)加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載Java_Home/jre/lib/ext目錄中,或者由java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫(kù)(如javax.*開頭的類),開發(fā)者可以直接使用擴(kuò)展類加載器。
- 系統(tǒng)類加載器(System ClassLoader):主要負(fù)責(zé)加載器由sun.misc.Launcher$AppClassLoader來(lái)實(shí)現(xiàn),它負(fù)責(zé)加載用戶類路徑(ClassPath)所指定的類,開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有自定義過(guò)自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。
- 自定義類加載器(Custom ClassLoader:自己開發(fā)的類加載器
- 雙親委派模型不是一種強(qiáng)制性約束,也就是你不這么做也不會(huì)報(bào)錯(cuò)怎樣的,它是一種JAVA設(shè)計(jì)者推薦使用類加載器的方式。
為甚要雙親委派
其實(shí)就是規(guī)范,當(dāng)我們加載這個(gè).Class字節(jié)碼文件的時(shí)候,是從本地磁盤中找這個(gè)文件的,那么入如果有在不同文件目錄下的相同的.Class文件的時(shí)候 那就該如何選擇呢,這時(shí)就需要一定的規(guī)范的和準(zhǔn)則了。
它使得類有了層次的劃分。就拿 java.lang.Object 來(lái)說(shuō),加載它經(jīng)過(guò)一層層委托最終是由Bootstrap ClassLoader來(lái)加載的,也就是最終都是由Bootstrap ClassLoader去找<JAVA_HOME>\lib中rt.jar里面的java.lang.Object加載到JVM中。
這樣如果有不法分子自己造了個(gè)java.lang.Object,里面嵌了不好的代碼,如果我們是按照雙親委派模型來(lái)實(shí)現(xiàn)的話,最終加載到JVM中的只會(huì)是我們r(jià)t.jar里面的東西,也就是這些核心的基礎(chǔ)類代碼得到了保護(hù)。
因?yàn)檫@個(gè)機(jī)制使得系統(tǒng)中只會(huì)出現(xiàn)一個(gè)java.lang.Object。不會(huì)亂套了。你想想如果我們JVM里面有兩個(gè)Object,那豈不是天下大亂了。
3:JVM的內(nèi)存空間
(1):前言
主要是考慮jvm的內(nèi)存和實(shí)際物理內(nèi)存的關(guān)系,首先可以知道的jvm(虛擬機(jī))的內(nèi)存模型是虛擬出來(lái)的模仿實(shí)際操作系統(tǒng)內(nèi)存的,但我們new 出來(lái)一個(gè)對(duì)象是占用虛擬機(jī)的內(nèi)存,同時(shí)也占用物理內(nèi)存(虛擬機(jī)中的內(nèi)存是與計(jì)算機(jī)的物理內(nèi)存映射的)。
(2):JVM的體系結(jié)構(gòu)圖
(3):操作系統(tǒng)的內(nèi)存布局
a:前言
為了解JVM的體系結(jié)構(gòu)圖,我們非常有必要先了解一下操作系統(tǒng)的內(nèi)存基本結(jié)構(gòu)
b:操作系統(tǒng)的內(nèi)存布局
c:操作系統(tǒng)的JVM
為什么jvm的內(nèi)存是分布在操作系統(tǒng)的堆中呢??因?yàn)椴僮飨到y(tǒng)的棧是操作系統(tǒng)管理的,它隨時(shí)會(huì)被回收,所以如果jvm放在棧中,那java的一個(gè)new的對(duì)象就很難確定會(huì)被誰(shuí)回收了,那gc的存在就一點(diǎn)意義都沒有了,而要對(duì)棧做到自動(dòng)釋放也是jvm需要考慮的,所以放在堆中就最合適不過(guò)了。
d:操作系統(tǒng)+jvm的內(nèi)存簡(jiǎn)單布局
- 從這個(gè)圖,你應(yīng)該不難發(fā)現(xiàn),原來(lái)jvm的設(shè)計(jì)的模型其實(shí)就是操作系統(tǒng)的模型,
- **基于操作系統(tǒng)的角度,jvm就是個(gè)該死的java.exe/javaw.exe,也就是一個(gè)應(yīng)用,用來(lái)加載.class文件
- 而基于class文件來(lái)說(shuō),jvm就是個(gè)操作系統(tǒng),**
- 而jvm的方法區(qū),也就相當(dāng)于操作系統(tǒng)的硬盤區(qū),
- 而java棧和操作系統(tǒng)棧是一致的,無(wú)論是生長(zhǎng)方向還是管理的方式,
- 至于堆嘛,雖然概念上一致目標(biāo)也一致,分配內(nèi)存的方式也一直(new,或者malloc等等),但是由于他們的管理方式不同,jvm是gc回收,而操作系統(tǒng)是程序員手動(dòng)釋放,所以在算法上有很多的差異,gc的回收算法。
e:操作系統(tǒng)+jvm的內(nèi)存簡(jiǎn)單布局+PC寄存器
將這個(gè)圖和上面的圖對(duì)比多了什么?沒錯(cuò),多了一個(gè)pc寄存器,我為什么要畫出來(lái),主要是要告訴你,所謂pc寄存器,無(wú)論是在虛擬機(jī)中還是在我們虛擬機(jī)所寄宿的操作系統(tǒng)中功能目的是一致的,計(jì)算機(jī)上的pc寄存器是計(jì)算機(jī)上的硬件,本來(lái)就是屬于計(jì)算機(jī),(這一點(diǎn)對(duì)于學(xué)過(guò)匯編的同學(xué)應(yīng)該很容易理解,有很多的寄存器eax,esp之類的32位寄存器,jvm里的寄存器就相當(dāng)于匯編里的esp寄存器),計(jì)算機(jī)用pc寄存器來(lái)存放“偽指令”或地址,而相對(duì)于虛擬機(jī),pc寄存器它表現(xiàn)為一塊內(nèi)存(一個(gè)字長(zhǎng),虛擬機(jī)要求字長(zhǎng)最小為32位),虛擬機(jī)的pc寄存器的功能也是存放偽指令,更確切的說(shuō)存放的是將要執(zhí)行指令的地址,它甚至可以是操作系統(tǒng)指令的本地地址,當(dāng)虛擬機(jī)正在執(zhí)行的方法是一個(gè)本地方法的時(shí)候,jvm的pc寄存器存儲(chǔ)的值是undefined,所以你現(xiàn)在應(yīng)該很明確的知道,虛擬機(jī)的pc寄存器是用于存放下一條將要執(zhí)行的指令的地址(字節(jié)碼流)。
f:操作系統(tǒng)+jvm的內(nèi)存簡(jiǎn)單布局+PC寄存器+classLoader
- 這個(gè)圖是要告訴你,當(dāng)一個(gè)classLoder啟動(dòng)的時(shí)候,classLoader的生存地點(diǎn)在jvm的堆,
- 然后它會(huì)去主機(jī)硬盤上將A.class裝載到j(luò)vm的方法區(qū),
- 方法區(qū)中的這個(gè)字節(jié)文件會(huì)被虛擬機(jī)拿來(lái)new A字節(jié)碼(),然后在堆內(nèi)存生成了一個(gè)A字節(jié)碼的對(duì)象,
- 然后A字節(jié)碼這個(gè)內(nèi)存文件有兩個(gè)引用一個(gè)指向A的Class對(duì)象,一個(gè)指向加載自classLoader
- 那么這個(gè)字節(jié)碼文件中還有什么信息呢
補(bǔ)充:
這里的A.class文件存放的位置 主機(jī)磁盤,就是我們類加載階段中的加載 通過(guò)不同的類加載器
在主機(jī)不同的文件夾中找.class
(4):JVM內(nèi)存中的棧和堆
a:Java內(nèi)存分配中的棧
在函數(shù)中定義的一些基本類型的變量數(shù)據(jù)和對(duì)象的引用變量都在函數(shù)的棧內(nèi)存中分配。當(dāng)在一段代碼塊定義一個(gè)變量時(shí),Java就在棧中為這個(gè)變量分配內(nèi)存空間,當(dāng)該變量退出該作用域后,Java會(huì)自動(dòng)釋放掉為該變量所分配的內(nèi)存空間,該內(nèi)存空間可以立即被另作他用。
b:Java內(nèi)存分配中的堆
-
堆內(nèi)存用來(lái)存放由new創(chuàng)建的對(duì)象和數(shù)組。 在堆中分配的內(nèi)存,由Java虛擬機(jī)的自動(dòng)垃圾回收器來(lái)管理。
-
在堆中產(chǎn)生了一個(gè)數(shù)組或?qū)ο蠛?#xff0c;還可以 在棧中定義一個(gè)特殊的變量,讓棧中這個(gè)變量的取值等于數(shù)組或?qū)ο笤诙褍?nèi)存中的首地址,棧中的這個(gè)變量就成了數(shù)組或?qū)ο蟮囊米兞俊R米兞烤拖喈?dāng)于是為數(shù)組或?qū)ο笃鸬囊粋€(gè)名稱,以后就可以在程序中使用棧中的引用變量來(lái)訪問(wèn)堆中的數(shù)組或?qū)ο蟆R米兞烤拖喈?dāng)于是為數(shù)組或者對(duì)象起的一個(gè)名稱。
-
引用變量是普通的變量,定義時(shí)在棧中分配,引用變量在程序運(yùn)行到其作用域之外后被釋放。而數(shù)組和對(duì)象本身在堆中分配,即使程序運(yùn)行到使用 new 產(chǎn)生數(shù)組或者對(duì)象的語(yǔ)句所在的代碼塊之外,數(shù)組和對(duì)象本身占據(jù)的內(nèi)存不會(huì)被釋放,數(shù)組和對(duì)象在沒有引用變量指向它的時(shí)候,才變?yōu)槔?#xff0c;不能在被使用,但仍 然占據(jù)內(nèi)存空間不放,在隨后的一個(gè)不確定的時(shí)間被垃圾回收器收走(釋放掉)。這也是 Java 比較占內(nèi)存的原因。
-
實(shí)際上,棧中的變量指向堆內(nèi)存中的變量,這就是Java中的指針!
4:JVM的垃圾回收器
JVM筆記之垃圾回收器
總結(jié)
以上是生活随笔為你收集整理的JVM笔记(JVM内存+垃圾回收器)详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 长安花3000亿元入股华为车BU?双方均
- 下一篇: 比亚迪赢得加州纯电动校车合同