<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 1. 字節碼 ## 1.1 Java 為什么跨平臺? 先將java文件編譯成**字節碼(.class)文件**, 在所有的平臺上生成的字節碼文件都是相同的。 再**使用Java虛擬機運行字節碼文件**。因為**不同操作系統有對應版本的jvm**, 這使得同一個java代碼文件可以在不同的平臺上運行。 ## 1.2 JVM 的生命周期? ### 虛擬機的啟動 Java 虛擬機的啟動是通過引導類加載器(bootstrap class loader) 場景一個初始類(initial class) 來完成的 , 這個類是由虛擬機的具體實現指定的. ### 虛擬機的退出有如下幾種情況 * 某線程調用Runtime類或System類的exit方法, 或Runtime類的halt方法,并且Java安全管理器也允許這次exit或halt操作。 * 程序正常執行結束 * 程序在執行過程中遇到了異常或錯誤而異常終止 * 由于操作系統出現錯誤而導致Java虛擬機進程終止 ## 1.3 JVM 的組成? (虛擬機的體系結構?) 1. 類加載器 2. 運行時數據區 3. 執行引擎 ![](https://img.kancloud.cn/88/2e/882eb2e4b529aa785962f9e2888253f9_992x849.png) 這個架構可以分成三層看: * 最上層:javac編譯器將編譯好的字節碼class文件,通過java 類裝載器執行機制,把對象或class文件存放在 jvm劃分內存區域。 * 中間層:稱為Runtime Data Area,主要是在Java代碼運行時用于存放數據的,從左至右為方法區(永久代、元數據區)、堆(共享,GC回收對象區域)、棧、程序計數器、寄存器、本地方法棧(私有)。 * 最下層:解釋器、JIT(just in time)編譯器和 GC(Garbage Collection,垃圾回收器) ## 1.4 什么是字節碼的指令?(虛擬機指令?) 字節碼是一種二進制的類文件,其指令由一個字節長度的、代表著某種特定操作含義的操作碼(opcode)以及跟隨其后的零至多個代表此操作所需參數的操作數(operand)所構成。虛擬機中許多指令并不包含操作數,只有一個操作碼。 ## 1.5 字節碼(.class) 文件結構 * 魔數 (用來標識文件類型的) * class文件版本 (jdk的版本信息) * 常量池計數器 和 常量池表數據 * 訪問標識(或標志) (public final 等等信息 ) * 類索引、父類索引、接口索引集合 (繼承那個類, 實現哪些接口) * 字段表集合 * 方法表集合 * 屬性表集合 ## 1.6 基本類型為什么不存到堆中而是存到棧中? 棧空間相對小, 運算速度更快, 基本類型占用空間小更適合放到棧中 # 2. 類的加載 ## 2.1 類加載過程?(生命周期?加載 .class文件的原理機制?) 1. 加載 (Loading) 根據類的路徑找到相對應的calss文件,然后導入。 2. 鏈接(Linking) 1. 驗證(Verification) 檢查待加載的class文件的正確性 2. 準備(Preparation) 給類中的靜態變量分配存儲空間 3. 解析(Resolution) 將符號引用轉換成直接引用 4. 初始化(Initialization) 對靜態變量和靜態代碼塊執行初始化工作 ![](https://img.kancloud.cn/65/57/65574732c4c9c821221aadb53d5f355a_735x241.png) ![](https://img.kancloud.cn/b3/60/b36006f64d5cac0ecb3eb3c12edea5a5_598x476.png) ### 一、加載: * 通過一個類的全限定名獲取定義此類的二進制字節流 * 將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構 * 在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口 注意:數組類是如何創建加載的呢? ### 二、鏈接: * 驗證(Verify): * 目的在于確保Class文件的字節流中包含信息符合當前虛擬機要求,保證被加載類的正確性, 不會危害虛擬機自身安全。 * 主要包括四種驗證,文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證。 * 準備(Prepare): * 為類變量分配內存并且設置該類變量的默認初始值,即零值。 * 這里不包含用final修飾的static,因為final在編譯的時候就會分配了,準備階段會顯式初始化; * 這里不會為實例變量分配初始化,類變量會分配在方法區中,而實例變量是會隨著對象一起分配到Java堆中。 * 解析(Resolve): * 將常量池內的符號引用轉換為直接引用的過程。 * 事實上,解析操作往往會伴隨著JVM在執行完初始化之后再執行。 符號引用就是一組符號來描述所引用的目標。符號引用的字面量形式明確定義在《java虛擬機規范》的Class文件格式中。 在解析階段,jvm根據字符串的內容找到內存區域中相應的地址,然后把符號引用替換成直接指向目標的指針、句柄、偏移量等,這些直接指向目標的指針、句柄、偏移量就被成為直接引用。 * 解析動作主要針對類或接口、字段、類方法、接口方法、方法類型等。對應常量池中的CONSTANT\_Class\_info、CONSTANT\_Fieldref\_info、CONSTANT\_Methodref\_info等。 ### 三、初始化: * 初始化階段就是執行類構造器方法()的過程。 * 此方法不需定義,是javac編譯器自動收集類中的所有類變量的賦值動作和靜態代碼塊中的語句合并而來。 * 構造器方法中指令按語句在源文件中出現的順序執行。 * ()不同于類的構造器。(關聯:構造器是虛擬機視角下的()) * 若該類具有父類,JVM會保證子類的()執行前,父類的()已經執行完畢。 * 虛擬機必須保證一個類的()方法在多線程下被同步加鎖。 ## 2.2 什么是類加載器?(簡述下類加載器?) 類加載器就是用來加載字節碼文件(.class)的類,其實質是把類文件從硬盤讀取到內存中! ## 2.3 類加載器有哪些? (類加載器的分類?) * 引導類加載器:Bootstrap ClassLoader,用來加載Java核心庫,比如rt.jar、java/javax/sun開頭的類, 并且負責加載 自定義類加載器。使用C++編寫 * 自定義類加載器(其他類加載器):擴展類加載器、系統類加載器、開發人員自定義的。主要是Java語言編寫,頂級父類是 ClassLoader 也有分成 * 啟動類加載器:BootstrapClassLoader * 擴展類加載器:ExtentionClassLoader * 應用類加載器:AppClassLoader (也叫做“系統類加載器”)開發人員自定義的也在這里 ![](https://img.kancloud.cn/96/fe/96fe784c7554501e0472fbf43a504878_658x469.png) ## 2.4 父類加載器和子類加載器的關系? 兩者不是子父類的那樣的繼承關系,而是包含關系 ## 2.5 為什么要自定義類加載器 1. 隔離加載類 : 如tomcat 就內部有自定義的類加載器用來隔離同一個服務器上的不同應用程序 2. 修改類加載方式 : 類的加載模型并非強制,除Bootstrap外,其他的加載并非一定要引入,或者根據實際情況在某個時間點進行按需進行動態加載 3. 擴展加載源:比如從數據庫、網絡 4. 防止源碼泄露:比如對字節碼加密,在加載時去解密 ## 2.6 雙親委派機制? 雙親委派機制是指當一個類加載器收到一個類加載請求時,該類加載器首先會把請求委派給父類加載器。每個類加載器都是如此,一層層往上找,只有在父類加載器在自己的搜索范圍內找不到指定類時,子類加載器才會嘗試自己去加載。如果找不到拋出ClassNotFoundException異常。 ### 工作原理 首先判斷被加載的類是否已經加載過,如果是則結束。 1. 如果一個類加載器收到了類加載請求,它并不會自己去加載,而是把這個請求委托給父類的加載器去執行; 2. 如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達頂層的啟動類加載器; 3. 如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成此加載任務,再一層層子加載器去加載,最終找不到,就會拋出一個異常:ClassNotFoundException。。 **先是子類一層層往上委托,再父類一層層往下開始加載(最上層的父類能加載就加載,不能就讓其子類加載)。** ## 2.7 雙親委派機制的作用?(好處?) * **保護程序安全,防止核心API被隨意篡改**。借助父親委托機制,Java核心類庫中的類的加載工作都是由啟動類加載器來統一完成的,從而確保了Java應用所使用的都是同一個版本的Java核心類庫,他們之間是互相兼容的。 * **避免類的重復加載。**通過委托去向上加載,如果已經有加載過,就不需要再加載了。 ## 2.8 雙親委派機制的缺點 子能使用父, 父不能使用子 父類加載器不能使用子類加載器的應用實例 # 3. 運行時內存 ***** ## 3.1 說一下 JVM 內存分區?(JVM 內存模型?)每個區放什么?(每個區作用?) 1. **方法區** 線程**共享**,它用于存儲已被虛擬機加載的**類的信息(類的名稱、字段信息、方法信息)、常量池、靜態變量、即時編譯器編譯后的代碼緩存**等,1.7 和 1.8 把**常量池、靜態變量都放到堆**中了。 2. **堆 **線程**共享**,主要存儲引用類型的數據 (對象實例) 3. **虛擬機棧** 線程**私有**,存的是**基本數據類型和堆中對象的引用**。內存結構是一個個棧幀,一個棧幀對應一個方法。棧幀主要包含 * 局部變量表(Local Variables) * 操作數棧(Operand stack) (或表達式棧) * 動態鏈接(Dynamic Linking) (或指向運行時常量池的方法引用) * 方法返回地址(Return Address) (或方法正常退出或者異常退出的定義) 4. **程序計數器** 線程**私有**,任何時間一個線程都只有一個方法在執行,程序計數器會存儲當前線程正在執行的Java方法的JVM字節碼指令地址; 5. **本地方法棧** 線程**私有**,用于管理本地方法的調用。 ![](https://img.kancloud.cn/a8/40/a840de7a52dfcb14c2359785f7aabeb5_665x343.png) ![](https://img.kancloud.cn/90/22/9022bd2a37fd0dd86a133c0306908e0b_343x526.png) ## 3.2 堆和棧的區別? 1. 棧空間很小,運行速度快,主要存放基本類型數據和存儲在堆中的引用類型數據的調用地址、部分結果、并參與方法的調用和返回 2. 堆空間大, 運行較慢,主要存放引用類型的數據 3. 棧不存在GC,堆存在GC 4. 棧管運行,堆管存儲 ## 3.3 棧溢出? * 遞歸的時候,壓棧遠遠超過出棧就可能會內存溢出 * 棧中局部變量表數據大導致棧幀過多就可能也出現內存溢出 ## 3.4 堆的內存結構? * 新生代 * 伊甸園(Eden) * Survivor 0 (From區) /s?r?va?v?r/ * Survivor 1 (To區) * 老年代 ## 3.5 為什么有新生代和老年代?(為什么分代?)新生代、老年代的比例如何? 不同對象的生命周期不同,大量對象是生命周期短的臨時對象,把生命周期短的放到新生代,生命周期長的放到老年代,以優化GC性能。 默認新生代和老年代的比例:1:2 \-XX:NewRatio=2 表示新生代占1,老年代占2,新生代占整個堆的1/3 ## 3.6 為什么新生代分為 Eden 和 Survivor?Eden 和 Survivor 的比例分配? 伊甸園 (Eden)主要是用來創建對象 幸存者(Survivor)是因為老年代的對象一般都是生命周期長,老年代內存也大,GC回收的效率低,在伊甸園和老年代增加篩選過濾,防止不必要的對象送往老年代。 默認新生代的Eden、S0、S1的比例是 8:1:1 \-XX:SurvivorRatio=8 JDK9及以上版本中默認使用的G1 垃圾收集器,會自動調整大小,這種設置就無效了 ## 3.7 為什么有倆個 survivor 區? 解決內存空間碎片化,新生代GC時是使用的復制算法,使用兩個 survivor 實現復制,以使伊甸園創建的對象和其中一個survivor的對象復制到空的survivor 區。 復制算法保證了S1中來自S0和Eden兩部分的存活對象占用連續 的內存空間,避免了碎片化的發生。 當新生代的 Survivor 分區為 2 個的時候,不論是空間利用率還是程序運行的效率都是最優的,所以這也是為什么 Survivor 分區是 2 個的原因了。 ## 3.8 什么時候對象會進入老年代? 新生代有GC最大次數, 超過這個次數,就會放到老年代了,默認 15 -XX: MaxTenuringThreshold=15 大對象直接進入老年代:設置了-XX:PretenureSizeThreshold這個參數,那么如果你要創建的對象大于這個參數的值,比如分配一個超大的字節數組,此時就直接把這個大對象放入到老年代,不會經過新生代。 長期存活的對象直接進入老年代 ## 3.9 為什么幸存者區15次進入老年代,原理是啥?對象如何晉升到老年代? 對象頭里的Mark Word(標記字),對象的分代年齡占4位,也就是0000,最大值為1111也就是最大為15。 ![](https://img.kancloud.cn/05/92/05929707ea08417803772b6292decf86_629x83.png) ## 3.10 初始堆大小和最大堆大小一樣,問這樣有什么好處? 為了能夠在java垃圾回收機制清理完堆區后不需要重新分隔計算堆區的大小,從而提高性能 ## 3.11 JVM中最大堆大小有沒有限制? 系統的可用物理內存限制,默認最大值為物理內存的1/4 ## 3.12 什么是空間分配擔保策略? HandlePromotionFailure 已經沒用了 JDK 6 Update 24之后的規則變為老年代的連續空間大于 新生代對象總大小或者歷次晉升的平均大小就會進行Minor GC, 否則將進行FullGC. /f?l/ adj. 滿的,滿是……的; ## 3.13 新生代和老年代的內存回收策略? * 對象優先在堆的 Eden 區分配 * 大對象直接進入老年代 * 長期存活的對象將直接進入老年代 ## 3.14 對象內存分配過程 以對象都先分配到 伊甸園 區舉例 Minor GC 或者 YGC 是新生代的GC /?ma?n?r/ n. (Minor)(美)邁納(人名) 1. 對象先在伊甸園區創建完成,當伊甸園內存不足時,觸發GC,將可回收的回收,剩下的放到空的幸存者區(Survivor)并記錄年齡為1 表示已經經過一次GC。Survivor 共有倆個區 2. 再繼續創建對象時,伊甸園內存不足再次觸發GC,將可回收的回收,剩下的放到空的幸存者區(Survivor)并記錄年齡為1,同時上一步中的Survivor 也 進行GC,存活的對象年齡增1放到這一個Survivor里 每次GC 伊甸園區和其中一個幸存者區都會清空 3. 上面兩步這樣反復循環,當有對象在GC時,年齡超過了設置得上限就會放入到老年代 ## 3.15 內存分配原則: 針對不同年齡段的對象分配原則如下所示: * 優先分配到Eder * 大對象直接分配到老年代,盡量避免程序中出現過多的大對象 * 長期存活的對象分配到老年代 * 動態對象年齡判斷,如果Survivor區中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象可以直接進入老年代,無須等到設置得上限( MaxTenuringThreshold) 要求的年齡。 * 空間分配擔保 -XX: HandlePromotionFailure ## 新生代的垃圾回收(Minor GC)什么時候觸發? * 新生代內存不足會自動觸發 * 老年代GC 會先執行一次新生代的GC * 全堆GC(Full GC )也會觸發新生代的GC 1. 對象先在伊甸園區創建完成,當伊甸園內存不足時,觸發GC,將可回收的回收,剩下的放到空的幸存者區(Survivor)并記錄年齡為1 表示已經經過一次GC。Survivor 共有倆個區 2. 再繼續創建對象時,伊甸園內存不足再次觸發GC,將可回收的回收,剩下的放到空的幸存者區(Survivor)并記錄年齡為1,同時上一步中的Survivor 也 進行GC,存活的對象年齡增1放到這一個Survivor里 每次GC 伊甸園區和其中一個幸存者區都會清空 3. 上面兩步這樣反復循環,當有對象在GC時,年齡超過了設置得上限就會放入到老年代 ## 老年代的垃圾回收(Major GC)什么時候觸發?自動觸發的閾值是多少? 老年代空間不足 ## 什么時候發生 Full GC ?Full GC 的過程? 清理整個堆空間—包括年輕代、老年代、元空間 * 老年代空間不足,這個很簡單,就是字面上的不足,例如:大對象不停的直接進入老年代,最終造成空間不足。 * 方法區空間不足。 * 空間分配擔保策略會觸發Full GC , 老年代的連續空間小于新生代對象總大小或者歷次晉升的平均大小就會進行整堆的GC ( Full GC) * 調用System.gc ()時,系統建議執行Full Gc,但是不必然執行 ## 描述JVM 堆的一次完整的GC流程? ## 什么是 TLAB?為什么有TLAB? JVM為每個線程分配了一個私有緩存區域,它包含在Eden空間內。 多線程同時分配內存時,使用TLAB可以避免一系列的非線程安全問題,同時還能夠提升內存分配的吞吐量,因此我們可以將這種內存分配方式稱之為快速分配策略。 ## 方法區的字符串常量池(StringTable) 為什么要移到堆中? 字符串也是經常創建,在方法區時,由于方法區GC頻率太低效果也不好,字符串被大量創建而沒有有效的釋放掉,浪費內存影響性能。放到堆中能及時回收釋放內存。 ## JVM的永久代中會發生垃圾回收么? 方法區的垃圾收集主要回收兩部分內容:常量池中廢棄的常量和不再使用的類型。 ## 幾種主要的JVM參數? 設置棧的大小:-Xss1024k 設置棧的大小為 1024k(默認也是這么大), 棧過大會導致系統可以用于創建線程的數量變少。 新生代GC最大次數:-XX: MaxTenuringThreshold=15 默認 15 堆的起始內存 -Xms 1G 堆的最大內存 -Xmx 1G ## 方法區jdk版本間的區別 JDK8 以前, 把方法區的叫做永久代。JDK8 開始叫做元空間。 元空間的本質和永久代類似,都是對JVM規范中方法區的實現。不過元空間與永久代最大的區別在于:元空間不在虛擬機設置的內存中,而是使用本地內存。JDK7 開始, 永久代就開始在修改,其中的 字符串常量池,靜態變量都轉移到了堆中 ## 在Java中,什么是是棧的起始點,同是也是程序的起始點? main方法 ## Java中的參數傳遞時傳值呢?還是傳引用? 基本數據類型及其封裝類,String傳的是值。 其它的引用數據類型(對象+數組)傳遞的是引用。 ## Java中有沒有指針的概念? java中不說指針,說的是引用。引用指向堆中的對象實例。引用也是占內存的。 ## 一個空Object對象的占多大空間? 16字節(byte)(8字節引用 + 8字節對象(64位虛擬機的對象頭是64bit, 包括哈希碼,垃圾回收分代年齡,鎖標記位,類信息引用)) # 4. 對象內存布局 ## 4.1 創建對象的幾種方式? 1. 用new關鍵字創建對象 F f = new F(); 2. 使用反射機制創建對象 使用Class類里的newInstance()方法,權限必須是public,調用的是無參構造方法,使用java.lang.reflect.Constructor類里的newInstance方法,調用的是有參構造方法,無權限要求。 3. 通過object類的clone方法 不調用任何構造器,當前類需要實現Cloneable接口,實現clone(),默認淺拷貝。 4. 使用反序列化 5. 一些第三方庫可以使用asm字節碼技術動態生產 Constructor 對象 /k?n?str?kt?r/ n. 構造器;構造方法;構造函數 ## 4.2 創建對象的步驟?(new 對象的流程?) 1. 創建對象對應的類是否加載、鏈接、初始化了。遇到new 指令,檢查這個指令的參數是否在元空間(Metaspace) 中的常量池中存在對應符號引用,并且檢查符號引用對應的類是否加載初始化完成。即判斷類元信息是否存在 * 1. 如果沒有,那么在雙親委派模式下,使用當前的類加載器以 ClassLoader + 包名 + 類名為Key 去查找對應的 .class 文件 * 2. 如果沒有找到文件,則拋出 ClassNotFoundException 異常 * 3. 如果找到,則進行類加載流程并生成對應的 Class 類對象 * 2. 為對象分配內存。 計算對象占用的空間大小,在堆中劃分一塊內存給新對象 * 1. 指針碰撞。 內存規整時,已經使用的在一邊,未使用的在另一邊,中間是一個指針,作為分界點。這時分配一塊內存的操作就是將指針向未使用的一邊移動與對象大小相同的距離,這就是指針碰撞。 * 2. 空閑列表。內存不規整時,也就是有內存碎片時,已經使用的和未被使用的混雜在一起沒法使用指針碰撞需要用到空閑列表。jvm維護一個列表,記錄哪些是未被使用的。在分配的時候,需要從列表中找出一塊足夠大的內存塊劃分給對象,并更新列表的記錄。這就是空閑列表。 3. 處理并發安全問題。 * 1. CAS ( Compare And |Swap ) 失敗重試、區域加鎖:保證指針更新操作的原子性; * 2. TLAB把內存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊內存,稱為本地線程分配緩沖區 4. 初始化分配到的空間。 內存分配結束,虛擬機將分配到的內存空間都初始化為默認值(不包括對象頭)。 比如 int 類型賦值為 0 5 設置對象的對象頭。 將對象的所屬類(即類的元數據信息)、對象的HashCode和對象的GC信息、鎖信息等數據存儲在對象的對象頭中。 6. 執行init方法進行初始化。初始化成員變量,執行實例化代碼塊,調用類的構造方法,并把堆內對象的首地址賦值給引用變量 ## 4.3 對象的內存布局? 對象分為 對象頭(Header)、實例數據(Instance Data)、對齊填充(Padding) ## 4.4 對象頭的內容? 1\. 運行時元數據:普通對象-》 * 哈希值(對象在堆中的引用地址,toString 顯示的那個 @xxx )、 * GC分代年齡 * 鎖狀態標志,在同步中判斷該對象是否是鎖 * 線程持有的鎖 * 線程偏向ID * 偏向時間戳 數組還會有長度,普通對象的長度在對象元數據中 2. 類型指針:連接類在方法區中類元數據的引用 3. 實例數據 它是對象真正存儲的有效信息,包括程序代碼中定義的各種類型的字段、所有父級(父類,父類的父類......)的實例數據(父類私有的也會在這里只是不能訪問) 相同寬度的字段總是被分配在一起,父類中定義的變量會出現在子類之前(因為父類的加載是優先于子類加載的) ## 4.5 對象的訪問定位? 使用直接指針訪問(Hotspot的方式) 棧中存放指向堆中的對象引用,而對象中又通過類型指針連接類在方法區中元數據 # 5. 執行引擎 ## 5.1 執行引擎的作用? 執行引擎是Java虛擬機核心的組成部分之一。 執行引擎的核心功能就是將字節碼指令解釋/編譯為對應平臺上的本地機器指令以實現Java程序的運行。 # 6. 垃圾回收 ***** ## 6.1 GC是什么? 在運行程序中沒有任何指針指向的對象,這個對象就是需要被回收的垃圾。 ## 6.2 為什么要有GC? 核心就是內存不足的問題 如果不及時對內存中的垃圾進行清理,那么垃圾會一直占用著內存直到程序結束,這會導致內存越來越少,甚至內存溢出。 ## 6.3 GC回收的是哪部分的垃圾(回收的重點區域)? GC只存在于堆和方法區,其中堆是垃圾回收的核心。頻繁的對新生代回收,較少對老年代回收,極少進行全堆或方法區的回收。 ## 6.4 用什么方法判斷對象是否死亡? (如何判斷一個對象是否存活?) 根據對象是否被引用來判斷。當一個對象已經不再被任何的存活對象繼續引用時,就可以宣判為已經死亡 Java使用 可達性分析算法 來判斷 ## 6.5 可達性分析算法? 1. 可達性分析算法是以根對象集合(GC Roots)為起始點,按照從上至下的方式搜索被根對象集合所連接的目標對象是否可達。 2. 使用可達性分析算法后,內存中的存活對象都會被根對象集合直接或間接連接著,搜索所走過的路徑稱為引用鏈(Rererence Chain)。 3. 如果目標對象沒有任何引用鏈相連,則是不可達的,就意味著該對象已經死亡,可以標記為垃圾對象。 4. 在可達性分析算法中,只有能夠被根對象集合直接或者間接連接的對象才是存活對象。 優點:實現簡單,執行高效,有效的解決循環引用的問題,降低內存泄漏的風險。 為保證一致性可達性算法在判斷內存是否可回收時需要對其他線程做暫停處理(Stop The World )( stw) ## 6.6 GC Roots有哪些? 主要包含的幾類 * 虛擬機樹中引用的對象,比如:各個線程被調用的方法中使用到的參數、局部變量等。 * 類靜態屬性引用的對象,比如:Java類的引用類型靜態變量 * 方法區中常量引用的對象,比如:字符串常量池(string Table)里的引用 * 所有被同步鎖synchronized持有的對象 * Java虛擬機內部的引用。基本數據類型對應的Class對象,一些常駐的異常對象(如: NullPointerExceptionoutofMemoryError) ,系統類加載器。 * 反映java虛擬機內部情況的JMXBean,JVMTI中注冊的回調、本地代碼緩存等。 **由于Root采用棧方式存放變量和指針,所以如果一個指針,它保存了堆內存里面的對象,但是自己又不·存放在堆內存里面,那它就是一個Root 現在靜態變量也放到堆里面了,以前不在堆里** ## 6.7 引用計數算法 Java未使用 原理:對于一個對象A,只要有任何一個對象引用了A ,則A的引用計數器就加1,當引用失效時, 引用計數器就減1。只要對象A的引用計數器的值為0,即表示對象A不可能再被使用,可進行回收。 Java未使用,引用計數器有一個嚴重的問題,即無法處理循環引用的情況。這是一條致命缺陷,導致在Java的垃圾回收器中沒有使用這類算法。 ## 6.8 GC的三種收集方法:標記清除、標記整理(標記壓縮)、復制算法的原理與特點,分別用在什么地方,如果讓你優化收集方法,有什么思路? 1. 標記-清除算法,當堆中的有效內存空間(available memory)被耗盡的時候,就會停止整個程序(也被稱為stop the world),然后進行標記和清除。標記的是可達對象(非垃圾),清除的是未標記的對象 * 標記: 從引用根節點開始遍歷,標記所有被引用的對象。一般是在對象的Header中記錄為可達對象。 * 清除:對堆內存從頭到尾進行線性的遍歷,如果發現某個對象在其Header中沒有標記為可達對象,則將其回收。這里的清除只是把對象地址保存到空閑列表中,下次有新對象需要加載時,判斷垃圾的位置空間是否夠,如果夠,就存放。 * 優點:實現簡單,不移動對象,與保守式GC算法兼容 * 缺點:效率比較低:遞歸與全堆對象遍歷兩次。清理出來的空閑內存是不連續的,產生內存碎片。GC的時候,需要停止整個應用程序,導致用戶體驗差 stop the world STW ## 2 復制算法 將活著的內存空間分為兩塊,每次只使用其中一塊,在垃圾回收時將正在使用的內存中的存活對象復制到未被使用的內存塊中,之后清除正在使用的內存塊中的所有對象,交換兩個內存的角色,最后完成垃圾回收。適合存活對象少、垃圾對象多的場景。 * 優點:沒有標記和清除過程,實現簡單,在存活對象少、垃圾對象多的前提下的運行高效。復制過去以后保證空間的連續性,不會出現“內存碎片”問題 * 缺點:核心缺點是內存占用大。GC的時候,需要停止整個應用程序,導致用戶體驗差 stop the world STW ## 3. 標記-壓縮(標記整理)算法, * 標記:和標記清除算法一樣,從根節點開始標記所有被引用對象。一般是在對象的Header中記錄為可達對象。 * 壓縮(整理):將所有的存活對象壓縮移動到內存的一端,按順序排放。之后, 清理邊界外所有的空間。 * 優點:消除了標記/清除算法當中,內存區域分散的缺點(解決了內存碎片問題),我們需要給新對象分配內存時, JVM只需要持有一個內存的起始地址即可。消除了復制算法當中,內存減半的高額代價。 * 缺點: * 從效率上來說,標記-壓縮算法要低于復制算法 * 效率不高,不僅要標記所有在活對象,還要整理所有存活對象的引用地址。 * 對于老年代每次都有大量對象存適的區域來說,極為負重。 * 移動對象的同時,如果對象被其他對象引用,則還需要調整引用的地址。 * GC的時候,需要停止整個應用程序,導致用戶體驗差 ![](https://img.kancloud.cn/85/26/8526f90f5eebe65b34bcb32770a8df9d_1028x118.png) ## 6.9 JVM的垃圾回收為什么采用分代GC? 不同的對象的生命周期是不一樣的。因此,不同生命周期的對象,可以采取不同的收集方式,以便提高回收效率。一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點使用不同的回收算法,以提高垃圾回收的效率。 ## 6.10 分代垃圾回收過程? 新生代: 大部分可回收的對象都是在新生代, 這里的對象生命周期短、存適率低、回收頻繁,使用的是復制算法(因為很多是可回收對象,所以需要復制的對象少) 老年代: 區域較大,對象生命周期長、存活率高、回收不及新生代頻繁。這種情況存在大量存活率高的對象,復制算法明顯變得不合適。一般是由標記-清除或者是標記-清除與標記一整理的混合實現。 標記(Mark)階段的開銷與存活對象的數量成正比。 清除(Sweep)階段的開銷與所管理區域的大小成正相關。 壓縮(Compact)階段的開銷與存活對象的數據成正比。 ## 6.11 什么是內存泄漏和什么是內存溢出?區別是? ### 內存溢出: * Java 虛擬機的堆內存設置不合理,設置得過小 * 代碼中創建了大量在用的對象,長時間不能被垃圾收集器收集 ### 內存泄漏: 是垃圾但是沒有回收掉。 1. **靜態集合類**,生命周期和JVM程序一致。長生命周期的對象持有短生命周期對象的引用,盡管短生命周期的對象不再使用,但是因為長生命周期對象持有它的引用而導致不能被回收。 2. **單例模式**,因為單例的靜態特性,它的生命周期和JVM程序一致。所以如果單例對象如果持有外部對象的引用,那么這個外部對象也不會被回收,那么就會造成內存泄漏。 3. **內部類持有外部類**,如果一個外部類的實例對象的方法返回了一個內部類的實例對象。這個內部類對象被長期引用了,即使那個外部類實例對象不再被使用,但由于內部類持有,外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會造成內存泄漏。 4. **各種連接,如數據庫連接、網絡連接和IO連接等**。在對數據庫進行操作的過程中,首先需要建立與數據庫的連接,當不再使用時,需要調用close方法來釋放與數據庫的連接。只有連接被關閉后,垃圾回收器才會回收對應的對象。否則,如果在訪問數據庫的過程中,對Connection,Statement或ResultSet不顯性地關閉,將會造成大量的對象無法被回收,從而引起內存泄漏。 5. **變量不合理的作用域**,一個變量的定義的作用范圍大于其使用范圍,很有可能,會造成內存泄漏。另一方面,如果沒有及時地把對象設置為nul1,很有可能導致內存泄漏的發生。 6. **改變哈希值** 7. **緩存泄漏** * 內存泄漏的另一個常見來源是緩存,一旦你把對象引用放入到緩存中,他就很容易遺忘。比如:之前項目在一次上線的時候,應用啟動奇慢直到夯死,就是因為代碼中會加載一個表中的數據到緩存(內存)中,測試環境只有幾百條數據,但是生產環境有幾百萬的數據。 * 可以使用 WeakHashMap (弱引用),此種Map的特點是,當除了自身有對 ,key的引用外,此key沒有其他引用那么此map會自動丟棄此值。 8. **監聽器和回調** * 內存泄漏另一個常見來源是監聽器和其他回調,如果客戶端在你實現的API中注冊回調,卻沒有顯式的取消,那么就會積聚。 ## 6.12 什么是 STW(Stop the world)? 指的是GC事件發生過程中, 為解決一致性會產生應用程序的停頓。停頓產生時整個應用程序線程都會被暫停,沒有任何響應,有點像卡死的感覺,這個停頓稱為STW。 * 可達性分析算法中枚舉根節點(GC Roots) 會導致所有Java執行線程停頓。 * 被STW中斷的應用程序線程會在完成GC之后恢復,頻繁中斷會讓用戶感覺像是網速不快造成電影卡帶一樣,所以我們需要減少STW的發生。 * 所有的GC都有這個事件,和采用哪種GC無關,有的是以增加STW頻次來減少單次STW的時間。 * STW是JVM在后臺GC時自動發起和自動完成的。 ## 6.12 強引用、軟引用、弱引用、虛引用的區別? 強引用:不回收 軟引用:內存不足即回收區 一般用來實現內存敏感的緩存 比如 高速緩存 弱引用:發現即回收 一般用來實現可有可無的緩存 WeakHashMap 虛引用:對象回收跟蹤 ## 6.13 你開發中使用過weakHashMap嗎? 弱引用,此種Map的特點是,當除了自身有對 ,key的引用外,此key沒有其他引用那么此map會自動丟棄此值。 ## 6.14 System.gc()的作用? 提醒jvm的垃圾回收器執行Full GC,但是不確定是否馬上執行GC。 這個方法內部調用 Runtime .getRuntime ().gc(); 所以作用一樣。 ## 6.15 finalize() 方法詳解? 主要作用是在對象回收前做一些(Socket,文件加載)資源釋放操作。 finalize()是object的protected方法,子類可以覆蓋該方法以實現資源清理工作,GC在首次回收已不可達的對象之前調用對象的該方法。如果首次GC沒回收掉,下次也不會在調用。 ## 6.16 垃圾回收器有哪些?都有哪些算法來實現?項目中用的垃圾回收器是什么? 重點 CMS Parallel G1 ### 1. Serial 收集器 新生代收集器 Serial收集器是最基本的、發展歷史最悠久的收集器。新生代收集器,目前不怎么用 特點:單線程、簡單高效(與其他收集器的單線程相比),使用復制算法。對于限定單個CPU的環境來說,Serial收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程手機效率。收集器進行垃圾回收時,必須暫停其他所有的工作線程,直到它結束(Stop The World)。 應用場景:適用于Client模式下的虛擬機。單核服務器。 可以用 -XX:+UserSerialGC 來選擇 Serial 作為新生代收集器。 ![](https://img.kancloud.cn/b7/b5/b7b50300549ed326601687789a090df7_684x222.png) ### 2. Serial Old 收集器 老年代 Serial Old 是 Serial收集器的老年代版本,它同樣是一個單線程收集器,使用“標記-整理”(Mark-Compact)算法。 此收集器的主要意義也是在于給Client模式下的虛擬機使用。如果在Server模式下,它還有兩大用途: * 在JDK1.5 以及之前版本(Parallel Old誕生以前)中與Parallel Scavenge收集器搭配使用。 * 作為CMS收集器的后備預案,在并發收集發生Concurrent Mode Failure時使用。 ### 3. ParNew 收集器 新生代收集器 Serial 的多線程版本。其他和 Servial 區別不大 ![](https://img.kancloud.cn/f2/27/f2277203deec1e7c8f2d95814412e549_698x245.png) ### 4. Parallel Scavenge 收集器 新生代 吞吐量優先 jdk8 默認使用 Parallel Scavenge 也是一款用于新生代的多線程收集器,吞吐量優先 會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量。這種方式稱為GC自適應的調節策略。 另外值得注意的一點是,Parallel Scavenge收集器無法與CMS收集器配合使用,所以在JDK 1.6推出Parallel Old之前,如果新生代選擇Parallel Scavenge收集器,老年代只有Serial Old收集器能與之配合使用。 ![](https://img.kancloud.cn/3f/24/3f2409ceb5f6efab54e3ee4b8c9ccffa_691x225.png) ### 5. Parallel Old收集器 老年代 Parallel Old收集器是Parallel Scavenge收集器的老年代版本, 一個多線程收集器,jdk8 默認使用,采用標記-整理算法。可以與 Parallel Scavenge 收集器搭配,可以充分利用多核 CPU 的計算能力。 ### 6. CMS 老年代 (Concurrent Mark Sweep) 低延遲,一種以獲取最短回收停頓時間為目標的收集器。 新生代只能選擇ParNew或者Serial收集器中的一個 特點:基于標記-清除算法實現。并發收集、低停頓。 應用場景:適用于注重服務的響應速度,希望系統停頓時間最短,給用戶帶來更好的體驗等場景下。如web程序、b/s服務。 初始標記:標記GC Roots能直接到的對象。速度很快但是仍存在Stop The World問題。 并發標記:進行GC Roots 追蹤 的過程,找出存活對象且用戶線程可并發執行。 重新標記:為了修正并發標記期間因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄。仍然存在Stop The World問題。 并發清除:對標記的對象進行清除回收。 CMS收集器的內存回收過程除了初始標記,其他都是與用戶線程一起并發執行的。 CMS的優點: * 并發收集 * 低延遲 CMS收集器的缺點: * 對CPU資源非常敏感。其實,面向并發設計的程序都對CPU資源比較敏感。在并發階段,它雖然不會導致用戶線程停頓,但會因為占用了一部分線程(或者說CPU資源)而導致應用程序變慢,總吞吐量會降低。 * 無法處理浮動垃圾,可能出現Concurrent Model Failure失敗而導致另一次Full GC的產生。 * 因為采用標記-清除算法所以會存在內存碎片的問題,導致大對象無法分配空間,不得不提前觸發一次Full GC。 * 因為是并行執行的,沒法使用 標記-壓縮算法(標記壓縮需要移動對象,而GC線程和用戶線程同時在用,沒法移動) ![](https://img.kancloud.cn/fa/2d/fa2d76f7ea0034157b884a72186f6957_683x204.png) ### 7. G1(Garbage-First) 一款面向服務端應用的垃圾收集器,兼顧吞吐量和停頓時間。在JDK1.7版本正式啟用,是JDK 9以后的默認GC選項,取代了CMS回收器。 特點: * **并行與并發**:G1能充分利用多CPU、多核環境下的硬件優勢,使用多個CPU來縮短Stop-The-World停頓時間。部分收集器原本需要停頓Java線程來執行GC動作,G1收集器仍然可以通過并發的方式讓Java程序繼續運行。 * ** 分代收集**:G1能夠獨自管理整個Java堆,并且采用不同的方式去處理新創建的對象和已經存活了一段時間、熬過多次GC的舊對象以獲取更好的收集效果。 * **空間整合**:G1將內存劃分為一個個的區(region)。內存的回收是以區(region)作為基本單位的。區(region)之間是復制算法,但整體上實際可看作是標記-壓縮(Mark-Compact)·算法,兩種算法都可以避免內存碎片。這種特性有利于程序長時間運行,分配大對象時不會因為無法找到連續內存空間而提前觸發下一次GC。尤其是當Java堆非常大的時候, G1的優勢更加明顯。 * **可預測的停頓**:G1除了追求低停頓外,還能建立可預測的停頓時間模型。能讓使用者明確指定在一個長度為M毫秒的時間段內,消耗在垃圾收集上的時間不得超過N毫秒。 **G1為什么能建立可預測的停頓時間模型?** 因為它有計劃的避免在整個Java堆中進行全區域的垃圾收集。G1跟蹤各個區(region)里面的垃圾堆積的大小,在后臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的區(region)。這樣就保證了在有限的時間內可以獲取盡可能高的收集效率。 **G1與其他收集器的區別:** 其他收集器的工作范圍是整個新生代或者老年代、G1收集器的工作范圍是整個Java堆。在使用G1收集器時,它將整個Java堆劃分為多個大小相等的獨立區域(Region)。雖然也保留了新生代、老年代的概念,但新生代和老年代不再是相互隔離的,他們都是一部分Region(不需要連續)的集合。 **G1收集器存在的問題:** Region不可能是孤立的,分配在Region中的對象可以與Java堆中的任意對象發生引用關系。在采用可達性分析算法來判斷對象是否存活時,得掃描整個Java堆才能保證準確性。其他收集器也存在這種問題(G1更加突出而已)。會導致Minor GC效率下降。 **G1收集器是如何解決上述問題的?** 采用Remembered Set來避免整堆掃描。G1中每個Region都有一個與之對應的Remembered Set,虛擬機發現程序在對Reference類型進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用對象是否處于多個Region中(即檢查老年代中是否引用了新生代中的對象),如果是,便通過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set中。當進行內存回收時,在GC根節點的枚舉范圍中加入Remembered Set即可保證不對全堆進行掃描也不會有遺漏。 如果不計算維護?Remembered Set 的操作,G1收集器大致可分為如下步驟: **初始標記**:僅標記GC Roots能直接到的對象,并且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序并發運行時,能在正確可用的Region中創建新對象。(需要線程停頓,但耗時很短。) **并發標記**:從GC Roots開始對堆中對象進行可達性分析,找出存活對象。(耗時較長,但可與用戶程序并發執行) **最終標記**:為了修正在并發標記期間因用戶程序執行而導致標記產生變化的那一部分標記記錄。且對象的變化記錄在線程Remembered Set? Logs里面,把Remembered Set? Logs里面的數據合并到Remembered Set中。(需要線程停頓,但可并行執行。) **篩選回收**:對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來制定回收計劃。(可并發執行) 適用場景:要求盡可能可控 GC 停頓時間;具有大內存、多處理器的機器。可以用 -XX:+UseG1GC 使用 G1 收集器,jdk9 默認使用 G1 收集器。 ![](https://img.kancloud.cn/b4/82/b482350444d8b207d7f947426e3f69da_706x225.png) ## 6.17 請問吞吐量的優化和響應優先的垃圾收集器是如何選擇的呢? ## GC的優點和原理(機制)? 就是把 什么是GC, 回收的重點區域,GC判斷垃圾的方式,GC常用算法, 垃圾收集器, # 7. 調優 常用的性能優化方式有哪些?(JVM調優策略?) 棧溢出導致的原因?如何解決? JVM相關的分析工具使用過的有哪些?具體的性能調優步驟如何
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看