后台开发人员面试内容——JVM虚拟机(四)
一、Java類加載機制
類從被加載到虛擬機內存中開始,到卸載出內存,它的整個生命周期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initiallization)、使用(Using)和卸載(Unloading)這7個階段。其中驗證、準備、解析3個部分統稱為連接(Linking),這七個階段的發生順序如下圖
1.加載Loading:
1)通過一個類的全限定名來獲取定義此類的二進制字節流。
2)將這個字節流所代表的靜態變量、類信息、常量等內容放入到方法區。
3)在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。
?
2.驗證Verification:
目的是確保class文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身安全,
驗證階段主要包括四個檢驗過程:文件格式驗證、元數據驗證、字節碼驗證和符號引用驗證
?
3.準備Preparation
靜態成員的初始化。準備階段是正式為類變量分配內存并設置其初始值的階段,這些變量所使用的內存都將在方法區中分配。關于這點,有兩個地方注意一下:
1)這時候進行內存分配的僅僅是類變量(被static修飾的變量),而不是實例變量,實例變量將會在對象實例化的時候隨著對象一起分配在Java堆中
2)這個階段賦初始值的變量指的是那些不被final修飾的static變量,比如"public static int value = 123;",value在準備階段過后是0而不是123,給value賦值為123的動作將在初始化階段才進行
?
4.解析Resolution
解析階段是虛擬機常量池內的符號引用替換為直接引用的過程。 Java在編譯階段,會將.java文件編譯成.class文件,在生成的.class文件中,static修飾的靜態變量就是我們常說的符號引用,但是在編譯階段該符號引用并不知道引用類的實際內存地址(虛擬機還沒運行)。直到解析階段,虛擬機加載了該類才能真正解析到具體的內存地址。
?
5.初始化Initialization
初始化階段做的事就是給static變量賦予用戶指定的值以及執行靜態代碼塊
?
二、雙親委派加載
1.類加載器介紹:
從Java開發人員的角度來看,大部分Java程序一般會使用到以下三種系統提供的類加載器:
1)啟動類加載器(Bootstrap ClassLoader):負責加載JAVA_HOME\lib目錄中并且能被虛擬機識別的類庫到JVM內存中,如果名稱不符合的類庫即使放在lib目錄中也不會被加載。該類加載器無法被Java程序直接引用。
2)擴展類加載器(Extension ClassLoader):該加載器主要是負責加載JAVA_HOME\lib\,該加載器可以被開發者直接使用。
3)應用程序類加載器(Application ClassLoader):該類加載器也稱為系統類加載器,它負責加載用戶類路徑(Classpath)上所指定的類庫,開發者可以直接使用該類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。?
4)自定義類加載器(必須繼承 ClassLoader)。?
這些類加載器之間的關系如下圖所示:
如上圖所示的類加載器之間的這種層次關系,就稱為類加載器的雙親委派模型(Parent Delegation Model)。該模型要求除了頂層的啟動類加載器外,其余的類加載器都應當有自己的父類加載器。子類加載器和父類加載器不是以繼承(Inheritance)的關系來實現,而是通過組合(Composition)關系來復用父加載器的代碼。
2.雙親委派模型的工作過程為:
如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的加載器都是如此,因此所有的類加載請求都會傳給頂層的啟動類加載器,只有當父加載器反饋自己無法完成該加載請求(該加載器的搜索范圍中沒有找到對應的類)時,子加載器才會嘗試自己去加載
3.雙親委派模型?的好處:
使用這種模型來組織類加載器之間的關系的好處是Java類隨著它的類加載器一起具備了一種帶有優先級的層次關系。例如java.lang.Object類,無論哪個類加載器去加載該類,最終都是由啟動類加載器進行加載,因此Object類在程序的各種類加載器環境中都是同一個類。否則的話,如果不使用該模型的話,如果用戶自定義一個java.lang.Object類且存放在classpath中,那么系統中將會出現多個Object類,應用程序也會變得很混亂。如果我們自定義一個rt.jar中已有類的同名Java類,會發現JVM可以正常編譯,但該類永遠無法被加載運行
?
三、JVM運行時數據區
1.堆(Heap)
1)被所有線程共享的一塊內存區域,在虛擬機啟動時創建;
2)用來存儲對象實例;
3)可以通過-Xmx和-Xms控制堆的大小
4) java堆是垃圾收集器管理的主要區域。java堆還可以細分為:新生代(New/Young)、舊生代/年老代(Old/Tenured)
?
2.方法區(Method Area)
1)被所有線程共享的一塊內存區域
2)用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據
?
3.虛擬機棧(VM Stack)
1)線程私有,生命周期與線程相同
2) 存儲方法的局部變量表(基本類型、對象引用)、操作數棧、動態鏈接、方法出口等信息
?
4.本地方法棧(Native?Method?Stack)
與虛擬機棧相似,主要為虛擬機使用到的Native方法服務,在HotSpot虛擬機中直接把本地方法棧與虛擬機棧二合一
?
5.程序計數器(Program?Counter?Register)
1)當前線程所執行的字節碼的行號指示器
2)當前線程私有
?
四、JVM常見配置
-Xss:每個線程的java虛擬機內存大小
1.堆設置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:設置年輕代大小
-XX:NewRatio=n:設置年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,年輕代占整個年輕代年老代和的1/4
-XX:SurvivorRatio=n:年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區占整個年輕代的1/5
-XX:MaxPermSize=n:設置持久代大小
2.收集器設置
-XX:+UseSerialGC:設置串行收集器
-XX:+UseParallelGC:設置并行收集器
-XX:+UseParalledlOldGC:設置并行年老代收集器
-XX:+UseConcMarkSweepGC:設置并發收集器
3.垃圾回收統計信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
4.并行收集器設置
-XX:ParallelGCThreads=n:設置并行收集器收集時使用的CPU數。并行收集線程數。
-XX:MaxGCPauseMillis=n:設置并行收集最大暫停時間
-XX:GCTimeRatio=n:設置垃圾回收時間占程序運行時間的百分比。公式為1/(1+n)
5.并發收集器設置
-XX:+CMSIncrementalMode:設置為增量模式。適用于單CPU情況。
-XX:ParallelGCThreads=n:設置并發收集器年輕代收集方式為并行收集時,使用的CPU數。并行收集線程數
?
五、JVM垃圾回收機制
1.Java中的四種引用類型
1)強引用. 這里的o就是一個強引用,也是我們用得最多的引用,在實例化類的時候經常會用到。遇到這類引用,GC(垃圾回收器)是絕對不會回收它的。當遇到內存不足的情況,JVM會拋出OOM異常。所以,在不使用這類對象的時候要注意釋放它,以便讓系統回收
Object o = new Object();
2)軟引用. 這里的s就是一個軟引用,它是用來描述一些有用但非必需的對象。如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存
?
SoftReference<String> s = new SoftReference<>(new String("Hello"));
System.out.println(s.get());
3)弱引用。只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存
WeakReference<String> w = new WeakReference<>(new String("Hello"));
System.out.println(w.get());
System.gc();
System.out.println(w.get());
4)虛引用。與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用,一樣,在任何時候都可能被垃圾回收器回收。 它唯一的作用就是就是用于追蹤,讓我們能夠在這個對象被回收的時候收到一個通知。
ReferenceQueue<String> queue = new ReferenceQueue<>();
PhantomReference<String> pr = new PhantomReference<>(new String("Hello"), queue);
System.out.println(pr.get());
?
2.如何判斷對象需要被回收
1)引用計數法。 在對象內部會有一個引用計數器,一旦某個地方引用它時,計數器就加1。 計數器表示的是對象的人氣指數,也就是有多少程序引用了這個對象(被引用數)。下圖是引用計數法中的對象。
、
2)可達性分析法。 所謂可達性分析就是通過一系列被稱為“GC Roots”的點作為起始點,從這些節點開始向下搜索,搜索的路徑稱為引用鏈,當一個對象到GC Roots不可達的時候,則證明此對象是可回收的
?
3.垃圾回收算法
回收機制——隔代收集法
新生代:
復制法:新生代活躍對象多,先將內存分為兩個部分,From區和To區,兩部分大小相等。對象分配時,只會在From區進行分配。復制算法可以分兩步,第一步為類似標記清除算法的標記,在From區中,找出所有活動的對象。區別在于第二步。復制算法會把這些活動的對象,復制到To區中,再將原有的From區全部清空,并交換兩部分內存的職責,即一次GC后,原有的From區會成為To區,To區相反
優點:效率高 、高速分配、不會發生碎片化;GC后的內存空間是連續的。
缺點:堆使用效率低下,把堆二等分, 真正存放新對象的內存區域會變少,只有一半堆能夠存放對象
老年代(永久代):
標記-整理法:分為標記和整理兩個階段: 標記所有從根節點開始的遍歷到的存活對象,讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存
標記—清除法:分為標記和清除兩個階段: 標記所有從根節點開始的遍歷到的存活對象,在標記完成后,清除所有未被標記的對象
優點:有效利用堆,能夠在整個堆中進行操作
缺點:壓縮花費時間,清楚和壓縮都會搜索堆,浪費時間,沒有壓縮的話會產生碎片化問題,對象存儲不連續。
4.垃圾收集器
?
| 名稱 | 工作區域 | 單線程/多線程 | 垃圾收集算法 | 描述 |
| Serial收集器 | 新生代 | 單線程 | 復制算法 | 進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束 |
| ParNew收集器 (Serial收集器的多線程版本) | 新生代 | 多線程 | 復制算法 | 并行:指多條垃圾收集線程并行工作,但此時用戶線程仍然處于等待狀態; 并發:指用戶線程與垃圾收集線程同時執行? |
| Parallel Scavenge收集器 | 新生代 | 多線程 | 復制算法 | 吞吐量優先收集器 目標:控制吞吐量 |
| Serial Old收集器 | 老年代 | 單線程 | 標記整理算法 | 主要意義也是在于給Client模式下的虛擬機使用 |
| Parallel Old收集器 | 老年代 | 多線程 | 標記整理算法 | Parallel Old?是Parallel Scavenge收集器的老年代版本 1.6之后,“吞吐量優先“收集器組合:Parallel Scavenge + Parallel Old |
| CMS收集器 | 老年代 | 多線程 | 標記-清除算法 | 目標:最短回收停頓時間 優點:并發收集,低停頓 缺點:CPU資源敏感;無法處理浮動垃圾;內存碎片問題 |
5.內存分配策略
Minor GC 和 Full GC
- Minor GC:回收新生代,因為新生代對象存活時間很短,因此 Minor GC 會頻繁執行,執行的速度一般也會比較快。
- Full GC:回收老年代和新生代,老年代對象其存活時間長,因此 Full GC 很少執行,執行速度會比 Minor GC 慢很多
1)對象優先在Eden上分配,當Eden上內存不夠時,發起Minor?GC
2)大對象和長期存活的對象進入老年代
3)? 長期存活的對象將進入老年代
3)調用System.gc()時,建議虛擬機執行Full?GC,老年代空間不足也會執行Full?GC
?
總結
以上是生活随笔為你收集整理的后台开发人员面试内容——JVM虚拟机(四)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot中使用Redis数据
- 下一篇: 后台开发人员面试内容——计算机网络(五)