摘要:如果沒有,那必須先執行相應的類加載過程。分配內存在類加載檢查通過后,接下來虛擬機將為新生對象分配內存。程序計數器主要有兩個作用字節碼解釋器通過改變程序計數器來依次讀取指令,從而實現代碼的流程控制,如順序執行選擇循環異常處理。
目錄介紹
01.Java對象的創建過程
1.0 看下創建類加載過程
1.1 對象的創建
1.2 對象的內存布局
02.Java內存區域
2.0 運行時數據區域
2.1 程序計數器
2.2 虛擬機棧
2.3 本地方法棧
2.4 Java堆
2.5 方法區
2.6 運行時常量池
2.7 直接內存
03.Java對象的訪問定位方式
3.1 句柄
3.2 直接指針
04.Java對象銷毀分析
4.1 JVM內存分配與回收
4.2 判斷對象是否死亡
4.3 不可達的對象并非“非死不可”
4.4 如何判斷一個常量是廢棄常量
4.5 如何判斷一個類是無用的類
4.6 GC回收算法詳解
05.String類和常量池
5.1 String對象的兩種創建方式
5.2 String類型的常量池
好消息博客筆記大匯總【16年3月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉載請注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質變!
問題思考答疑說一下創建一個對象,類的加載過程。類信息,常量,變量,方法分別放到內存中哪里?
對于運行時數據區域,哪些是私有的,哪些是共享的,為什么要這樣設計?
程序計數器會出現OOM嗎?它的生命周期是怎么樣的?
本地方法棧和Java虛擬機棧有什么區別?本地方法棧在什么情況下會造成OOM?
java堆主要是做什么作用的?
什么是類的加載檢查,主要檢查什么,如何檢查呢?
Java對象訪問定位方式有哪些?主要有什么區別?為什么說使用指針效率更高?
String類可以new嗎?直接new和賦值的內容有什么區別,分別放在內存中什么地方?
如何判斷對象是否死亡(兩種方法)。如果有不同方法,那么之間有什么區別?
簡單的介紹一下強引用、軟引用、弱引用、虛引用(虛引用與軟引用和弱引用的區別、使用軟引用能帶來的好處)。
如何判斷一個常量是廢棄常量,如何判斷一個類是無用的類?
垃圾收集有哪些算法,各自的特點?常見的垃圾回收器有那些?
HotSpot為什么要分為新生代和老年代?
介紹一下CMS,G1收集器。Minor Gc和Full GC 有什么不同呢?
01.Java對象的創建過程 1.1 看下創建類加載過程
Person p = new Person()請寫一下類的加載過程?
1).因為new用到了Person.class,所以會先找到Person.class文件,并加載到內存中; 2).執行該類中的static代碼塊,如果有的話,給Person.class類進行初始化; 3).在堆內存中開辟空間分配內存地址; 4).在堆內存中建立對象的特有屬性,并進行默認初始化; 5).對屬性進行顯示初始化; 6).對對象進行構造代碼塊初始化; 7).對對象進行與之對應的構造函數進行初始化; 8).將內存地址付給棧內存中的p變量1.1 對象的創建
Java對象的創建過程,我建議最好是能默寫出來,并且要掌握每一步在做什么。
1.類加載檢查
2.分配內存
3.初始化零值
4.設置對象頭
5.執行init方法
①類加載檢查:
虛擬機遇到一條 new 指令時,首先將去檢查這個指令的參數是否能在常量池中定位到這個類的符號引用,并且檢查這個符號引用代表的類是否已被加載過、解析和初始化過。如果沒有,那必須先執行相應的類加載過程。
②分配內存:
在類加載檢查通過后,接下來虛擬機將為新生對象分配內存。對象所需的內存大小在類加載完成后便可確定,為對象分配空間的任務等同于把一塊確定大小的內存從 Java 堆中劃分出來。分配方式有 “指針碰撞” 和 “空閑列表” 兩種,選擇那種分配方式由 Java 堆是否規整決定,而Java堆是否規整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
內存分配的兩種方式:
選擇以上兩種方式中的哪一種,取決于 Java 堆內存是否規整。而 Java 堆內存是否規整,取決于 GC 收集器的算法是"標記-清除",還是"標記-整理"("標記-壓縮"),值得注意的是,復制算法內存也是規整的
內存分配并發問題
在創建對象的時候有一個很重要的問題,就是線程安全,因為在實際開發過程中,創建對象是很頻繁的事情,作為虛擬機來說,必須要保證線程是安全的,通常來講,虛擬機采用兩種方式來保證線程安全:
CAS+失敗重試:
CAS 是樂觀鎖的一種實現方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。虛擬機采用 CAS 配上失敗重試的方式保證更新操作的原子性。
TLAB:
為每一個線程預先在Eden區分配一塊兒內存,JVM在給線程中的對象分配內存時,首先在TLAB分配,當對象大于TLAB中的剩余內存或TLAB的內存已用盡時,再采用上述的CAS進行內存分配
③初始化零值:
內存分配完成后,虛擬機需要將分配到的內存空間都初始化為零值(不包括對象頭),這一步操作保證了對象的實例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數據類型所對應的零值。
④設置對象頭:
初始化零值完成之后,虛擬機要對對象進行必要的設置,例如這個對象是那個類的實例、如何才能找到類的元數據信息、對象的哈希嗎、對象的 GC 分代年齡等信息。
這些信息存放在對象頭中。 另外,根據虛擬機當前運行狀態的不同,如是否啟用偏向鎖等,對象頭會有不同的設置方式。
⑤執行 init 方法:
在上面工作都完成之后,從虛擬機的視角來看,一個新的對象已經產生了,但從 Java 程序的視角來看,對象創建才剛開始,
在 Hotspot 虛擬機中,對象在內存中的布局可以分為3快區域:對象頭、實例數據和對齊填充。
Hotspot虛擬機的對象頭包括兩部分信息,第一部分用于存儲對象自身的自身運行時數據(哈希嗎、GC分代年齡、鎖狀態標志等等),另一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是那個類的實例。
實例數據部分是對象真正存儲的有效信息,也是在程序中所定義的各種類型的字段內容。
對齊填充部分不是必然存在的,也沒有什么特別的含義,僅僅起占位作用。
因為Hotspot虛擬機的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說就是對象的大小必須是8字節的整數倍。而對象頭部分正好是8字節的倍數(1倍或2倍),因此,當對象實例數據部分沒有對齊時,就需要通過對齊填充來補全。
02.Java內存區域 2.0 運行時數據區域Java 虛擬機在執行 Java 程序的過程中會把它管理的內存劃分成若干個不同的數據區域。
這些組成部分一些事線程私有的,其他的則是線程共享的。
線程私有的:
程序計數器
虛擬機棧
本地方法棧
線程共享的:
Java堆
方法區
運行時常量池
直接內存
2.1 程序計數器程序計數器:是一個數據結構,用于保存當前正常執行的程序的內存地址。Java虛擬機的多線程就是通過線程輪流切換并分配處理器時間來實現的,為了線程切換后能恢復到正確的位置,每條線程都需要一個獨立的程序計數器,互不影響,該區域為“線程私有”。
程序計數器是一塊較小的內存空間,可以看作是當前線程所執行的字節碼的行號指示器。字節碼解釋器工作時通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等功能都需要依賴這個計數器來完。
程序計數器主要有兩個作用:
1.字節碼解釋器通過改變程序計數器來依次讀取指令,從而實現代碼的流程控制,如:順序執行、選擇、循環、異常處理。
2.在多線程的情況下,程序計數器用于記錄當前線程執行的位置,從而當線程被切換回來的時候能夠知道該線程上次運行到哪兒。
注意:程序計數器是唯一一個不會出現OutOfMemoryError的內存區域,它的生命周期隨著線程的創建而創建,隨著線程的結束而死亡。
2.2 虛擬機棧Java虛擬機棧:線程私有的,與線程生命周期相同,用于存儲局部變量表,操作棧,方法返回值。局部變量表放著基本數據類型,還有對象的引用。
Java 內存可以粗糙的區分為堆內存(Heap)和棧內存(Stack),其中棧就是現在說的虛擬機棧,或者說是虛擬機棧中局部變量表部分。(實際上,Java虛擬機棧是由一個個棧幀組成,而每個棧幀中都擁有:局部變量表、操作數棧、動態鏈接、方法出口信息。)
局部變量表主要存放了編譯器可知的各種數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不同于對象本身,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置)。
Java 虛擬機棧會出現兩種異常:StackOverFlowError 和 OutOfMemoryError。
StackOverFlowError: 若Java虛擬機棧的內存大小不允許動態擴展,那么當線程請求棧的深度超過當前Java虛擬機棧的最大深度的時候,就拋出StackOverFlowError異常。
OutOfMemoryError: 若Java虛擬機棧的內存大小允許動態擴展,且當線程請求棧時內存用完了,無法再動態擴展了,此時拋出OutOfMemoryError異常。
Java 虛擬機棧也是線程私有的,每個線程都有各自的Java虛擬機棧,而且隨著線程的創建而創建,隨著線程的死亡而死亡。
2.3 本地方法棧本地方法棧:跟虛擬機棧很像, 虛擬機棧為虛擬機執行 Java 方法 (也就是字節碼)服務,而本地方法棧則為虛擬機使用到的 Native 方法服務。 在 HotSpot 虛擬機中和 Java 虛擬機棧合二為一。
本地方法被執行的時候,在本地方法棧也會創建一個棧幀,用于存放該本地方法的局部變量表、操作數棧、動態鏈接、出口信息。
方法執行完畢后相應的棧幀也會出棧并釋放內存空間,也會出現 StackOverFlowError 和 OutOfMemoryError 兩種異常。
2.4 Java堆Java堆:所有線程共享的一塊內存區域,此內存區域的唯一目的就是存放對象實例,對象實例幾乎都在這分配內存。在虛擬機啟動時創建。
Java 堆是垃圾收集器管理的主要區域,因此也被稱作GC堆(Garbage Collected Heap).從垃圾回收的角度,由于現在收集器基本都采用分代垃圾收集算法,所以Java堆還可以細分為:新生代和老年代:在細致一點有:Eden空間、From Survivor、To Survivor空間等。進一步劃分的目的是更好地回收內存,或者更快地分配內存。
在 JDK 1.8中移除整個永久代,取而代之的是一個叫元空間(Metaspace)的區域(永久代使用的是JVM的堆內存空間,而元空間使用的是物理內存,直接受到本機的物理內存限制)。
2.5 方法區
方法區:各個線程共享的區域,儲存虛擬機加載的類信息,常量,靜態變量,編譯后的代碼。
雖然Java虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做 Non-Heap(非堆),目的應該是與 Java 堆區分開來。
相對而言,垃圾收集行為在這個區域是比較少出現的,但并非數據進入方法區后就“永久存在”了。如何理解這句話?
2.6 運行時常量池
運行時常量池:代表運行時每個class文件中的常量表。包括幾種常量:編譯時的數字常量、方法或者域的引用。
。Class 文件中包括類的版本、字段、方法、接口等描述信息
既然運行時常量池時方法區的一部分,自然受到方法區內存的限制,當常量池無法再申請到內存時會拋出 OutOfMemoryError 異常。JDK1.7及之后版本的 JVM已經將運行時常量池從方法區中移了出來,在Java堆(Heap)中開辟了一塊區域存放運行時常量池。
2.7 直接內存直接內存并不是虛擬機運行時數據區的一部分,也不是虛擬機規范中定義的內存區域,但是這部分內存也被頻繁地使用。而且也可能導致OutOfMemoryError異常出現。
JDK1.4中新加入的 NIO(New Input/Output) 類,引入了一種基于通道(Channel) 與緩存區(Buffer) 的 I/O 方式,它可以直接使用Native函數庫直接分配堆外內存,然后通過一個存儲在 Java 堆中的 DirectByteBuffer 對象作為這塊內存的引用進行操作。這樣就能在一些場景中顯著提高性能,因為避免了在 Java 堆和 Native 堆之間來回復制數據。
本機直接內存的分配不會收到 Java 堆的限制,但是,既然是內存就會受到本機總內存大小以及處理器尋址空間的限制。
03.Java對象的訪問定位方式建立對象就是為了使用對象,我們的Java程序通過棧上的 reference 數據來操作堆上的具體對象。對象的訪問方式有虛擬機實現而定
目前主流的訪問方式有
①使用句柄
②直接指針
這兩種對象訪問方式各有優勢。
使用句柄來訪問的最大好處是 reference 中存儲的是穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針,而 reference 本身不需要修改。
使用直接指針訪問方式最大的好處就是速度快,它節省了一次指針定位的時間開銷。
3.1 句柄如果使用句柄的話,那么Java堆中將會劃分出一塊內存來作為句柄池,reference 中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息;
3.2 直接指針如果使用直接指針訪問,那么 Java 堆對像的布局中就必須考慮如何放置訪問類型數據的相關信息,而reference 中存儲的直接就是對象的地址。
04.Java對象銷毀分析思考一下,更多可以參考我的博客:https://github.com/yangchong2...
4.1 JVM內存分配與回收Java 的自動內存管理主要是針對對象內存的回收和對象內存的分配。同時,Java 自動內存管理最核心的功能是 堆 內存中對象的分配與回收。
JDK1.8之前的堆內存示意圖:
從上圖可以看出堆內存的分為新生代、老年代和永久代。新生代又被進一步分為:Eden 區+Survior1 區+Survior2 區。值得注意的是,在JDK1.8中移除整個永久代,取而代之的是一個叫元空間(Metaspace)的區域(永久代使用的是JVM的堆內存空間,而元空間使用的是物理內存,直接受到本機的物理內存限制)。
分代回收算法
目前主流的垃圾收集器都會采用分代回收算法,因此需要將堆內存分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集算法。
大多數情況下,對象在新生代中 eden 區分配。當 eden 區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC。
Minor Gc和Full GC 有什么不同呢?
新生代GC(Minor GC):指發生新生代的的垃圾收集動作,Minor GC非常頻繁,回收速度一般也比較快。
老年代GC(Major GC/Full GC):指發生在老年代的GC,出現了Major GC經常會伴隨至少一次的Minor GC(并非絕對),Major GC的速度一般會比Minor GC的慢10倍以上。
4.2 判斷對象是否死亡
堆中幾乎放著所有的對象實例,對堆垃圾回收前的第一步就是要判斷那些對象已經死亡(即不能再被任何途徑使用的對象)。
給對象中添加一個引用計數器,每當有一個地方引用它,計數器就加1;當引用失效,計數器就減1;任何時候計數器為0的對象就是不可能再被使用的。
這個方法實現簡單,效率高,但是目前主流的虛擬機中并沒有選擇這個算法來管理內存,其最主要的原因是它很難解決對象之間相互循環引用的問題。
所謂對象之間的相互引用問題,如下面代碼所示:除了對象objA和objB相互引用著對方之外,這兩個對象之間再無任何引用。但是他們因為互相引用對方,導致它們的引用計數器都不為0,于是引用計數算法無法通知 GC 回收器回收他們。
public class Test { Object instance = null; public static void main(String[] args) { Test objA = new Test(); Test objB = new Test(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; } }
這個算法的基本思想就是通過一系列的稱為 “GC Roots” 的對象作為起點,從這些節點開始向下搜索,節點所走過的路徑稱為引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連的話,則證明此對象是不可用的。
無論是通過引用計數法判斷對象引用數量,還是通過可達性分析法判斷對象的引用鏈是否可達,判定對象的存活都與“引用”有關。
JDK1.2以后,Java對引用的概念進行了擴充,將引用分為強引用、軟引用、弱引用、虛引用四種(引用強度逐漸減弱)
關于四種引用以及源代碼分析,可以看我的這篇文章:https://blog.csdn.net/m0_3770...
4.3 不可達的對象并非“非死不可”即使在可達性分析法中不可達的對象,也并非是“非死不可”的,這時候它們暫時處于“緩刑階段”,要真正宣告一個對象死亡,至少要經歷兩次標記過程;可達性分析法中不可達的對象被第一次標記并且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize 方法。當對象沒有覆蓋 finalize 方法,或 finalize 方法已經被虛擬機調用過時,虛擬機將這兩種情況視為沒有必要執行。
被判定為需要執行的對象將會被放在一個隊列中進行第二次標記,除非這個對象與引用鏈上的任何一個對象建立關聯,否則就會被真的回收。
4.4 如何判斷一個常量是廢棄常量 4.5 如何判斷一個類是無用的類
方法區主要回收的是無用的類,那么如何判斷一個類是無用的類的呢?要判定一個類是否是“無用的類”的條件則相對苛刻許多。類需要同時滿足下面3個條件才能算是 “無用的類” :
該類所有的實例都已經被回收,也就是 Java 堆中不存在該類的任何實例。
加載該類的 ClassLoader 已經被回收。
該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
虛擬機可以對滿足上述3個條件的無用類進行回收,這里說的僅僅是“可以”,而并不是和對象一樣不使用了就會必然被回收。
4.6 GC回收算法詳解這個可以看我的另外一篇文章:https://blog.csdn.net/m0_3770...
05.String類和常量池 5.1 String對象的兩種創建方式
1 String 對象的兩種創建方式:
String str1 = "abcd"; String str2 = new String("abcd"); System.out.println(str1==str2);//false
這兩種不同的創建方法是有差別的【記住:只要使用new方法,便需要創建新的對象】
第一種方式是在常量池中拿對象
第二種方式是直接在堆內存空間創建一個新的對象。
5.2 String類型的常量池
String 類型的常量池比較特殊。它的主要使用方法有兩種:
直接使用雙引號聲明出來的 String 對象會直接存儲在常量池中。
如果不是用雙引號聲明的 String 對象,可以使用 String 提供的 intern 方String.intern() 是一個 Native 方法,它的作用是:如果運行時常量池中已經包含一個等于此 String 對象內容的字符串,則返回常量池中該字符串的引用;如果沒有,則在常量池中創建與此 String 內容相同的字符串,并返回常量池中創建的字符串的引用。
String s1 = new String("yc"); String s2 = s1.intern(); String s3 = "yc"; System.out.println(s2);//yc System.out.println(s1 == s2);//false,因為一個是堆內存中的String對象一個是常量池中的String對象, System.out.println(s3 == s2);//true,因為兩個都是常量池中的String對關于其他內容介紹 01.關于博客匯總鏈接
1.技術博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關于我的博客我的個人站點:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網上的日子:http://www.jcodecraeer.com/me...
郵箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71994.html
摘要:作用負責將加載到中審查每個類由誰加載父優先的等級加載機制將字節碼重新解析成統一要求的對象格式類結構分析為了更好的理解類的加載機制,我們來深入研究一下和他的方法。就算兩個是同一份字節碼,如果被兩個不同的實例所加載,也會認為它們是兩個不同。 申明:本文首發于 詳細深入分析 ClassLoader 工作機制 ,如有轉載,注明原出處即可,謝謝配合。 什么是 ClassLoader ? 大家...
摘要:如果需要支持類的動態加載或需要對編譯后的字節碼文件進行解密操作等,就需要與類加載器打交道了。雙親委派模型,雙親委派模型,約定類加載器的加載機制。任何之類的字節碼都無法調用方法,因為該方法只能在類加載的過程中由調用。 jvm系列 垃圾回收基礎 JVM的編譯策略 GC的三大基礎算法 GC的三大高級算法 GC策略的評價指標 JVM信息查看 GC通用日志解讀 jvm的card table數據...
摘要:前面提到,對于數組類來說,它并沒有對應的字節流,而是由虛擬機直接生成的。對于其他的類來說,虛擬機則需要借助類加載器來完成查找字節流的過程。驗證階段的目的,在于確保被加載類能夠滿足虛擬機的約束條件。 Java 虛擬機將字節流轉化為 Java 類的過程。這個過程可分為加載、鏈接以及初始化 三大步驟。 加載是指查找字節流,并且據此創建類的過程。加載需要借助類加載器,在 Java 虛擬機中,類...
摘要:學習能更深入的理解這門語言,能理解語言底層的執行過程,深入到字節碼層次。 目錄 ? 前言 程序的運行 1.JVM類加載機制 ①一般在什么情況下會去加載一個類?也就是說,什么時候.class字節碼文件中加載這個類到JVM內存里來? ②驗證、準備、初始化 ③初始化 2.類加載器和雙親委派機制 ...
閱讀 3685·2021-10-09 09:44
閱讀 3376·2021-09-22 15:29
閱讀 3134·2019-08-30 15:54
閱讀 3022·2019-08-29 16:19
閱讀 2149·2019-08-29 12:50
閱讀 597·2019-08-26 14:04
閱讀 1704·2019-08-23 18:39
閱讀 1352·2019-08-23 17:59