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