## Java內存模型
### 我們開發人員編寫的Java代碼是怎么讓電腦認識的
* 首先先了解電腦是二進制的系統,他只認識 01010101
* 比如我們經常要編寫 HelloWord.java 電腦是怎么認識運行的
* HelloWord.java是我們程序員編寫的,我們人可以認識,但是電腦不認識
**Java文件編譯的過程**
1. 程序員編寫的.java文件
2. 由javac編譯成字節碼文件.class:(為什么編譯成class文件,因為JVM只認識.class文件)
3. 在由JVM編譯成電腦認識的文件 (對于電腦系統來說 文件代表一切)
`(這是一個大概的觀念 抽象畫的概念)`

### 為什么說java是跨平臺語言
* 這個夸平臺是中間語言(JVM)實現的夸平臺
* Java有JVM從軟件層面屏蔽了底層硬件、指令層面的細節讓他兼容各種系統
`難道 C 和 C++ 不能夸平臺嗎 其實也可以` `C和C++需要在編譯器層面去兼容不同操作系統的不同層面,寫過C和C++的就知道不同操作系統的有些代碼是不一樣`
### Jdk和Jre和JVM的區別
* Jdk包括了Jre和Jvm,Jre包括了Jvm
* Jdk是我們編寫代碼使用的開發工具包
* Jre 是Java的運行時環境,他大部分都是 C 和 C++ 語言編寫的,他是我們在編譯java時所需要的基礎的類庫
* Jvm俗稱Java虛擬機,他是java運行環境的一部分,它虛構出來的一臺計算機,在通過在實際的計算機上仿真模擬各種計算機功能來實現Java應用程序
* 看Java官方的圖片,Jdk中包括了Jre,Jre中包括了JVM

### 說一下 JVM由那些部分組成,運行流程是什么?

* JVM包含兩個子系統和兩個組件: 兩個子系統為Class loader(類裝載)、Execution engine(執行引擎); 兩個組件為Runtime data area(運行時數據區)、Native Interface(本地接口)。
* Class loader(類裝載):根據給定的全限定名類名(如:java.lang.Object)來裝載class文件到Runtime data area中的method area。
* Execution engine(執行引擎):執行classes中的指令。
* Native Interface(本地接口):與native libraries交互,是其它編程語言交互的接口。
* Runtime data area(運行時數據區域):這就是我們常說的JVM的內存。
* **流程** :首先通過編譯器把 Java 代碼轉換成字節碼,類加載器(ClassLoader)再把字節碼加載到內存中,將其放在運行時數據區(Runtime data area)的方法區內,而字節碼文件只是 JVM 的一套指令集規范,并不能直接交給底層操作系統去執行,因此需要特定的命令解析器執行引擎(Execution Engine),將字節碼翻譯成底層系統指令,再交由 CPU 去執行,而這個過程中需要調用其他語言的本地庫接口(Native Interface)來實現整個程序的功能。
### 說一下 JVM 運行時數據區
* Java 虛擬機在執行 Java 程序的過程中會把它所管理的內存區域劃分為若干個不同的數據區域。這些區域都有各自的用途,以及創建和銷毀的時間,有些區域隨著虛擬機進程的啟動而存在,有些區域則是依賴線程的啟動和結束而建立和銷毀。Java 虛擬機所管理的內存被劃分為如下幾個區域:
`簡單的說就是我們java運行時的東西是放在那里的`

* 程序計數器(Program Counter Register):當前線程所執行的字節碼的行號指示器,字節碼解析器的工作是通過改變這個計數器的值,來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能,都需要依賴這個計數器來完成;
`為什么要線程計數器?因為線程是不具備記憶功能`
* Java 虛擬機棧(Java Virtual Machine Stacks):每個方法在執行的同時都會在Java 虛擬機棧中創建一個棧幀(Stack Frame)用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息;
`棧幀就是Java虛擬機棧中的下一個單位`
* 本地方法棧(Native Method Stack):與虛擬機棧的作用是一樣的,只不過虛擬機棧是服務 Java 方法的,而本地方法棧是為虛擬機調用 Native 方法服務的;
`Native 關鍵字修飾的方法是看不到的,Native 方法的源碼大部分都是 C和C++ 的代碼`
* Java 堆(Java Heap):Java 虛擬機中內存最大的一塊,是被所有線程共享的,幾乎所有的對象實例都在這里分配內存;
* 方法區(Methed Area):用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯后的代碼等數據。
`后面有詳細的說明JVM 運行時數據區`
### 詳細的介紹下程序計數器?(重點理解)
1. 程序計數器是一塊較小的內存空間,它可以看作是:保存當前線程所正在執行的字節碼指令的地址(行號)
2. 由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,一個處理器都只會執行一條線程中的指令。因此,為了線程切換后能恢復到正確的執行位置,每條線程都有一個獨立的程序計數器,各個線程之間計數器互不影響,獨立存儲。稱之為“線程私有”的內存。程序計數器內存區域是虛擬機中唯一沒有規定OutOfMemoryError情況的區域。
`總結:也可以把它叫做線程計數器`
* **例子**:在java中最小的執行單位是線程,線程是要執行指令的,執行的指令最終操作的就是我們的電腦,就是 CPU。在CPU上面去運行,有個非常不穩定的因素,叫做調度策略,這個調度策略是時基于時間片的,也就是當前的這一納秒是分配給那個指令的。
* **假如**:
* 線程A在看直播 
* 突然,線程B來了一個視頻電話,就會搶奪線程A的時間片,就會打斷了線程A,線程A就會掛起 
* 然后,視頻電話結束,這時線程A究竟該干什么? (線程是最小的執行單位,他不具備記憶功能,他只負責去干,那這個記憶就由:**程序計數器來記錄**) 
### 詳細介紹下Java虛擬機棧?(重點理解)
1. Java虛擬機是線程私有的,它的生命周期和線程相同。
2. 虛擬機棧描述的是Java方法執行的內存模型:`每個方法在執行的同時`都會創建一個棧幀(Stack Frame)用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。
* **解釋**:虛擬機棧中是有單位的,單位就是**棧幀**,一個方法一個**棧幀**。一個**棧幀**中他又要存儲,局部變量,操作數棧,動態鏈接,出口等。

**解析棧幀:**
1. 局部變量表:是用來存儲我們臨時8個基本數據類型、對象引用地址、returnAddress類型。(returnAddress中保存的是return后要執行的字節碼的指令地址。)
2. 操作數棧:操作數棧就是用來操作的,例如代碼中有個 i = 6\*6,他在一開始的時候就會進行操作,讀取我們的代碼,進行計算后再放入局部變量表中去
3. 動態鏈接:假如我方法中,有個 service.add()方法,要鏈接到別的方法中去,這就是動態鏈接,存儲鏈接的地方。
4. 出口:出口是什呢,出口正常的話就是return 不正常的話就是拋出異常落
#### 一個方法調用另一個方法,會創建很多棧幀嗎?
* 答:會創建。如果一個棧中有動態鏈接調用別的方法,就會去創建新的棧幀,棧中是由順序的,一個棧幀調用另一個棧幀,另一個棧幀就會排在調用者下面
#### 棧指向堆是什么意思?
* 棧指向堆是什么意思,就是棧中要使用成員變量怎么辦,棧中不會存儲成員變量,只會存儲一個應用地址
#### 遞歸的調用自己會創建很多棧幀嗎?
* 答:遞歸的話也會創建多個棧幀,就是在棧中一直從上往下排下去
### 你能給我詳細的介紹Java堆嗎?(重點理解)
* java堆(Java Heap)是java虛擬機所管理的內存中最大的一塊,是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例。
* 在Java虛擬機規范中的描述是:所有的對象實例以及數組都要在堆上分配。
* java堆是垃圾收集器管理的主要區域,因此也被成為“GC堆”。
* 從內存回收角度來看java堆可分為:新生代和老生代。
* 從內存分配的角度看,線程共享的Java堆中可能劃分出多個線程私有的分配緩沖區。
* 無論怎么劃分,都與存放內容無關,無論哪個區域,存儲的都是對象實例,進一步的劃分都是為了更好的回收內存,或者更快的分配內存。
* 根據Java虛擬機規范的規定,java堆可以處于物理上不連續的內存空間中。當前主流的虛擬機都是可擴展的(通過 -Xmx 和 -Xms 控制)。如果堆中沒有內存可以完成實例分配,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常。
### 能不能解釋一下本地方法棧?
1. 本地方法棧很好理解,他很棧很像,只不過方法上帶了 native 關鍵字的棧字
2. 它是虛擬機棧為虛擬機執行Java方法(也就是字節碼)的服務方法
3. native關鍵字的方法是看不到的,必須要去oracle官網去下載才可以看的到,而且native關鍵字修飾的大部分源碼都是C和C++的代碼。
4. 同理可得,本地方法棧中就是C和C++的代碼
### 能不能解釋一下方法區(重點理解)
1. 方法區是所有線程共享的內存區域,它用于存儲已被Java虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
2. 它有個別命叫Non-Heap(非堆)。當方法區無法滿足內存分配需求時,拋出OutOfMemoryError異常。
### 什么是JVM字節碼執行引擎
* 虛擬機核心的組件就是執行引擎,它負責執行虛擬機的字節碼,一般戶先進行編譯成機器碼后執行。
* “虛擬機”是一個相對于“物理機”的概念,虛擬機的字節碼是不能直接在物理機上運行的,需要JVM字節碼執行引擎- 編譯成機器碼后才可在物理機上執行。
### 你聽過直接內存嗎?
* 直接內存(Direct Memory)并不是虛擬機運行時數據區的一部分,也不是Java虛擬機中定義的內存區域。但是這部分內存也被頻繁地使用,而且也可能導致 OutOfMemoryError 異常出現,所以我們放到這里一起講解。
* 我的理解就是直接內存是基于物理內存和Java虛擬機內存的中間內存
### 知道垃圾收集系統嗎?
* 程序在運行過程中,會產生大量的內存垃圾(一些沒有引用指向的內存對象都屬于內存垃圾,因為這些對象已經無法訪問,程序用不了它們了,對程序而言它們已經死亡),為了確保程序運行時的性能,java虛擬機在程序運行的過程中不斷地進行自動的垃圾回收(GC)。
* 垃圾收集系統是Java的核心,也是不可少的,Java有一套自己進行垃圾清理的機制,開發人員無需手工清理
* 有一部分原因就是因為Java垃圾回收系統的強大導致Java領先市場
### 堆棧的區別是什么?
> | 對比 | JVM堆 | JVM棧 |
> | --- | --- | --- |
> | 物理地址 | 堆的物理地址分配對對象是不連續的。因此性能慢些。在GC的時候也要考慮到不連續的分配,所以有各種算法。比如,標記-消除,復制,標記-壓縮,分代(即新生代使用復制算法,老年代使用標記——壓縮) | 棧使用的是數據結構中的棧,先進后出的原則,物理地址分配是連續的。所以性能快。 |
> | 內存分別 | 堆因為是不連續的,所以分配的內存是在運行期確認的,因此大小不固定。一般堆大小遠遠大于棧。 | 棧是連續的,所以分配的內存大小要在編譯期就確認,大小是固定的。 |
> | 存放的內容 | 堆存放的是對象的實例和數組。因此該區更關注的是數據的存儲 | 棧存放:局部變量,操作數棧,返回結果。該區更關注的是程序方法的執行。 |
> | 程序的可見度 | 堆對于整個應用程序都是共享、可見的。 | 棧只對于線程是可見的。所以也是線程私有。他的生命周期和線程相同。 |
* 注意:
* 靜態變量放在方法區
* 靜態的對象還是放在堆。
### 深拷貝和淺拷貝
* 淺拷貝(shallowCopy)只是增加了一個指針指向已存在的內存地址,
* 深拷貝(deepCopy)是增加了一個指針并且申請了一個新的內存,使這個增加的指針指向這個新的內存,
* 淺復制:僅僅是指向被復制的內存地址,如果原地址發生改變,那么淺復制出來的對象也會相應的改變。
* 深復制:在計算機中開辟一塊**新的內存地址**用于存放復制的對象。
### Java會存在內存泄漏嗎?請說明為什么?
* 內存泄漏是指不再被使用的對象或者變量一直被占據在內存中。理論上來說,Java是有GC垃圾回收機制的,也就是說,不再被使用的對象,會被GC自動回收掉,自動從內存中清除。
* 但是,即使這樣,Java也還是存在著內存泄漏的情況,java導致內存泄露的原因很明確:長生命周期的對象持有短生命周期對象的引用就很可能發生內存泄露,`盡管短生命周期對象已經不再需要,但是因為長生命周期對象持有它的引用而導致不能被回收`,這就是java中內存泄露的發生場景。
## 垃圾回收機制及算法
### 簡述Java垃圾回收機制
* 在java中,程序員是不需要顯示的去釋放一個對象的內存的,而是由虛擬機自行執行。在JVM中,有一個垃圾回收線程,它是低優先級的,在正常情況下是不會執行的,只有在虛擬機空閑或者當前堆內存不足時,才會觸發執行,掃面那些沒有被任何引用的對象,并將它們添加到要回收的集合中,進行回收。
### GC是什么?為什么要GC
* GC 是垃圾收集的意思(Gabage Collection),內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會導致程序或系統的不穩定甚至崩潰,Java 提供的 GC 功能可以自動監測對象是否超過作用域從而達到自動回收內存的目的,Java 語言沒有提供釋放已分配內存的顯示操作方法。
### 垃圾回收的優點和缺點
* 優點:JVM的垃圾回收器都不需要我們手動處理無引用的對象了,這個就是最大的優點
* 缺點:程序員不能實時的對某個對象或所有對象調用垃圾回收器進行垃圾回收。
### 垃圾回收器的原理是什么?有什么辦法手動進行垃圾回收?
* 對于GC來說,當程序員創建對象時,GC就開始監控這個對象的地址、大小以及使用情況。
* 通常,GC采用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是"可達的",哪些對象是"不可達的"。當GC確定一些對象為"不可達"時,GC就有責任回收這些內存空間。
* 可以。程序員可以手動執行System.gc(),通知GC運行,但是Java語言規范并不保證GC一定會執行。
### JVM 中都有哪些引用類型?
* 強引用:發生 gc 的時候不會被回收。
* 軟引用:有用但不是必須的對象,在發生內存溢出之前會被回收。
* 弱引用:有用但不是必須的對象,在下一次GC時會被回收。
* 虛引用(幽靈引用/幻影引用):無法通過虛引用獲得對象,用 PhantomReference 實現虛引用,虛引用的用途是在 gc 時返回一個通知。
### 怎么判斷對象是否可以被回收?
* 垃圾收集器在做垃圾回收的時候,首先需要判定的就是哪些內存是需要被回收的,哪些對象是存活的,是不可以被回收的;哪些對象已經死掉了,需要被回收。
* 一般有兩種方法來判斷:
* 引用計數器法:為每個對象創建一個引用計數,有對象引用時計數器 +1,引用被釋放時計數 -1,當計數器為 0 時就可以被回收。它有一個缺點不能解決循環引用的問題;(這個已經淘汰了)
* 可達性分析算法:從 GC Roots 開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連時,則證明此對象是可以被回收的。(市場上用的非常非常廣泛)
### Full GC是什么
* 清理整個堆空間—包括年輕代和老年代和永久代
* 因為Full GC是清理整個堆空間所以Full GC執行速度非常慢,在Java開發中最好保證少觸發Full GC
### 對象什么時候可以被垃圾器回收
* 當對象對當前使用這個對象的應用程序變得不可觸及的時候,這個對象就可以被回收了。
* 垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。如果你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是為什么正確的永久代大小對避免Full GC是非常重要的原因。
### JVM 垃圾回收算法有哪些?
* 標記-清除算法:標記無用對象,然后進行清除回收。缺點:效率不高,無法清除垃圾碎片。
* 復制算法:按照容量劃分二個大小相等的內存區域,當一塊用完的時候將活著的對象復制到另一塊上,然后再把已使用的內存空間一次清理掉。缺點:內存使用率不高,只有原來的一半。
* 標記-整理算法:標記無用對象,讓所有存活的對象都向一端移動,然后直接清除掉端邊界以外的內存。
* 分代算法:根據對象存活周期的不同將內存劃分為幾塊,一般是新生代和老年代,新生代基本采用復制算法,老年代采用標記整理算法。
#### 標記-清除算法
* 標記無用對象,然后進行清除回收。
* 標記-清除算法(Mark-Sweep)是一種常見的基礎垃圾收集算法,它將垃圾收集分為兩個階段:
* 標記階段:標記出可以回收的對象。
* 清除階段:回收被標記的對象所占用的空間。
* 標記-清除算法之所以是基礎的,是因為后面講到的垃圾收集算法都是在此算法的基礎上進行改進的。
* **優點**:實現簡單,不需要對象進行移動。
* **缺點**:標記、清除過程效率低,產生大量不連續的內存碎片,提高了垃圾回收的頻率。
* 標記-清除算法的執行的過程如下圖所示

#### 復制算法
* 為了解決標記-清除算法的效率不高的問題,產生了復制算法。它把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾收集時,遍歷當前使用的區域,把存活對象復制到另外一個區域中,最后將當前使用的區域的可回收的對象進行回收。
* **優點**:按順序分配內存即可,實現簡單、運行高效,不用考慮內存碎片。
* **缺點**:可用的內存大小縮小為原來的一半,對象存活率高時會頻繁進行復制。
* 復制算法的執行過程如下圖所示

#### 標記-整理算法
* 在新生代中可以使用復制算法,但是在老年代就不能選擇復制算法了,因為老年代的對象存活率會較高,這樣會有較多的復制操作,導致效率變低。標記-清除算法可以應用在老年代中,但是它效率不高,在內存回收后容易產生大量內存碎片。因此就出現了一種標記-整理算法(Mark-Compact)算法,與標記-整理算法不同的是,在標記可回收的對象后將所有存活的對象壓縮到內存的一端,使他們緊湊的排列在一起,然后對端邊界以外的內存進行回收。回收后,已用和未用的內存都各自一邊。
* **優點**:解決了標記-清理算法存在的內存碎片問題。
* **缺點**:仍需要進行局部對象移動,一定程度上降低了效率。
* 標記-整理算法的執行過程如下圖所示

#### 分代收集算法
* 當前商業虛擬機都采用 `分代收集`的垃圾收集算法。分代收集算法,顧名思義是根據對象的`存活周期`將內存劃分為幾塊。一般包括`年輕代`、`老年代`和 `永久代`,如圖所示:`(后面有重點講解)`

### JVM中的永久代中會發生垃圾回收嗎
* 垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。如果你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是為什么正確的永久代大小對避免Full GC是非常重要的原因。請參考下Java8:從永久代到元數據區
(注:Java8中已經移除了永久代,新加了一個叫做元數據區的native內存區)
## 垃圾收集器以及新生代、老年代、永久代
### 講一下新生代、老年代、永久代的區別

* 在 Java 中,堆被劃分成兩個不同的區域:新生代 ( Young )、老年代 ( Old )。而新生代 ( Young ) 又被劃分為三個區域:Eden、From Survivor、To Survivor。這樣劃分的目的是為了使 JVM 能夠更好的管理堆內存中的對象,包括內存的分配以及回收。
* 新生代中一般保存新出現的對象,所以每次垃圾收集時都發現大批對象死去,只有少量對象存活,便采用了`復制算法`,只需要付出少量存活對象的復制成本就可以完成收集。
* 老年代中一般保存存活了很久的對象,他們存活率高、沒有額外空間對它進行分配擔保,就必須采用`“標記-清理”或者“標記-整理”`算法。
* 永久代就是JVM的方法區。在這里都是放著一些被虛擬機加載的類信息,靜態變量,常量等數據。這個區中的東西比老年代和新生代更不容易回收。
### Minor GC、Major GC、Full GC是什么
1. Minor GC是新生代GC,指的是發生在新生代的垃圾收集動作。由于java對象大都是朝生夕死的,所以Minor GC非常頻繁,一般回收速度也比較快。(一般采用復制算法回收垃圾)
2. Major GC是老年代GC,指的是發生在老年代的GC,通常執行Major GC會連著Minor GC一起執行。Major GC的速度要比Minor GC慢的多。(可采用標記清楚法和標記整理法)
3. Full GC是清理整個堆空間,包括年輕代和老年代
### Minor GC、Major GC、Full GC區別及觸發條件
* **Minor GC 觸發條件一般為:**
1. eden區滿時,觸發MinorGC。即申請一個對象時,發現eden區不夠用,則觸發一次MinorGC。
2. 新創建的對象大小 > Eden所剩空間時觸發Minor GC
* **Major GC和Full GC 觸發條件一般為:** `Major GC通常是跟full GC是等價的`
1. 每次晉升到老年代的對象平均大小>老年代剩余空間
2. MinorGC后存活的對象超過了老年代剩余空間
3. 永久代空間不足
4. 執行System.gc()
5. CMS GC異常
6. 堆內存分配很大的對象
### 為什么新生代要分Eden和兩個 Survivor 區域?
* 如果沒有Survivor,Eden區每進行一次Minor GC,存活的對象就會被送到老年代。老年代很快被填滿,觸發Major GC.老年代的內存空間遠大于新生代,進行一次Full GC消耗的時間比Minor GC長得多,所以需要分為Eden和Survivor。
* Survivor的存在意義,就是減少被送到老年代的對象,進而減少Full GC的發生,Survivor的預篩選保證,只有經歷15次Minor GC還能在新生代中存活的對象,才會被送到老年代。
* 設置兩個Survivor區最大的好處就是解決了碎片化,剛剛新建的對象在Eden中,經歷一次Minor GC,Eden中的存活對象就會被移動到第一塊survivor space S0,Eden被清空;等Eden區再滿了,就再觸發一次Minor GC,Eden和S0中的存活對象又會被復制送入第二塊survivor space S1(這個過程非常重要,因為這種復制算法保證了S1中來自S0和Eden兩部分的存活對象占用連續的內存空間,避免了碎片化的發生)
### Java堆老年代( Old ) 和新生代 ( Young ) 的默認比例?
* 默認的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數 –XX:NewRatio 來指定 ),即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。
* 其中,新生代 ( Young ) 被細分為 Eden 和 **兩個 Survivor 區域**,Edem 和倆個Survivor 區域比例是 = 8 : 1 : 1 ( 可以通過參數 –XX:SurvivorRatio 來設定 ),
* 但是JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來為對象服務,所以無論什么時候,總是有一塊 Survivor 區域是空閑著的。
### 為什么要這樣分代:
* 其實主要原因就是可以根據各個年代的特點進行對象分區存儲,更便于回收,采用最適當的收集算法:
* 新生代中,每次垃圾收集時都發現大批對象死去,只有少量對象存活,便采用了復制算法,只需要付出少量存活對象的復制成本就可以完成收集。
* 而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須采用“標記-清理”或者“標記-整理”算法。
* 新生代又分為Eden和Survivor (From與To,這里簡稱一個區)兩個區。加上老年代就這三個區。數據會首先分配到Eden區當中(當然也有特殊情況,如果是大對象那么會直接放入到老年代(大對象是指需要大量連續內存空間的java對象)。當Eden沒有足夠空間的時候就會觸發jvm發起一次Minor GC,。如果對象經過一次Minor-GC還存活,并且又能被Survivor空間接受,那么將被移動到Survivor空間當中。并將其年齡設為1,對象在Survivor每熬過一次Minor GC,年齡就加1,當年齡達到一定的程度(默認為15)時,就會被晉升到老年代中了,當然晉升老年代的年齡是可以設置的。
### 什么是垃圾回收器他和垃圾算法有什么區別
* 垃圾收集器是垃圾回收算法(標記清楚法、標記整理法、復制算法、分代算法)的具體實現,不同垃圾收集器、不同版本的JVM所提供的垃圾收集器可能會有很在差別。
### 說一下 JVM 有哪些垃圾回收器?
* 如果說垃圾收集算法是內存回收的方法論,那么垃圾收集器就是內存回收的具體實現。下圖展示了7種作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,還有用于回收整個Java堆的G1收集器。不同收集器之間的連線表示它們可以搭配使用。

> | 垃圾回收器 | 工作區域 | 回收算法 | 工作線程 | 用戶線程并行 | 描述 |
> | --- | --- | --- | --- | --- | --- |
> | Serial | 新生帶 | 復制算法 | 單線程 | 否 | Client模式下默認新生代收集器。簡單高效 |
> | ParNew | 新生帶 | 復制算法 | 多線程 | 否 | Serial的多線程版本,Server模式下首選, 可搭配CMS的新生代收集器 |
> | Parallel Scavenge | 新生帶 | 復制算法 | 多線程 | 否 | 目標是達到可控制的吞吐量 |
> | Serial Old | 老年帶 | 標記-整理 | 單線程 | 否 | Serial老年代版本,給Client模式下的虛擬機使用 |
> | Parallel Old | 老年帶 | 標記-整理 | 多線程 | 否 | Parallel Scavenge老年代版本,吞吐量優先 |
> | G1 | 新生帶 + 老年帶 | 標記-整理 + 復制算法 | 多線程 | 是 | JDK1.9默認垃圾收集器 |
* Serial收集器(復制算法): 新生代單線程收集器,標記和清理都是單線程,優點是簡單高效;
* ParNew收集器 (復制算法): 新生代收并行集器,實際上是Serial收集器的多線程版本,在多核CPU環境下有著比Serial更好的表現;
* Parallel Scavenge收集器 (復制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用戶線程時間/(用戶線程時間+GC線程時間),高吞吐量可以高效率的利用CPU時間,盡快完成程序的運算任務,適合后臺應用等對交互相應要求不高的場景;
* Serial Old收集器 (標記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;
* Parallel Old收集器 (標記-整理算法): 老年代并行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;
* CMS(Concurrent Mark Sweep)收集器(標記-清除算法): 老年代并行收集器,以獲取最短回收停頓時間為目標的收集器,具有高并發、低停頓的特點,追求最短GC回收停頓時間。
* G1(Garbage First)收集器 ( `標記整理 + 復制算法來回收垃圾` ): Java堆并行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基于“標記-整理”算法實現,也就是說不會產生內存碎片。此外,G1收集器不同于之前的收集器的一個重要特點是:G1回收的范圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的范圍僅限于新生代或老年代。
### 收集器可以這么分配?(了解就好了)
~~~
Serial / Serial Old
Serial / CMS
ParNew / Serial Old
ParNew / CMS
Parallel Scavenge / Serial Old
Parallel Scavenge / Parallel Old
G1
復制代碼
~~~
### 新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么區別?
* 新生代回收器:Serial、ParNew、Parallel Scavenge
* 老年代回收器:Serial Old、Parallel Old、CMS
* 整堆回收器:G1
新生代垃圾回收器一般采用的是復制算法,復制算法的優點是效率高,缺點是內存利用率低;老年代回收器一般采用的是標記-整理的算法進行垃圾回收。
### 簡述分代垃圾回收器是怎么工作的?
* 分代回收器有兩個分區:老生代和新生代,新生代默認的空間占比總空間的 1/3,老生代的默認占比是 2/3。
* 新生代使用的是復制算法,新生代里有 3 個分區:Eden、To Survivor、From Survivor,它們的默認占比是 8:1:1,它的執行流程如下:
* 把 Eden + From Survivor 存活的對象放入 To Survivor 區;
* 清空 Eden 和 From Survivor 分區;
* From Survivor 和 To Survivor 分區交換,From Survivor 變 To Survivor,To Survivor 變 From Survivor。
* 每次在 From Survivor 到 To Survivor 移動時都存活的對象,年齡就 +1,當年齡到達 15(默認配置是 15)時,升級為老生代。大對象也會直接進入老生代。
* 老生代當空間占用到達某個值之后就會觸發全局垃圾收回,一般使用標記整理的執行算法。以上這些循環往復就構成了整個分代垃圾回收的整體執行流程。
## 內存分配策略
### 簡述java內存分配與回收策率以及Minor GC和Major GC
* 所謂自動內存管理,最終要解決的也就是內存分配和內存回收兩個問題。前面我們介紹了內存回收,這里我們再來聊聊內存分配。
* 對象的內存分配通常是在 Java 堆上分配(隨著虛擬機優化技術的誕生,某些場景下也會在棧上分配,后面會詳細介紹),對象主要分配在新生代的 Eden 區,如果啟動了本地線程緩沖,將按照線程優先在 TLAB 上分配。少數情況下也會直接在老年代上分配。總的來說分配規則不是百分百固定的,其細節取決于哪一種垃圾收集器組合以及虛擬機相關參數有關,但是虛擬機對于內存的分配還是會遵循以下幾種「普世」規則:
#### 對象優先在 Eden 區分配
* 多數情況,對象都在新生代 Eden 區分配。當 Eden 區分配沒有足夠的空間進行分配時,虛擬機將會發起一次 Minor GC。如果本次 GC 后還是沒有足夠的空間,則將啟用分配擔保機制在老年代中分配內存。
* 這里我們提到 Minor GC,如果你仔細觀察過 GC 日常,通常我們還能從日志中發現 Major GC/Full GC。
* **Minor GC** 是指發生在新生代的 GC,因為 Java 對象大多都是朝生夕死,所有 Minor GC 非常頻繁,一般回收速度也非常快;
* **Major GC/Full GC** 是指發生在老年代的 GC,出現了 Major GC 通常會伴隨至少一次 Minor GC。Major GC 的速度通常會比 Minor GC 慢 10 倍以上。
#### 為什么大對象直接進入老年代
* 所謂大對象是指需要大量連續內存空間的對象,頻繁出現大對象是致命的,會導致在內存還有不少空間的情況下提前觸發 GC 以獲取足夠的連續空間來安置新對象。
* 前面我們介紹過新生代使用的是標記-清除算法來處理垃圾回收的,如果大對象直接在新生代分配就會導致 Eden 區和兩個 Survivor 區之間發生大量的內存復制。因此對于大對象都會直接在老年代進行分配。
#### 長期存活對象將進入老年代
* 虛擬機采用分代收集的思想來管理內存,那么內存回收時就必須判斷哪些對象應該放在新生代,哪些對象應該放在老年代。因此虛擬機給每個對象定義了一個對象年齡的計數器,如果對象在 Eden 區出生,并且能夠被 Survivor 容納,將被移動到 Survivor 空間中,這時設置對象年齡為 1。對象在 Survivor 區中每「熬過」一次 Minor GC 年齡就加 1,當年齡達到一定程度(默認 15) 就會被晉升到老年代。
## 虛擬機類加載機制
### 簡述java類加載機制?
* 虛擬機把描述類的數據從Class文件加載到內存,并對數據進行校驗,解析和初始化,最終形成可以被虛擬機直接使用的java類型。
### 類加載的機制及過程
* 程序主動使用某個類時,如果該類還未被加載到內存中,則JVM會通過加載、連接、初始化3個步驟來對該類進行初始化。如果沒有意外,JVM將會連續完成3個步驟,所以有時也把這個3個步驟統稱為類加載或類初始化。

##### 1、加載
* 加載指的是將類的class文件讀入到內存,并將這些靜態數據轉換成方法區中的運行時數據結構,并在堆中生成一個代表這個類的java.lang.Class對象,作為方法區類數據的訪問入口,這個過程需要類加載器參與。
* Java類加載器由JVM提供,是所有程序運行的基礎,JVM提供的這些類加載器通常被稱為系統類加載器。除此之外,開發者可以通過繼承ClassLoader基類來創建自己的類加載器。
* 類加載器,可以從不同來源加載類的二進制數據,比如:本地Class文件、Jar包Class文件、網絡Class文件等等等。
* 類加載的最終產物就是位于堆中的Class對象(注意不是目標類對象),該對象封裝了類在方法區中的數據結構,并且向用戶提供了訪問方法區數據結構的接口,即Java反射的接口
##### 2、連接過程
* 當類被加載之后,系統為之生成一個對應的Class對象,接著將會進入連接階段,連接階段負責把類的二進制數據合并到JRE中(意思就是將java類的二進制代碼合并到JVM的運行狀態之中)。類連接又可分為如下3個階段。
1. 驗證:確保加載的類信息符合JVM規范,沒有安全方面的問題。主要驗證是否符合Class文件格式規范,并且是否能被當前的虛擬機加載處理。
2. 準備:正式為類變量(static變量)分配內存并設置類變量初始值的階段,這些內存都將在方法區中進行分配
3. 解析:虛擬機常量池的符號引用替換為字節引用過程
##### 3、初始化
* 初始化階段是執行類構造器`<clinit>`() 方法的過程。類構造器`<clinit>`()方法是由編譯器自動收藏類中的`所有類變量的賦值動作和靜態語句塊(static塊)中的語句合并產生,代碼從上往下執行。`
* 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化
* 虛擬機會保證一個類的`<clinit>`() 方法在多線程環境中被正確加鎖和同步
`初始化的總結就是:初始化是為類的靜態變量賦予正確的初始值`
### 描述一下JVM加載Class文件的原理機制
* Java中的所有類,都需要由類加載器裝載到JVM中才能運行。類加載器本身也是一個類,而它的工作就是把class文件從硬盤讀取到內存中。在寫程序的時候,我們幾乎不需要關心類的加載,因為這些都是隱式裝載的,除非我們有特殊的用法,像是反射,就需要顯式的加載所需要的類。
* 類裝載方式,有兩種 :
* 1.隱式裝載, 程序在運行過程中當碰到通過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中,
* 2.顯式裝載, 通過class.forname()等方法,顯式加載需要的類
* Java類的加載是動態的,它并不會一次性將所有類全部加載后再運行,而是保證程序運行的基礎類(像是基類)完全加載到jvm中,至于其他類,則在需要的時候才加載。這當然就是為了節省內存開銷。
### 什么是類加載器,類加載器有哪些?

* 實現通過類的權限定名獲取該類的二進制字節流的代碼塊叫做類加載器。
* 主要有一下四種類加載器:
1. 啟動類加載器(Bootstrap ClassLoader)用來加載java核心類庫,無法被java程序直接引用。
2. 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。
3. 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來獲取它。
4. 用戶自定義類加載器,通過繼承 java.lang.ClassLoader類的方式實現。
### 說一下類裝載的執行過程?
* 類裝載分為以下 5 個步驟:
* 加載:根據查找路徑找到相應的 class 文件然后導入;
* 驗證:檢查加載的 class 文件的正確性;
* 準備:給類中的靜態變量分配內存空間;
* 解析:虛擬機將常量池中的符號引用替換成直接引用的過程。符號引用就理解為一個標示,而在直接引用直接指向內存中的地址;
* 初始化:對靜態變量和靜態代碼塊執行初始化工作。
### 什么是雙親委派模型?
* 在介紹雙親委派模型之前先說下類加載器。對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立在 JVM 中的唯一性,每一個類加載器,都有一個獨立的類名稱空間。類加載器就是根據指定全限定名稱將 class 文件加載到 JVM 內存,然后再轉化為 class 對象。

* 類加載器分類:
* 啟動類加載器(Bootstrap ClassLoader),是虛擬機自身的一部分,用來加載Java\_HOME/lib/目錄中的,或者被 -Xbootclasspath 參數所指定的路徑中并且被虛擬機識別的類庫;
* 其他類加載器:
* 擴展類加載器(Extension ClassLoader):負責加載\\lib\\ext目錄或Java. ext. dirs系統變量指定的路徑中的所有類庫;
* 應用程序類加載器(Application ClassLoader)。負責加載用戶類路徑(classpath)上的指定類庫,我們可以直接使用這個類加載器。一般情況,如果我們沒有自定義類加載器默認就是用這個加載器。
* 雙親委派模型:如果一個類加載器收到了類加載的請求,它首先不會自己去加載這個類,而是把這個請求委派給父類加載器去完成,每一層的類加載器都是如此,這樣所有的加載請求都會被傳送到頂層的啟動類加載器中,只有當父加載無法完成加載請求(它的搜索范圍中沒找到所需的類)時,子加載器才會嘗試去加載類。
* 總結就是:`當一個類收到了類加載請求時,不會自己先去加載這個類,而是將其委派給父類,由父類去加載,如果此時父類不能加載,反饋給子類,由子類去完成類的加載。`
## JVM調優
### JVM 調優的參數可以在那設置參數值
* 可以在IDEA,Eclipse,工具里設置
* 如果上線了是WAR包的話可以在Tomcat設置
* 如果是Jar包直接 :java -jar 是直接插入JVM命令就好了
~~~
java -Xms1024m -Xmx1024m ...等等等 JVM參數 -jar springboot_app.jar &
復制代碼
~~~
### 說一下 JVM 調優的工具?
* JDK 自帶了很多監控工具,都位于 JDK 的 bin 目錄下,其中最常用的是 jconsole 和 jvisualvm 這兩款視圖監控工具。
* jconsole:用于對 JVM 中的內存、線程和類等進行監控; 
* jvisualvm:JDK 自帶的全能分析工具,可以分析:內存快照、線程快照、程序死鎖、監控內存的變化、gc 變化等。 
### 常用的 JVM 調優的參數都有哪些?
~~~
#常用的設置
-Xms:初始堆大小,JVM 啟動的時候,給定堆空間大小。
-Xmx:最大堆大小,JVM 運行過程中,如果初始堆空間不足的時候,最大可以擴展到多少。
-Xmn:設置堆中年輕代大小。整個堆大小=年輕代大小+年老代大小+持久代大小。
-XX:NewSize=n 設置年輕代初始化大小大小
-XX:MaxNewSize=n 設置年輕代最大值
-XX:NewRatio=n 設置年輕代和年老代的比值。如: -XX:NewRatio=3,表示年輕代與年老代比值為 1:3,年輕代占整個年輕代+年老代和的 1/4
-XX:SurvivorRatio=n 年輕代中 Eden 區與兩個 Survivor 區的比值。注意 Survivor 區有兩個。8表示兩個Survivor :eden=2:8 ,即一個Survivor占年輕代的1/10,默認就為8
-Xss:設置每個線程的堆棧大小。JDK5后每個線程 Java 棧大小為 1M,以前每個線程堆棧大小為 256K。
-XX:ThreadStackSize=n 線程堆棧大小
-XX:PermSize=n 設置持久代初始值
-XX:MaxPermSize=n 設置持久代大小
-XX:MaxTenuringThreshold=n 設置年輕帶垃圾對象最大年齡。如果設置為 0 的話,則年輕代對象不經過 Survivor 區,直接進入年老代。
#下面是一些不常用的
-XX:LargePageSizeInBytes=n 設置堆內存的內存頁大小
-XX:+UseFastAccessorMethods 優化原始類型的getter方法性能
-XX:+DisableExplicitGC 禁止在運行期顯式地調用System.gc(),默認啟用
-XX:+AggressiveOpts 是否啟用JVM開發團隊最新的調優成果。例如編譯優化,偏向鎖,并行年老代收集等,jdk6紙之后默認啟動
-XX:+UseBiasedLocking 是否啟用偏向鎖,JDK6默認啟用
-Xnoclassgc 是否禁用垃圾回收
-XX:+UseThreadPriorities 使用本地線程的優先級,默認啟用
等等等......
復制代碼
~~~
### JVM的GC收集器設置
* \-xx:+Use xxx GC
* xxx 代表垃圾收集器名稱
~~~
-XX:+UseSerialGC:設置串行收集器,年輕帶收集器
-XX:+UseParNewGC:設置年輕代為并行收集。可與 CMS 收集同時使用。JDK5.0 以上,JVM 會根據系統配置自行設置,所以無需再設置此值。
-XX:+UseParallelGC:設置并行收集器,目標是目標是達到可控制的吞吐量
-XX:+UseParallelOldGC:設置并行年老代收集器,JDK6.0 支持對年老代并行收集。
-XX:+UseConcMarkSweepGC:設置年老代并發收集器
-XX:+UseG1GC:設置 G1 收集器,JDK1.9默認垃圾收集器
復制代碼
~~~
- 常見面試題
- 一.Java常見面試題
- 1.Java基礎
- 3.面向對象概念
- 10.Java面試題
- Java基礎知識面試題(總結最全面的面試題)
- 設計模式面試題(總結最全面的面試題)
- Java集合面試題(總結最全面的面試題)
- JavaIO、BIO、NIO、AIO、Netty面試題(總結最全面的面試題)
- Java并發編程面試題(總結最全面的面試題)
- Java異常面試題(總結最全面的面試題)
- Java虛擬機(JVM)面試題(總結最全面的面試題)
- Spring面試題(總結最全面的面試題)
- Spring MVC面試題(總結最全面的面試題)
- Spring Boot面試題(總結最全面的面試題)
- Spring Cloud面試題(總結最全面的面試題)
- Redis面試題(總結最全面的面試題)
- MyBatis面試題(總結最全面的面試題)
- TCP、UDP、Socket、HTTP面試題(總結最全面的面試題)
- 二、MySQL面試題
- 1.基礎部分
- MySQL面試題(總結最全面的面試題)
- HBase相關面試題整理
- Nginx面試題(總結最全面的面試題)
- RabbitMQ面試題(總結最全面的面試題)
- Dubbo面試題(總結最全面的面試題)
- ZooKeeper面試題(總結最全面的面試題)
- Tomcat面試題(總結最全面的面試題)
- Linux面試題(總結最全面的面試題)
- 超詳細的Django面試題
- SSM面試題
- 15個高頻微信小程序面試題
- VUE面試題
- Python面試題
- 二、常見問題解答列表
- 1.查看端口及殺死進程
- 三、學習電子書