摘要:虛擬機學習是一個虛構出來的計算機有自己的處理器堆棧寄存器以及相應的指令系統等。類裝載器子系統涉及虛擬機的其它組成部分和來自庫的類。運行中的程序的每一個線程都是一個獨立的虛擬機執行引擎的實例。
Java虛擬機學習 JVM
JVM是一個虛構出來的計算機,有自己的處理器,堆棧,寄存器以及相應的指令系統等。JVM是JRE的一部分,通過在實際的計算機上仿真模擬各種計算機功能,這樣就能使Java在跨平臺上運行。
JVM內存區域劃分JVM的內部體系結構分為三個部分,分別為類裝載器子系統,運行時數據區和執行引擎。
類裝載器子系統(ClassLoader)每個Java虛擬機都有一個類加載器,負責查找并加載程序中的類,接口,并給其確定唯一的名字。Java虛擬機有兩種類裝載器:系統類裝載器和用戶自定義類裝載器,系統類裝載器是JVM實現的一部分,用戶自定義類裝載器是Java程序的一部分,其必須是類裝載器ClassLoader類的子類。
啟動類裝載器(bootstrap calss loader): 其用來加載Java的核心庫,用原生代碼來實現的,沒有繼承java.lang.ClassLoader
擴展類裝載器(extensions class loader): 其用來加載Java的擴展庫,Jav虛擬機的實現會提供一個擴展庫目錄,該裝載器就是在這個目錄下查找加載類。
應用程序類裝載器(application class loader): 其根據java應用的類路徑(classpath)來加載Java應用的類。通過ClassLoader.getSystemClassLoader()獲取它。
用戶自定義裝載器(user class loader): 除了系統提供的類裝載器之外,我們還可以通過繼承java.lang.ClassLoader類的方式來實現自己的類裝載器來滿足一些特殊的需求。
類裝載器子系統涉及Java虛擬機的其它組成部分和來自java.lang庫的類。ClassLoader類定義的方法為程序提供了訪問類裝載器機制的接口。對于每個被裝載的類型,Java虛擬機都會給它創建一個java.lang.Class類的實例來代表該類型。和其它對象一樣,用戶自定義的類裝載器以及Class類的實例放在內存的堆區,裝載的類型信息位于方法區。
類裝載器子系統除了要查找定位導入二進制class文件外,還需要負責驗證被導入的類的正確性,為類的類變量分配并初始化內存,以及解析符號引用。順序是:
裝載(查找并裝載類型的二進制數據)——>連接(驗證:確保被導入類型的正確性,準備:為類變量分配內存,并將其初始化為默認值,解析:把類型中的符號引用轉換為直接引用)——>初始化(將類變量初始化為正確的初始值)
+----2017-12-16-----+
1. 裝載:裝載是指將編譯后的Java類文件(.class文件)中的二進制數據讀入內存,并將其放在運行時數據區的方法區內,然后在堆區創建一個java.lang.Class對象,用其來封裝類在方法區的數據結構.即加載后最后得到的是Class對象,該對象是單實例的,即無論這個類創建了多少個對象,他的Class對象是唯一的.通過Class.forName(類的全路徑), 實例對象.class, 實例對象getClass() 這三種可以加載并獲取到該類的Class對象. 類裝載時類中的靜態代碼會被執行,例如:Class.forName()加載JDBC驅動 2. 連接:靜態變量的第一次賦值----默認值 3. 初始化:靜態變量第二次賦值----真正的初始值 類的初始化發生在Java程序對類的首次**主動使用**中,主動使用有(創建類的實例,訪問操作類或接口的靜態變量,調用類的靜態方法,反射如:Class.forName(類全路徑),初始化此類的子類,Java虛擬機啟動時被表明為啟動類的類:java Test),除以上外其他對類的被動使用是不會導致類的初始化.執行引擎:執行字節碼或者執行調用的本地方法----執行引擎的行為由指令集定義
指令集:Java方法的字節碼流由Java虛擬機的指令序列構成。每條指令包含:一個單字節的操作碼(表示需要執行的操作),0或多個操作數(操作數向Java虛擬機提供執行操作碼的額外信息,使指令使用的值可能來自當前常量池中的項,當前幀的局部變量中的值或者當前操作數棧頂端的值)。
運行中的Java程序的每一個線程都是一個獨立的虛擬機執行引擎的實例。從線程生命周期的開始到結束,它要么在執行字節碼,要么在執行本地方法。
主要的執行技術有:解釋,及時編譯,自適應優化,芯片級直接執行。自適應優化吸取解釋和及時編譯的優點,采取兩種結合的方式。
自適應優化——開始對所有的代碼都采取解釋執行的方式并監視代碼的執行情況,然后對那些經常調用的方法啟動一個后臺線程,將其及時編譯為本地代碼進行調用,并進行仔細優化。當該方法不再頻繁的被調用,則取消編譯過的代碼,將其歸為解釋執行。
方法區——線程共享
當虛擬機裝載某個類型時,它使用類裝載器定位載入相應的.class文件到虛擬機中,接著虛擬機提取其中的類型信息,并將這些信息存儲到方法區。當開發人員在程序中通過Class對象的getName(),isInterface()等方法來獲取信息時,這些數據都會來源于方法區,同時方法區也是全局共享的,在一定條件下它也會被GC掉(虛擬機允許通過用戶自定義的類裝載器來動態擴展Java程序,此時方法區也可以被垃圾回收器收集),當方法區需要使用的內存超過最大允許時,會拋出OutOfMemory。
方法區存放內容如下:
已經被虛擬機所加載的類信息(類名稱,類類型(接口還是類),修飾符,類的直接超類名稱),
類中的靜態(類)變量,
運行常量池runtime constant pool (類中定義的final類型的常量,一個有序集合,包括直接常量(string,integer,floating 常量)和對其他類型,字段,方法的符號引用)——class文件除了有類信息外,還有一項是常量池(constant pool table),常量池用于存放編譯期生成的各種字面量和符號的引用,這部分內容將在類加載后進入方法區的運行時常量池中存儲。——運行時常量池相對于class文件常量池的一個重要特征是具備動態性:即除了可以存儲class文件常量池中的內容外,運行期間也可能將新的常量放入到池中,比如String類的intern()方法,,參考: 深入理解java虛擬機(三):String.intern()-字符串常量池
類中的方法信息(方法名,返回類型,參數數量和類型,修飾符),
字段信息(字段名,類型,修飾符),
指向ClassLoader類的引用(每個類型被裝載時,虛擬機必須跟蹤確定它是由系統類裝載器還是由用戶自定義裝載器裝載的),
指向Class類的引用(對于每隔一個被裝載的類型,虛擬機都為其相應的創建了一個java.lang.Class類實例)
----------
堆(heap)——線程共享
堆是JVM用來存儲對象實例以及數組值(數組在Java虛擬機中是一個真正的對象)的區域,可以認為Java中所有通過new創建的對象的內存都在堆中分配,堆中的對象所占的內存是需要等待GC進行回收的(JVM沒有釋放內存的指令,需要將釋放內存的任務交給垃圾收集器處理)。堆是JVM中所有線程共享的。
Java棧(Java stack) ——線程私有,生命周期與線程相同
每當啟動一個線程時,Java虛擬機就會為他分配一個Java棧。Java棧由許多棧幀組成,一個棧幀包含一個對應的Java方法調用的狀態。當線程調用一個Java方法時,虛擬機壓入一個新的棧幀到Java棧中,當該方法返回時,這個棧幀就會從Java棧中彈出。
棧幀:由局部變量區,操作數棧和幀數據區組成。當虛擬機調用一個Java方法時,它從對應類的類型信息中得到此方法的局部變量區和操作數棧大小,并根據此來分配幀的內存,然后壓入棧中。
局部變量區
局部變量區被組織為以字長為單位,從0開始計數的數組。字節碼指令通過從0開始的索引使用其中的數據。類型為int, float, reference和returnAddress的值在數組中占據一項,而類型為byte, short和char的值在存入數組前都被轉換為int值,也占據一項。但類型為long和double的值在數組中卻占據連續的兩項。如下圖:
操作數棧
與局部變量區一樣,操作數棧被組織成一個以字長為單位的數組,其通過標準的棧操作訪問。
幀數據區
Java棧幀需要幀數據區來支持常量池的解析——(每當虛擬機要執行一個需要操作常量池數據的指令時,就會通過幀數據區中指向常量池的指針來訪問常量池),正常方法返回——(幀數據區還要幫助虛擬機處理Java方法的正常結束或異常中止。如果通過return正常結束,虛擬機必須恢復發起調用的方法的棧幀,包括設置程序計數器指向發起調用方法的下一個指令;如果方法有返回值,虛擬機需要將它壓入到發起調用的方法的操作數棧)以及異常派發機制——(為了處理Java方法執行期間的異常退出情況,幀數據區還保存一個對此方法異常表的引用)。
PC寄存器(程序計數器 program counter)——線程私有,生命周期與線程相同
每一個線程都有它自己的PC寄存器,也是在該線程啟動時創建的。PC寄存器的內容總是指向下一條即將被執行的指令的地址,這里的地址可以是一個本地地址,也可以是在方法區中相對于該方法的起始指令的偏移量。如果線程執行Java方法,則PC寄存器保存的是下一條執行指令的地址。若線程執行的是本地方法,那么此時PC寄存器的值是"undefined"。
本地方法棧(native method stack)——線程私有,生命周期與線程相同
當線程調用Java方法時,虛擬機會創建一個新的棧幀并將其壓入到對應線程的Java棧。當線程調用的是本地方法時,虛擬機會保持Java棧不變,不再向Java棧中壓入新的棧幀,虛擬機只是簡單的動態連接并調用指定的本地方法。JVM垃圾回收(Generational Collecting)依賴于本地方法的實現,如某個JVM實現的本地方法接口使用C連接模型,則本地方法棧就是C棧,可以說某線程在調用本地方法時,就進入了一個不受JVM限制的領域,也就是JVM可以利用本地方法來動態擴展本身。
GC通過確定對象是否被活動對象引用來確定是否收集回收該對象。
觸發GC的條件Java內存不足時,GC被調用。當應用程序在運行時在運行過程中創建新的對象,若此時內存空間不足,就會強制調用GC線程。若GC一次扔不能滿足內存分配,會再次調用GC,若仍無法滿足要求,則會報錯"out of memory",Java應用停止。
GC在優先級最低的線程中運行,一般在應用程序空閑即沒有應用線程在運行的時候被調用。
兩個重要的方法System.gc()
使用System.gc()直接請求Java的垃圾回收。
finalize()
在jvm垃圾回收之前調用的方法。之所有要使用finalize(),是存在著垃圾回收器不能處理的情況:1) 在本地方法native method調用中,可能由于在分配內存的時候可能采用了類似C語言的做法,而非Java的new做法,比如本地方法調用了C++的malloc()來分配內存而沒有調用free()來釋放掉內存,這時候就可能造成內存泄露。這時就可以在finalize()方法中用本地方法調用free()來釋放掉這些特殊的內存空間。 2)又或者是打開了文件資源,這些資源不屬于垃圾回收器能回收的范圍,則需要在finalize()中調用對應的本地方法來回收文件資源。
減少GC開銷的措施不要顯式調用System.gc()。此函數建議JVM進行主GC,雖然只是建議而非一定,但很多情況下它會觸發主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數。大大的影響系統性能。
減少對臨時對象的使用。臨時對象在方法結束后會成為垃圾,很快創建很快結束,增加了GC開銷。
對象不用時最好置為NULL。NULL對象一般都會作為垃圾處理,把不用的對象置為NULL有利于GC判定垃圾效率。
能用基本類型int long,就不要new Integet,new Long對象。基本類型占用內存資源相應較小。
少用靜態對象變量。靜態對象變量屬于全局變量,不會被GC回收,他們會一直占用內存空間。
字符串修改用StringBuffer,StringBuilder,不用String。
避免大量集中new新對象。
對象在jvm堆區的狀態可觸及狀態:程序中還有變量引用,那么此對象為可觸及狀態。
可復活狀態:當程序中已經沒有變量引用這個對象,那么此對象由可觸及狀態轉為可復活狀態。CG線程將在一定的時間準備調用此對象的finalize方法(finalize方法繼承或重寫子Object),finalize方法內的代碼有可能將對象轉為可觸及狀態,否則對象轉化為不可觸及狀態。
不可觸及狀態:只有當對象處于不可觸及狀態時,GC線程才能回收此對象的內存。
常用垃圾收集器標記-清除收集器 mark-sweep
復制收集器 copying
標記-壓縮收集器 mark-compact
分代收集器 generational
垃圾收集算法 tracing算法基于tracing算法的垃圾回收也稱為標記和清除(mark-sweep)垃圾收集器。
標記-清除算法分為兩個階段:標記階段和清除階段。標記階段的任務是標記出所有的需要被回收的對象,清除階段就是回收被標記的對象所占用的內存空間。
此算法缺陷就是很容易產生內存碎片,在產生大量的內存碎片后就可能無法對新的大對象所需要的內存空間進行分配。
為了解決標記-清除算法的缺陷,coping算法是將內存按內存容量劃分為大小相等的兩塊,每次對新對象的內存分配只使用其中的一塊。當這一塊內存用完的時候,就將在這塊內存上還存活下來的對象復制到另以空閑塊內存上面,然后把那塊已經使用的內存空間全部清理。
此算法雖然不會產生內存碎片,但是每次只能使用一半的內存空間,降低了內存實際使用率。而且當存活的對象還很多的時候,需要將它們全部復制到另一塊內存上,這也使效率降低。
為了解決compying算法的缺陷而充分的利用內存空間,提出了mark-compact 算法,即標記-壓縮。該算法的標記階段和mark-sweep一樣將所有需要被回收的對象進行標記。但是標記完成后,它不是直接清理可回收對象,而是將存活的對象都向一端移動,然后清理掉存活對象邊界以外的內存空間。這樣即不會產生內碎片,也充分利用了內存空間。
generation算法分代收集算法是目前大部分jvm的垃圾回收器所采用的算法。它的核心思想是根據對象存活的生命周期來劃分不同的區域,對每個區域進行不用的垃圾回收策略。一般情況將堆區分為老年代(tenured generation)和新生代(young generation) ,老年代的特點是每次垃圾回收時只會有少量的對象需要被回收,而新生代的特點是每次垃圾回收時都會有大量的對象需要被回收掉。
目前大部分垃圾收集器對于新生代都采取Copying算法,因為新生代中每次垃圾回收都要回收大部分對象,也就是說需要復制的操作次數較少,但是實際中并不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象復制到另一塊Survivor空間中,然后清理掉Eden和剛才使用過的Survivor空間。
而由于老年代的特點是每次回收都只回收少量對象,一般使用的是Mark-Compact算法。
新生代:新創建的對象都存放在這里。因為大多數對象很快變得不可達,所以大多數對象在年輕代中創建,然后消失。當對象從這塊內存區域消失時,我們說發生了一次“minor GC”。
老年代:沒有變得不可達,存活下來的年輕代對象被復制到這里。這塊內存區域一般大于年輕代。因為它更大的規模,GC發生的次數比在年輕代的少。對象從老年代消失時,我們說 "major GC"("full GC")。
永久代(permanent generation)也稱為“方法區(method area)”,他存儲class對象和字符串常量。所以這塊內存區域絕對不是永久的存放從老年代存活下來的對象的。在這塊內存中有可能發生垃圾回收。發生在這里垃圾回收也被稱為major GC。
參考來自
深入理解Java虛擬機體系結構
什么是JVM?
面試準備之JVM的組成、垃圾回收機制
深入理解Java虛擬機
深入理解JVM--JVM垃圾回收機制
分享一波阿里云代金券快速上云
from usthe.com
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68974.html
摘要:運行時數據區域的學習,是學習以及機制的基礎,也是深入理解對象創建及運行過程的前提。了解內存區域劃分,是學習概念的前提。 Java 運行時數據區域的學習,是學習 jvm 以及 GC 機制的基礎,也是深入理解 java 對象創建及運行過程的前提。廢話不多說,直接進入正題: 一張圖總結 showImg(https://segmentfault.com/img/bVOMAn?w=685&h=5...
在社會化分工、軟件行業細分專業化的趨勢下,會真的參與到底層系統實現的人肯定是越來越少(比例上說)。真的會參與到JVM實現的人肯定是少數。 但如果您對JVM是如何實現的有興趣、充滿好奇,卻苦于沒有足夠系統的知識去深入,那么可以參考RednaxelaFX整理的這個書單。 showImg(http://segmentfault.com/img/bVbGzn); 本豆列的脈絡是: 1. JV...
摘要:此內存區域是唯一一個在虛擬機規范中沒有規定任何情況的區域。其中位長度的和類型的數據會占用個局部變量空間,其余數據類型只占用個。內存區域異常線程請求棧深度大于虛擬機允許的深度,將拋出。上限控制異常直接內存 showImg(https://segmentfault.com/img/bVbundc?w=800&h=559); 運行時數據區域 程序計數器 線程正在執行時,如果執行的是一個Jav...
摘要:上一篇文章講解了虛擬機中的內存布局,這里就稍作拓展,聊聊對象在虛擬機中的一些存儲細節吧。參考文檔深入理解虛擬機高級特效與最佳實現,第章周志明著系列筆記內存區域和機制明舞深入理解結構團長聯系作者 上一篇文章講解了 java 虛擬機中的內存布局,這里就稍作拓展,聊聊 java 對象在虛擬機中的一些存儲細節吧。 本文主要圍繞虛擬機中對象如何創建?對象內存都放些什么?如何訪問對象內存?這么三...
閱讀 3187·2021-11-23 09:51
閱讀 1524·2021-11-22 09:34
閱讀 2836·2021-10-27 14:15
閱讀 2265·2021-10-12 10:17
閱讀 1884·2021-10-12 10:12
閱讀 946·2021-09-27 14:00
閱讀 1996·2021-09-22 15:19
閱讀 1032·2019-08-30 10:51