筆記---JVM相關

這篇文章的問題和答案是我在準備面試複習時通過查詢資料和代碼測試自己整理的,分享給大家,如有不正確的地方,歡迎大家批評指正。
1、內存模型、Java內存模型和JVM內存結構、GC機制和原理;
    內存模型:
        爲了保證共享內存的正確性(可見性、有序性、原子性),內存模型定義了共享內存系統中多線程程序讀寫操作行爲的規範。通過這些規則來規範對內存的讀寫操作,從而保證指令執行的正確性。保證了併發場景下的一致性、原子性和有序性。
        內存模型解決併發問題主要採用兩種方式:限制處理器優化使用內存屏障
    java內存模型:
        Java內存模型(Java Memory Model ,JMM)就是一種符合內存模型規範的,屏蔽了各種硬件和操作系統的訪問差異的,保證了Java程序在各種平臺下對內存的訪問都能保證效果一致的機制及規範。
         JMM是一種規範,目的是解決由於多線程通過共享內存進行通信時,存在的本地內存數據不一致、編譯器會對代碼指令重排序、處理器會對代碼亂序執行等帶來的問題。
 
 
    JVM 內存結構(描述的是線程運行所設計的內存空間):
        JVM內存結構主要有五部分:堆內存、方法區、java虛擬機棧、程序計數器、本地方法棧
        堆內存:
            堆是是jvm虛擬機的主要內存區域。所有線程共享,在jvm虛擬機啓動時創建。用來存儲幾乎所有的對象實例。Java 堆的是可以擴展的,通過 調整jvm的-Xmx 和 -Xms 控制堆大小。
         方法區:
              方法區被所有線程共享,存儲加載的類信息、常量、靜態變量等。方法區還包含運行常量池,常量池中主要存儲編譯生成的常量和引用。
          java虛擬機棧:
              java虛擬機棧是每個線程私有的,當線程被創建時棧也會被創建。 棧幀中存儲了方法的局部變量表,操作數棧,動態連接,和方法返回地址等信息。並且局部變量表所需空間在編譯期就確定並分配完成,方法運行期間不會改變。(補充:局部變量表中存放了原生類型(java的8中基本數據類型)、引用類型和returnAddress類型 )
          程序計數器:
              程序計數器是當前線程所執行的字節碼的行號指示器,它會指出下一條將要執行的指令的地址,字節碼解釋器就是通過改變計數器的值來選取程序接下來執行的操作。每個線程都會有一個獨立的程序計數器,並且程序計數器是唯一不會出現OOM( OutOfMemoryError,內存溢出 )的內存區域。跟GC也有關,引用計數。
          本地方法棧:
              本地方法棧與java虛擬機棧作用類似,區別在於java虛擬機棧執行java方法,本地方法棧執行Native方法。(補充:java無法訪問操作系統底層,所以使用Native方法拓展功能,這也是java運行速度要比傳統的c/c++慢一些的原因。當程序想使用底層主機平臺的某個特性但是不能通過java訪問或使用一個並不是java語言編寫的庫或加快程序的性能,將部分對時間要求快速的代碼作爲本地方法實現時就需要用到native去聲明java方法。)
   
        GC機制和原理:
            GC也就是java JVM的垃圾回收機制,根據一定的回收策略自動回收內存,永不停歇,進而保證JVM的內存空間夠用,防止內存泄漏和溢出等問題。
            GC回收算法:
           · 標記-清除:分爲“標記”和“清除”兩個階段,首先標記所有需要回收的對象,在標記完成時統一回收,是最基礎的回收算法。缺點是效率不高並且清除之後會產生大量不連續的內存碎片,當程序後續運行中要給對象分配較大內存時會提前觸發另一次垃圾收集。
            ·複製算法:將內存按照容量劃分成大小相等的兩塊,每次只是用其中一塊,當這塊內存使用完了,把存活的對象複製到另一塊內存中,然後把這塊內存一次性清理掉。該算法實現簡單,運行高效。只是將原本的內存縮小了一半並且持續複製生存期長的對象也會降低效率。
            ·分代(分區)垃圾回收:Java堆分爲新生代和老年代,方法區作爲永久代。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,適用複製算法;而老年代中因爲對象存活率高、沒有額外空間對它進行分配擔保,就要使用標記-清除或標記-整理算法來進行回收。
            ·標記-壓縮:標記壓縮是根據標記清除算法提出的一種算法,相同的是標記過程一樣,不同的是標記完成之後,讓所有存活的對象移向一端,然後清理掉另一端的內存空間。
        補充:jdk1.8以後移除了永久代,由於永久代內存經常不夠用或發生內存泄漏,所以jvm開發者在jdk1.8中移除了永久代,轉而用元空間代替。所謂元空間,本質跟永久代類似,最大的區別是元空間不存在jvm虛擬機中,而是使用的本地內存。元空間的大小僅受本地內存限制,但可以通過以下參數來指定元空間的大小:
        -XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。
        -XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。
        -XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少爲分配空間所導致的垃圾收集次數;
        -XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少爲釋放空間所導致的垃圾收集次數。
 
2、GC分哪兩種,Minor GC 和Full GC有什麼區別?什麼時候會觸發Full GC?分別採用什麼算法?
    GC分爲Minor GC 和Full GC兩種。
    Minor GC 和Full GC有什麼區別:
        Minor GC是針對新生代(Eden區、 To Survivor區、 From Survivor區)的垃圾回收,等新生代的Eden區空間耗光的時候就會觸發一次Minor GC,而存活下來的對象會被送到新生代的Survivor區(倖存者區,分爲兩個to&from)。當發生Minor GC時,Eden區和form指向的Survivor區存活的對象會通過標記-複製算法複製到to指向的Survivor區,然後進行垃圾回收,再然後交換form和to的指針,保證to指向的Survivor區是空的;
        補充:jvm虛擬機會記錄Survivor區的對象一共被來回複製了幾次,如果一個對象被複制了15( 虛擬機參數 -XX:+MaxTenuringThreshold)次,該對象就會晉升到老年代,如果Survivor區的空間佔用達到50%( 虛擬機參數: -XX:TargetSurvivorRatio),複製次數較高的對象也會晉升到老年代。當在 Minor GC過程中,to指向的內存空間不足以保存Eden區和from指向的Survivor區存活的對象,那麼多餘的對象會晉升到老年代,這就是過早提升。弊端就是會導致老年代的“短命”對象增多,很可能在 Minor GC之後緊接着進行 Full GC,導致提升失敗。
        Full GC是對整個堆內存進行垃圾回收,包括新生代、老年代、永久代等。Full GC本身不會先進行Minor GC,可以通過配置 -XX:+ScavengeBeforeFullGC(非CMS回收算法)、CMSScavengeBeforeRemark(CMS回收算法)讓進行Full GC之前先進行一次Minor GC,進而提高老年代的GC速度。Full GC是單線程回收整個堆。
    觸發Full GC的條件:
             System.gc()方法的調用默認觸發 full GC;
             老年代內存空間不足;
             方法區內存空間不足;
 
    分別採用什麼算法:
        Minor GC採用的是標記-複製算法;Full GC是對整個堆內存回收,分代回收,不同的代使用的算法不同。
3、JVM裏的有幾種classloader,爲什麼會有多種?
    類加載器是通過類名獲取二進制字節流
    啓動類加載器 bootstrap class loader
        啓動類加載器主要是加載JVM自身所需要的類;負責加載/lib路徑下的核心類庫或者-Xbootclasspath參數指定的路徑下的jar包;出於安全方面的考慮,該加載器只加載java、javax、sun等開頭的包下的類。
        用C++語言實現,是虛擬機的一部分,沒有父類。
 
    拓展類加載器 extensions class loader
         負責加載JAVA_HOME/lib/ext目錄下或系統變量-Djava.ext.dir指定位路徑中的類庫。
        java語言實現,父類加載器是null。
 
    應用程序類加載器 application class loader
        負責加載系統類路徑java -classpath或-D java.class.path 指定路徑下的類庫,一般是程序中默認的類加載器,通過ClassLoader.getSystemClassLoader()方法可以獲取到該類加載器。
        java語言實現,父類加載器是ExtClassLoader。
 
    自定義類加載器 java.lang.class loader
         通過繼承java.lang.ClassLoader類的方式。
         父類加載器爲AppClassLoader。
    爲什麼要有多種類加載器:
        1.爲了區分同名的類;
        2.更方便的加強類的能力。類加載器可以在load class時對class進行重寫、覆蓋,其實就可以對類進行功能性的增強或修改。
 
4、什麼是雙親委派機制?介紹一些運作過程,雙親委派模型的好處;
    什麼是雙親委派機制:
        如果一個類加載器需要加載類,他會先把這個請求委託給父類的加載器去執行,如果父類加載器還有父類加載器,那麼這個請求再向上委託,直到到達最終頂層的引導(啓動)類加載器,如果父類加載器可以完成類加載任務,就成功返回,如果父類加載器不能加載,子類加載器纔會自己去加載,這就是雙親委派機制。注意,此處的父子關係並非java類繼承關係。
    
    雙親委派模型的好處:
        首先這種層級關係可以避免類的重複加載,其次是安全,防止核心API被隨意篡改。越基礎的類交給越高級的加載器加載。
 
5、什麼情況下我們需要破壞雙親委派模型?
        
涉及到SPI的加載動作。
    補充:什麼是SPI?SPI全稱Service Provider Interface,是jdk內置的服務提供發現機制(動態替換髮現的機制)。
用戶追求程序的動態性,OSGI環境下類加載器由雙親委派模型的樹狀結構發展爲網狀結構。
 
附加:常見的JVM調優方法有哪些?可以具體到調整哪個參數,調成什麼值?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章