# 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. 執行引擎

這個架構可以分成三層看:
* 最上層: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) 對靜態變量和靜態代碼塊執行初始化工作


### 一、加載:
* 通過一個類的全限定名獲取定義此類的二進制字節流
* 將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構
* 在內存中生成一個代表這個類的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 (也叫做“系統類加載器”)開發人員自定義的也在這里

## 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. **本地方法棧** 線程**私有**,用于管理本地方法的調用。


## 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。

## 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的時候,需要停止整個應用程序,導致用戶體驗差

## 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 作為新生代收集器。

### 2. Serial Old 收集器 老年代
Serial Old 是 Serial收集器的老年代版本,它同樣是一個單線程收集器,使用“標記-整理”(Mark-Compact)算法。
此收集器的主要意義也是在于給Client模式下的虛擬機使用。如果在Server模式下,它還有兩大用途:
* 在JDK1.5 以及之前版本(Parallel Old誕生以前)中與Parallel Scavenge收集器搭配使用。
* 作為CMS收集器的后備預案,在并發收集發生Concurrent Mode Failure時使用。
### 3. ParNew 收集器 新生代收集器
Serial 的多線程版本。其他和 Servial 區別不大

### 4. Parallel Scavenge 收集器 新生代
吞吐量優先
jdk8 默認使用
Parallel Scavenge 也是一款用于新生代的多線程收集器,吞吐量優先
會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量。這種方式稱為GC自適應的調節策略。
另外值得注意的一點是,Parallel Scavenge收集器無法與CMS收集器配合使用,所以在JDK 1.6推出Parallel Old之前,如果新生代選擇Parallel Scavenge收集器,老年代只有Serial Old收集器能與之配合使用。

### 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線程和用戶線程同時在用,沒法移動)

### 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 收集器。

## 6.17 請問吞吐量的優化和響應優先的垃圾收集器是如何選擇的呢?
## GC的優點和原理(機制)?
就是把 什么是GC, 回收的重點區域,GC判斷垃圾的方式,GC常用算法, 垃圾收集器,
# 7. 調優
常用的性能優化方式有哪些?(JVM調優策略?)
棧溢出導致的原因?如何解決?
JVM相關的分析工具使用過的有哪些?具體的性能調優步驟如何
- 學習地址
- MySQL
- 查詢優化
- SQL優化
- 關于or、in、not in、!=等走不走索引的說明
- 千萬級數據查詢優化
- MySQL 深度分頁問題
- 嵌套循環 Block Nested Loop 導致索引查詢慢
- MySQL增加日志統計表優化各種日志表的統計功能
- MySQL單機讀寫QPS(性能)優化
- sqlMode 置 select 的值可以比 group 里的多
- drop、delete、truncate的區別
- 尚硅谷MySQL數據庫高級學習筆記
- MySQL架構
- 事務部分
- MySQL知識點
- mysql索引
- Linux docker安裝 mysql 8.0.25
- docker 安裝mysql 5.7
- mysql Field ‘xxx’ doesn’t have a default value
- mysql多實例
- docker中的sql文件導入
- mysql進階知識
- mysql字符集
- 連接的原理
- redo日志
- InnoDB存儲引擎
- InnoDB的數據存儲結構
- B+樹索引
- 文件系統-表空間
- Buffer Pool
- 億級數據導入到es
- MySQL數據復制
- MySQL缺少主鍵的表數據
- mysql update 其中更新的字段根據另一個更新字段作為條件去更新
- MySQL指定字段值排序(將指定值排在前面)
- 設置MySQL連接數、時區
- Navicat15右鍵刪除數據刷新就又恢復了
- MySQL替換字段部分內容
- Java和MySQL統計本周本月本季和年
- 分頁時order by 排序數據重復,丟失
- mysql同一張表根據某個字段刪除重復數據
- mysqldump定時全量熱備
- 專題總結
- 事務
- MySQL事務
- spring事務
- spring事務本類調用
- spring事務傳播行為
- spring事務失效問題
- 鎖和Transactional注解一塊使用的問題
- 數據安全
- 敏感數據
- SQL注入
- 數據源
- XSS
- 接口設計
- 緩存設計
- 限流
- 自定義注解實現根據用戶做QPS限流
- 架構
- 高可用
- Java
- Unsatisfied dependency expressed through field ‘baseMapper‘
- mybatisplus多數據源
- 單個字母前綴的java變量
- spring
- spring循環依賴解決
- 事務@Transactional
- yml 文件配置信息綁定到java工具類的靜態變量上
- @Configuration @Component 區別
- springboot啟動yml文件報錯
- spring方法重試注解Retryable
- spring讀取yml集合數據
- spring自定義注解
- 獲取resource下的圖片資源
- 手機號和電話號的正則驗證
- 獲取字符串中的數字
- mybatis
- mybatis多參數添加數據并返回主鍵
- 統一異常處理
- 分組校驗
- Java讀取Python json.dumps 函數保存的redis數據
- springboot整合springCache
- 若依mybatis值為null的字段沒有返回
- 若依
- 接口白名單
- @JsonFormat時區問題
- RequestParam.value() was empty on parameter 0
- jdk8和hutool請求第三方的https報錯
- springMVC
- springMVC與vue使用post傳數組
- elementUI 時間組件報錯問題
- vue具名插槽slot
- springboot配置maven的profiles(配置微服務多環境切換打包)
- resources 配置文件讀取順序
- Windows的cmd部署jar注意事項
- Java基礎
- JUC(鎖-并發-線程池)
- CAS
- Java 鎖簡介
- synchronized和Logk有什么區別?用新的ock有什么好處
- synchronized鎖介紹
- CompletableFuture
- 多線程
- 線程池
- 集合類
- map見過的小問題
- 退出雙層循環
- StringBuilder和StringBuffer核心區別
- 日志打印
- 打印log日志
- log日志文件生成配置
- 日期時間
- 時間戳轉為時間
- 并發工具
- 連接池
- http調用
- 內網訪問天地圖
- 判等問題
- 數值計算
- null問題
- 異常處理
- 文件IO
- 序列化
- 內存溢出OOM
- Double轉String出現E的問題
- springboot接收前端表單提交多字段和上傳文件
- 子線程的錯誤, 全局異常處理捕獲不到
- vue同一個項目訪問多個不同ip地址接口
- Autowired注解導入為null
- shiro
- UnavailableSecurityManagerException錯誤
- Windows服務器80端口被占用
- java圖片增加水印
- springcloud
- Feign方法配置錯誤導致jar包啟動失敗
- feign調用超時
- Springcloud從Nacos的yml文件讀取出錯
- 定時任務quartz
- JavaPOI導出Excel
- 合并行和列
- 設置樣式
- 設置背景色
- docker
- Linux 安裝
- docker命令
- docker網絡
- docker數據卷
- dockerfile
- docker安裝ping命令
- docker-compose
- docker-compose文件內容介紹
- Linux關閉docker開機啟動
- jar打包為鏡像
- 遷移docker容器存儲位置
- Nginx
- Linux在線安裝Nginx
- nginx.conf 核心配置文件
- vue 和 nginx 刷新頁面會報404
- nginx 轉發給三個集群的tomcat
- ServerName匹配規則
- Nginx負載均衡策略
- location 匹配規則
- Nginx 搭建前端調用后臺接口的集群
- alias與root
- nginx 攔截 post 請求, 帶參數轉發到前端頁面
- 防盜鏈配置
- Nginx的緩存
- 通用Nginx配置
- nginx配置文件服務器
- 后臺jar包得不到正確ip,nginx代理時要處理
- 升級使用websocket協議
- 設置IP黑/白名單
- vue項目get請求Nginx返回html頁面post返回405錯誤
- Nginx限制所有接口流量
- Redis
- 緩存數據一致性
- 內存淘汰策略
- Redis數據類型
- gmt6
- Linux安裝GMT6
- GMT6配置中文
- GMT文件修改Windows版本到Linux版本
- 注意GMT不同字體導致符號不同的問題
- GMT繪制南海諸島小圖
- GMT生成中文圖例
- elasticsearch
- 安裝配置
- Linux安裝配置elasticsearch7.6.2
- Linux 安裝 kibana 7.6.2
- 安裝7.6.2中文分詞器
- docker 安裝elasticsearch7.6.2
- 安裝Logback7.6.2
- springboot使用
- 0. elasticsearch賬號密碼模式訪問
- 1. 配置連接
- 2. 索引
- 3. 批量保存更新
- Result window is too large 10000
- elasticsearch 分詞的字段做排序 fielddata, 設置fielddata=true 無效果
- elasticsearch 完全匹配查詢(精確查詢)
- 模糊搜索
- 日期區間查詢
- 6.x基礎知識
- 自定義詞庫
- elasticsearch集群
- 搜索推薦Suggester
- 查詢es保存的數組
- 億級mysql數據導入到es
- es 報錯 ORBIDDEN/12/index read-only
- es核心概念
- es的分布式架構原理
- 優化大數據量時的ES查詢性能
- canal
- 1. mysql的Binlog
- 2. Canal 的工作原理
- 3. canal同步es
- JVM
- 1 類的字節碼
- 2. 類的加載
- JVM知識點
- Maven
- 依賴沖突
- xxl-job
- docker 安裝配置 xxl-job
- idea
- springboot啟動報錯命令過長
- services統一啟動微服務各模塊
- 云服務器安裝寶塔面板
- 突然出現啟動或者運行特別慢
- 有導入依賴但是顯示紅色同時點擊進去也有依賴
- Linux
- sh文件執行報錯: command not found
- 使用vagrant安裝虛擬機
- Linux 開啟端口
- 開放端口
- 復制文件夾及其文件到另一個文件夾
- 兩個服務器之間映射端口
- TCP協議
- 分層模型
- TCP概述
- 支撐 TCP 協議的基石 —— 首部字段
- 數據包大小對網絡的影響 —— MTU 與 MSS 的奧秘
- 端口號
- 三次握手
- TCP 自連接
- 四次揮手
- TCP 頭部時間戳
- 分布式
- 分布式腦裂問題
- 分布式事務
- 基礎知識
- 實現分布式事務的方案
- 阿里分布式事務中間件seata
- 冪等性問題
- 其他工具
- webstorm git提交代碼后project目錄樹不顯示
- 消息隊列
- 如何保證消費的順序
- 數據結構
- 漫畫算法:小灰的算法之旅
- oracle