1 記憶體結構
1、簡述一下JVM的記憶體結構?(高頻)
JVM在執行Java程式時,會把它管理的記憶體劃分為若干個的區域,每個區域都有自己的用途和創建銷毀時間,如下圖所示,可以分為兩大部分,執行緒私有區和共享區,

執行緒私有區:
① 程式計數器
- 作用:是一塊較小的記憶體空間,可以理解為是當前執行緒所執行程式的位元組碼檔案的行號指示器,存盤的是當前執行緒所執行的行號
- 特點:執行緒私有 ,唯一一個不會出現記憶體溢位的記憶體空間
② 虛擬機堆疊
- 作用:管理JAVA方法執行的記憶體模型,每個方法執行時都會創建一個堆疊楨來存盤方法中變數的變數表、運算元堆疊、動態鏈接方法、回傳值、回傳地址等資訊,堆疊的大小決定了方法呼叫的可達深度(遞回多少層次,或嵌套呼叫多少層其他方法,-Xss引數可以設定虛擬機堆疊大小)

-
特點:
1、執行緒私有
2、區域變數表存放了編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)以及物件參考(reference 型別)
3、堆疊太小或者方法呼叫過深,都將拋出StackOverflowError例外
-
測驗代碼
public class StackDemo02 {
// 記錄呼叫了多少次出現了堆疊記憶體溢位
private static int count = 0 ;
// 入口方法
public static void main(String[] args) {
try {
show() ;
}catch (Throwable e) {
e.printStackTrace();
}
System.out.println("show方法被呼叫了:" + count + "次");
}
// 測驗方法
public static void show() {
count++ ;
System.out.println("show方法執行了.....");
show();
}
}
配置虛擬機引數-Xss可以指定堆疊記憶體大小;例如:-Xss180k
堆疊記憶體的默認值問題:
The default value depends on the platform:
* Linux/x64 (64-bit): 1024 KB
* macOS (64-bit): 1024 KB
* Oracle Solaris/x64 (64-bit): 1024 KB
* Windows: The default value depends on virtual memory
③ 本地方法堆疊:與虛擬機堆疊作用相似,但它不是為Java方法服務的,而是本地方法(C語言),由于規范對這塊沒有強制要求,不同虛擬機實作方法不同,
執行緒共享區:
① 堆記憶體
- 作用:是Java記憶體區域中一塊用來存放物件實體的區域,新創建的物件,陣列都使用堆記憶體;【從Java7開始,常量池也會使用堆記憶體】
-------------------------------------------------------- |
Java 堆從GC的角度還可以細分為: 新生代( Eden區 、From Survivor區和 To Survivor區 )和老年代,
-
特點:
1、被執行緒共享,因此需要考慮執行緒安全問題
2、會產生記憶體溢位問題
-
測驗代碼:
public class HeapDemo01 {
public static void main(String[] args) {
// 定義一個變數
int count = 0 ;
// 創建一個ArrayList物件
ArrayList arrayList = new ArrayList() ;
try {
while(true) {
arrayList.add(new Object()) ;
count++ ;
}
}catch (Throwable a) {
a.printStackTrace();
// 輸出程式執行的次數
System.out.println("總共執行了:" + count + "次");
}
}
}
- 虛擬機引數:
? -Xms 設定最小堆記憶體大小(不能小于1024K); -Xms 堆記憶體初始大小,可以通過jmap工具進行查看
? -Xmx 設定最大堆記憶體大小(不能小于1024K); -Xmx 堆記憶體最大值,可以通過jmap工具進行查看
? 例如:-Xms1024K -Xmx2048K
注意:

② 方法區
-
作用:它用于存盤已被虛擬機加載的類資訊、常量、靜態變數、即時編譯器編譯后的代碼等資料
-
特點:
1、方法區是一塊執行緒共享的記憶體區域
2、方法區的大小決定了系統可以保存多少個類,如果系統定義了太多的類,導致方法區溢位,虛擬機同樣會拋出記憶體溢位的錯誤
3、jdk1.6和jdk1.7方法區也常常被稱之為永久區(永久代),大小一般都是幾百兆;
4、jdk1.8已經將方法區取消,替代的是元資料區(元空間),如果不指定大小,默認情況下,虛擬機會耗盡可用系統記憶體
5、jdk7以后就將方法區中的常量池移動至堆記憶體

? 變化的原因:
? 1、提高記憶體的回收效率(方法區記憶體的回收效率遠遠低于堆記憶體,因為方法去中存盤的都是類資訊,靜態變數...這些資訊不能被輕易回收)
? 2、字串常量池在方法區,那么很容易產生記憶體溢位(因為方法區的垃圾回收效率比較低);
- 測驗代碼
/**
jdk1.8的元資料區可以使用引數-XX:MaxMetaspaceSzie設定大小
* 演示元空間記憶體溢位
* -XX:-UseCompressedClassPointers -XX:MaxMetaspaceSize=10m
UseCompressedClassPointers使用指標壓縮,如果不使用這個引數可能會出現: Compressed class space記憶體溢位
*/
public class MaxMetaspaceDemo extends ClassLoader { // 當前這個類就是一個類加載器
public static void main(String[] args) {
// 定義變數,記錄程式產生類的個數
int j = 0;
try {
MaxMetaspaceDemo test = new MaxMetaspaceDemo();
for (int i = 0; i < 10000; i++, j++) {
// 位元組碼寫入器
ClassWriter cw = new ClassWriter(0);
// 定義一個類版本為Opcodes.V1_1,它的訪問域為public,名稱為Class{i},父類為java.lang.Object,不實作任何介面
cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
byte[] code = cw.toByteArray();
// 加載該類
test.defineClass("Class" + i, code, 0, code.length);
}
} finally {
System.out.println(j);
}
}
}
2、堆和堆疊的區別?(高頻)
① 功能不同:堆疊記憶體用來存盤區域變數和方法呼叫,而堆記憶體用來存盤Java中的物件,無論是成員變數,區域變數,還是類變數,它們指向的物件都存盤在堆記憶體中,
② 共享性不同:堆疊記憶體是執行緒私有的,堆記憶體是所有執行緒共有的,
③ 異常錯誤不同:如果堆疊記憶體或者堆記憶體不足都會拋出例外,堆疊空間不足:java.lang.StackOverFlowError,堆空間不足:
java.lang.OutOfMemoryError,
④ 空間大小:堆疊的空間大小遠遠小于堆的,
3、怎么獲取Java程式使用的記憶體?堆使用的百分比?
可以通過java.lang.Runtime類中與記憶體相關方法來獲取剩余的記憶體,總記憶體及最大堆記憶體,通過這些方法你也可以獲取到堆使用的百分比及堆記憶體的剩余空間,
1、Runtime.freeMemory() 方法回傳剩余空間的位元組數
2、Runtime.totalMemory()方法總記憶體的位元組數
4、堆疊幀都有哪些資料?
堆疊幀包含:區域變數表、運算元堆疊、動態連接、回傳值、回傳地址等,
5、如何啟動系統的時候設定jvm的啟動引數?
其實都很簡單,比如說采用"java -jar"的方式啟動一個jar包里面的系統,那么就可以才用類似下面的格式:

2 垃圾回收
6、如何判斷一個物件是否為垃圾?(高頻)
兩種演算法:
① 參考計數法:堆中每個物件實體都有一個參考計數,當一個物件被創建時,且將該物件實體分配給一個變數,該變數計數設定為1,當任何其它變數被賦值為這個物件的參考時,計數加1(a = b,則b參考的物件實體的計數器+1),但當一個物件實體的某個參考超過了生命周期或者被設定為一個新值時,物件實體的參考計數器減1,任何參考計數器為0的物件實體可以被當作垃圾收集,
特點:簡單、無法解決回圈參考問題
定義學生類:
public class Student {
// 定義成員變數
public Object instance ;
}
撰寫測驗類:
/*
jvm引數:-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-verbose:gc -XX:+PrintGCDetails:列印gc日志資訊
-XX:+PrintGCTimeStamps: 列印gc日志的時間戳
*/
public class ReferenceCountGcDemo {
public static void main(String[] args) {
// 創建Student物件
Student a = new Student() ;
Student b = new Student() ;
// 進行回圈參考
a.instance = b ;
b.instance = a ;
// 將a物件和b物件設定為null
a = null ;
b = null ;
// 呼叫System.gc進行垃圾回收
System.gc(); // 如果沒有觸發垃圾回收說明Hotspot的jvm使用的就是參考計數法來判斷物件是否為垃圾
}
}
控制臺輸出gc日志:
0.076: [GC (System.gc()) [PSYoungGen: 7802K->856K(151552K)] 7802K->864K(498688K), 0.0008493 secs] [Times: user=0.17 sys=0.02, real=0.00 secs]
0.077: [Full GC (System.gc()) [PSYoungGen: 856K->0K(151552K)] [ParOldGen: 8K->620K(347136K)] 864K->620K(498688K), [Metaspace: 3356K->3356K(1056768K)], 0.0044768 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 151552K, used 3901K [0x0000000716c00000, 0x0000000721500000, 0x00000007c0000000)
eden space 130048K, 3% used [0x0000000716c00000,0x0000000716fcf748,0x000000071eb00000)
from space 21504K, 0% used [0x000000071eb00000,0x000000071eb00000,0x0000000720000000)
to space 21504K, 0% used [0x0000000720000000,0x0000000720000000,0x0000000721500000)
ParOldGen total 347136K, used 620K [0x00000005c4400000, 0x00000005d9700000, 0x0000000716c00000)
object space 347136K, 0% used [0x00000005c4400000,0x00000005c449b318,0x00000005d9700000)
Metaspace used 3365K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 370K, capacity 388K, committed 512K, reserved 1048576K
① 0.076: 代表gc發生的時間,從jvm啟動以來經過的秒數
② [GC和[Full Gc: 說明這次垃圾收集器的停頓型別,而不是用來區分新生代GC還是老年代GC的,如果有"Full",說明此次GC發生了stop-the-world,System.gc()是說明顯示的呼叫了 System.gc方法進行垃圾回收
③ [PSYoungGen:表示GC發生的區域, 不同的垃圾收集器展示的區域名稱不一樣,PSYoungGen表示的是新生代,這里默認使用的是Parallel Scavenge收集器 (-XX:+UseSerialGC)
④ 7802K->856K(151552K):GC前該區域已使用容量 -> GC后該區域已使用容量(該區域的總容量)
⑤ 7802K->864K(498688K):GC前Java堆已使用容量 -> GC后Java堆已使用容量(Java堆總容量)
⑥ 0.0008493 secs:該區域GC所占用的時間
⑦ [Times: user=0.17 sys=0.02, real=0.00 secs]: 分別表示用戶態消耗的CPU時間、內核態消耗的CPU時間和操作從開始到結束所經過的墻鐘時間(墻鐘時間包括非運算的等待耗時),多執行緒操作會疊加這些CPU時間,所以user、sys時間超過real時間是完全正常的,
② 可達性分析演算法 : 可達性分析演算法又叫做跟搜索法,就是通過一系列的稱之為"GC Roots"的物件作為起始點,從這些節點開始向下搜索,搜索走過的
路徑被稱為(Reference Chain),當一個物件到GC Roots沒有任何參考鏈相連時(即從GC Roots節點到該節點不可達),則證明該物件是不可用的,
(似于葡萄串);

7、可達性演算法中,哪些物件可作為GC Roots物件?(高頻)
可以作為GC ROOTS物件的情況:
1、虛擬機堆疊中參考的物件
2、方法區靜態成員參考的物件
3、方法區常量參考物件
4、本地方法堆疊參考的物件
8、Java中都有哪些參考型別?(高頻)
① 強參考
Java中默認宣告的就是強參考,比如:
Object obj = new Object(); //只要obj還指向Object物件,Object物件就不會被回收
obj = null; //手動置null
只要強參考存在,垃圾回收器將永遠不會回收被參考的物件,哪怕記憶體不足時,JVM也會直接拋出OutOfMemoryError,不會去回收,如果想中斷強參考與物件之間的聯系,可以顯示的將強參考賦值為null,這樣一來,JVM就可以適時的回收物件了
示例:
/**
* JVM引數:-verbose:gc -XX:+PrintGCDetails -Xms10M -Xmx10M -Xmn5M
*/
public class StrongReferenceDemo01 {
private static List<Object> list = new ArrayList<Object>() ;
public static void main(String[] args) {
// 創建物件
for(int x = 0 ; x < 10 ; x++) {
byte[] buff = new byte[1024 * 1024 * 1];
list.add(buff);
}
}
}
② 軟參考
軟參考是用來描述一些非必需但仍有用的物件,在記憶體足夠的時候,軟參考物件不會被回收,只有在記憶體不足時,系統則會回收軟參考物件,如果回收了軟參考物件之后仍然沒有足夠的記憶體,才會拋出記憶體溢位例外,這種特性常常被用來實作快取技術,比如網頁快取,圖片快取等,
在 JDK1.2 之后,用java.lang.ref.SoftReference類來表示軟參考,
示例代碼:
/**
* JVM引數:-verbose:gc -XX:+PrintGCDetails -Xms10M -Xmx10M -Xmn5M
*/
public class SoftReferenceDemo01 {
private static List<Object> list = new ArrayList<>();
public static void main(String[] args) {
// 創建陣列物件
for(int x = 0 ; x < 10 ; x++) {
SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024 * 1024 * 1]) ;
list.add(softReference) ;
}
System.gc(); // 主動通知垃圾回收器進行垃圾回收
for(int i=0; i < list.size(); i++){
Object obj = ((SoftReference) list.get(i)).get();
System.out.println(obj);
}
}
}
我們發現無論回圈創建多少個軟參考物件,列印結果總是有一些為null,這里就說明了在記憶體不足的情況下,軟參考將會被自動回收,
③ 弱參考
弱參考的參考強度比軟參考要更弱一些,無論記憶體是否足夠,只要 JVM 開始進行垃圾回收,那些被弱參考關聯的物件都會被回收,在 JDK1.2之后,用
java.lang.ref.WeakReference來表示弱參考,
示例代碼:
/**
* JVM引數:-verbose:gc -XX:+PrintGCDetails -Xms10M -Xmx10M -Xmn5M
*/
public class WeakReferenceDemo01 {
private static List<Object> list = new ArrayList<>();
public static void main(String[] args) {
// 創建陣列物件
for(int x = 0 ; x < 10 ; x++) {
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1024 * 1024 * 1]) ;
list.add(weakReference) ;
}
System.gc(); // 主動通知垃圾回收器進行垃圾回收
for(int i=0; i < list.size(); i++){
Object obj = ((WeakReference) list.get(i)).get();
System.out.println(obj);
}
}
}
④ 虛參考
虛參考是最弱的一種參考關系,如果一個物件僅持有虛參考,那么它就和沒有任何參考一樣,它隨時可能會被回收,在 JDK1.2 之后,用PhantomReference 類來表示,通過查看這個類的原始碼,發現它只有一個建構式和一個 get() 方法,而且它的 get() 方法僅僅是回傳一個null,也就是說將永遠無法通過虛參考來獲取物件,虛參考必須要和 ReferenceQueue 參考佇列一起使用,
public class PhantomReference<T> extends Reference<T> {
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
/**
* Creates a new phantom reference that refers to the given object and
* is registered with the given queue.
*
* <p> It is possible to create a phantom reference with a <tt>null</tt>
* queue, but such a reference is completely useless: Its <tt>get</tt>
* method will always return null and, since it does not have a queue, it
* will never be enqueued.
*
* @param referent the object the new phantom reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
特點:
1、每次垃圾回收時都會被回收,主要用于監測物件是否已經從記憶體中洗掉
2、虛參考必須和參考佇列關聯使用, 當垃圾回收器準備回收一個物件時,如果發現它還有虛參考,就會把這個虛參考加入到與之關聯的參考佇列中
3、程式可以通過判斷參考佇列中是否已經加入了虛參考,來了解被參考的物件是否將要被垃圾回收,如果程式發現某個虛參考已經被加入到參考佇列,那么就可以在所參考的物件的記憶體被回收之前采取必要的行動
示例代碼:
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
// 創建一個參考佇列
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
// 創建一個虛參考,指向一個Object物件
PhantomReference<Object> phantomReference = new PhantomReference<Object>(new Object(), referenceQueue);
// 主動通知垃圾回收器進行垃圾回收
System.gc();
// 從參考佇列中獲取元素, 該方法是阻塞方法
System.out.println(referenceQueue.remove());
}
}
9、常見的垃圾回收演算法都有哪些?(高頻)
① 標記清除
執行程序:首先標記出所有需要回收的物件,在標記完成后統一回收所有被標記的物件,

優點:速度比較快
缺點:會產生記憶體碎片,碎片過多,仍會使得連續空間少
② 標記整理
執行程序:首先標記出所有需要回收的物件,在標記完成后統一進行整理,整理是指存活物件向一端移動來減少記憶體碎片,相對效率較低

優點:無記憶體碎片
缺點:效率較低
③ 復制演算法
執行程序:開辟兩份大小相等空間,一份空間始終空著,垃圾回收時,將存活物件拷貝進入空閑空間;

優點:無記憶體碎片
缺點:占用空間多
注意:如果有很多物件的存活率較高,這時我們采用復制演算法,那么效率就比較低;
④ 分代回收
概述:根據物件存活周期的不同,將物件劃分為幾塊,比如Java的堆記憶體,分為新生代和老年代,然后根據各個年代的特點采用最合適的演算法;
新生代物件的存活的時間都比較短,因此使用的是【復制演算法】;而老年代物件存活的時間比較長那么采用的就是【標記清除】或者【標記整理】;
10、簡述Java垃圾回識訓制?有什么辦法主動通知虛擬機進行垃圾回收?
在Java中,程式員是不需要顯示的去釋放一個物件的記憶體的,而是由虛擬機自行執行,在JVM中,有一個垃圾回收執行緒,它是低優先級的,在正常情況下是不會執行的,只有在虛擬機空閑或者當前堆記憶體不足時,才會觸發執行,掃面那些沒有被任何參考的物件,并將它們添加到要回收的集合中,進行回收,程式員可以手動執行System.gc(),通知GC運行,但是Java語言規范并不保證GC一定會執行,
3 物件分配
11、物件在記憶體中是如何進行分配的?(高頻)
① 物件優先在Eden分配:物件優先在『伊甸園』分配,當『伊甸園』沒有足夠的空間時,觸發 'Minor GC'(小范圍的GC)
情況一:伊甸園的記憶體空間足夠,不會發生'Minor GC'
情況二:伊甸園的空間不夠了
垃圾回收執行緒啟動,進行垃圾回收,此時會觸發"stop the world"(停止所有用戶執行緒),Eden區中所有存活的物件都會被復制到“To”,而在“From”區中,仍存活的物件會根據他們的年齡值來決定去向,年齡最多到一定值(最大值是15,物件在Survivor區中每熬過一次Minor GC,年齡就會增加1歲)(年齡閾值,可以通過-XX:MaxTenuringThreshold來設定)的物件會被移動到年老代中,沒有達到閾值的物件會被復制到“To”區域,
"From"和"To"會交換他們的角色,下一次垃圾回收的時候也是從Eden將存活的物件復制到TO區
Minor GC會一直重復這樣的程序,直到“To”區被填滿,“To”區被填滿之后,會將所有物件移動到年老代中,
案例演示:
jvm引數設定:
-XX:+UseSerialGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:./gc.log -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
-XX:+UseSerialGC 是指使用 Serial + SerialOld 回收器組合
-XX:+PrintGCDetails -verbose:gc 是指列印 GC 詳細資訊
-XX:+PrintGCTimeStamps 列印gc日志的時間戳
-Xloggc:./gc.log 將gc日志輸出到一個日志檔案中
-Xms20M -Xmx20M -Xmn10M 是指分配給JVM的最小,最大以及新生代記憶體
-XX:SurvivorRatio=8 是指『伊甸園』與『幸存區 From』和『幸存區 To』比例為 8:1:1
定義記憶體大小變數
private static final int _512KB = 512 * 1024;
private static final int _1MB = 1024 * 1024;
private static final int _4MB = 4 * 1024 * 1024;
private static final int _7MB = 7 * 1024 * 1024;
private static final int _8MB = 8 * 1024 * 1024;
案例1:沒有創建陣列物件,看引數運行情況
案例2:創建一個4M的陣列,查看記憶體分配情況
// 創建一個4M大小的陣列
byte[] bytes = new byte[_4MB] ;
Heap
def new generation total 9216K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 100% used [0x00000000fec00000, 0x00000000ff400000, 0x00000000ff400000) // 在伊甸園中創建物件
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
Metaspace used 3444K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 376K, capacity 388K, committed 512K, reserved 1048576K
沒有觸發GC操作,物件直接在Eden分配;
案例3:創建一個7M的陣列,查看記憶體分配情況
// 創建一個7M大小的陣列
byte[] bytes1 = new byte[_7MB] ;
-- 觸發垃圾回收
[GC (Allocation Failure) [DefNew: 2004K->647K(9216K), 0.0023439 secs] 2004K->647K(19456K), 0.0024142 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 7897K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 88% used [0x00000000fec00000, 0x00000000ff314930, 0x00000000ff400000)
from space 1024K, 63% used [0x00000000ff500000, 0x00000000ff5a1e58, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
Metaspace used 3446K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 376K, capacity 388K, committed 512K, reserved 1048576K
由于程式在啟動的時候jdk內部還會存在一些物件的創建,因此當我們分配了一個7M的記憶體空間,eden記憶體不足,因此發生了一次Minor GC!并且將存活下的物件最終存盤到from區中,
案例4: 在案例3的基礎上,在分配一個512KB的陣列記憶體空間
byte[] bytes1 = new byte[_7MB] ;
byte[] bytes2 = new byte[_512KB] ;
[GC (Allocation Failure) [DefNew: 2005K->623K(9216K), 0.0015235 secs] 2005K->623K(19456K), 0.0015799 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 8713K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 98% used [0x00000000fec00000, 0x00000000ff3e6820, 0x00000000ff400000)
from space 1024K, 60% used [0x00000000ff500000, 0x00000000ff59bdb8, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
Metaspace used 3444K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 376K, capacity 388K, committed 512K, reserved 1048576K
觸發一次GC操作!并且將存活下的物件最終存盤到from區中,第二次分配_512KB大小的記憶體空間的時候,直接在伊甸園分配即可,
案例5: 在4的基礎上在分配一個512KB的陣列記憶體空間
byte[] bytes1 = new byte[_7MB] ;
byte[] bytes2 = new byte[_512KB] ;
byte[] bytes3 = new byte[_512KB] ;
[GC (Allocation Failure) [DefNew: 2004K->620K(9216K), 0.0018706 secs] 2004K->620K(19456K), 0.0019275 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 8628K->539K(9216K), 0.0063389 secs] 8628K->8323K(19456K), 0.0063773 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
Heap
def new generation total 9216K, used 1133K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 7% used [0x00000000fec00000, 0x00000000fec94930, 0x00000000ff400000)
from space 1024K, 52% used [0x00000000ff400000, 0x00000000ff486de0, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 7784K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 76% used [0x00000000ff600000, 0x00000000ffd9a040, 0x00000000ffd9a200, 0x0000000100000000)
Metaspace used 3443K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 376K, capacity 388K, committed 512K, reserved 1048576K
觸發了2次垃圾回收!并且將from區中存活的物件存盤到老年代!
② 大物件直接晉升至老年代
當物件太大,伊甸園包括幸存區都存放不下時,這時候老年代的連續空間足夠,此物件會直接晉升至老年代,不會發生 GC
案例演示:
案例1:直接分配一個8M的記憶體空間
byte[] bytes1 = new byte[_8MB] ;
伊甸園總大小只有 8 MB,但新分配的物件大小已經是 8MB,而幸存區都僅有 1MB,也無法容納這個物件
Heap
def new generation total 9216K, used 2169K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee1e560, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 80% used [0x00000000ff600000, 0x00000000ffe00010, 0x00000000ffe00200, 0x0000000100000000)
Metaspace used 3443K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 376K, capacity 388K, committed 512K, reserved 1048576K
可以看到結果并沒有發生 GC,大物件直接被放入了老年代「tenured generation total 10240K, used 8192K」
案例演示2:老年代連續空間不足,觸發 Full GC
byte[] bytes1 = new byte[_8MB] ;
byte[] bytes2 = new byte[_8MB] ;
第一個 8MB 直接進入老年代,第二個 8MB 物件在分配時發現老年代空間不足,只好嘗試先進行一次 Minor GC,結果發現新生代沒有連續空間,只好觸發一次 Full GC,最后發現老年代也沒有連續空間,這時出現 OutOfMemoryError
[GC (Allocation Failure) [DefNew: 2004K->647K(9216K), 0.0022693 secs][Tenured: 8192K->8838K(10240K), 0.0452151 secs] 10197K->8838K(19456K), [Metaspace: 3438K->3438K(1056768K)], 0.0504669 secs] [Times: user=0.00 sys=0.00, real=0.05 secs]
[Full GC (Allocation Failure) [TenuredException in thread "main" : 8838K->8820K(10240K), 0.0027463 secs] 8838K->8820K(19456K), [Metaspace: 3438K->3438K(1056768K)], 0.0027877 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
java.lang.OutOfMemoryError: Java heap space
at com.itheima.jvm.gc.ObjectMemoryDemo.main(ObjectMemoryDemo.java:14)
Heap
def new generation total 9216K, used 246K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 3% used [0x00000000fec00000, 0x00000000fec3d890, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 8820K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 86% used [0x00000000ff600000, 0x00000000ffe9d220, 0x00000000ffe9d400, 0x0000000100000000)
Metaspace used 3470K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 379K, capacity 388K, committed 512K, reserved 1048576K
12、物件是怎么從年輕代進入老年代的?
存在3種情況:
1、如果物件夠老,會通過提升(Promotion)進入老年代,這一般是根據物件的年齡進行判斷的,
2、動態物件年齡判定,有的垃圾回收演算法,比如G1,并不要求age必須達到15才能晉升到老年代,它會使用一些動態的計算方法,
3、超出某個大小的物件將直接在老年代分配,不過這個值默認為0,意思是全部首選Eden區進行分配,
13、簡單描述一下(分代)垃圾回收的程序?(高頻)

分代回收器有兩個磁區:老生代和新生代,新生代默認的空間占比總空間的 1/3,老生代的默認占比是2/3,
新生代使用的是復制演算法,新生代里有3個磁區:Eden、To Survivor、From Survivor,它們的默認占比是 8:1:1,它的執行流程如下:
當年輕代中的Eden區分配滿的時候,就會觸發年輕代的GC(Minor GC),具體程序如下:
1、在Eden區執行了第一次GC之后,存活的物件會被移動到其中一個Survivor磁區(以下簡稱to)
2、From區中的物件根據物件的年齡值決定去向,達到閾值15移動到老年代,沒有達到復制到to區域(復制演算法)
3、在把Eden和to區中的物件清空掉
14、JVM的永久代中會發生垃圾回收么?
永久代會觸發垃圾回收的,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC),
注:Java 8 中已經移除了永久代,新加了一個叫做元資料區(Metaspace)的記憶體區,
4 垃圾收集器
15、常見的垃圾收集器都有哪些?(高頻)
常見的垃圾收集器如下所示:

不同的垃圾收集器,作用的堆記憶體空間是不一樣的;上面的 serial , parnew , Paraller Scavenge 是新生代的垃圾回收器;CMS , Serial Old , Paralle Old是老年代的垃圾收集器 , G1垃圾收集器可以作用于新生代和老年代; 連線表示垃圾收集器可以搭配使用;
① Serial
特點:
-
Serial是一個單執行緒的垃圾收集器
-
"Stop The World",它進行垃圾收集時,必須暫停其他所有的作業執行緒,直到它收集結束,在用戶不可見的情況下把用戶正常作業的執行緒全部停掉,

應用場景: -
使用場景:多用于桌面應用,Client端的垃圾回收器
-
桌面應用記憶體小,進行垃圾回收的時間比較短,只要不頻繁發生停頓就可以接受
Serial Old收集器是Serial的老年代版本和Serial一樣是單執行緒,使用的演算法是"標記-整理"
② ParNew
概述: ParNew 收集器其實就是 Serial 收集器的多執行緒版本

特點:
1、會觸發stop the world
2、多執行緒方式進行垃圾回收
應用場景:它卻是許多運行在 Server 模式下的虛擬機中首選的新生代收集器
注意:如果是單核cpu即使使用該垃圾回收器也無法提高執行效率
③ Parallel Scavenge
概述:Parallel Scavenge 收集器是一個新生代收集器,它也是使用復制演算法的收集器,又是并行的多執行緒收集器
特點:由于與吞吐量關系密切,Parallel Scavenge 收集器也經常稱為“吞吐量優先”收集器
所謂吞吐量就是 CPU 用于運行用戶代碼的時間與 CPU 總消耗時間的比值,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),虛擬機總共運行了 100 分鐘,其中垃圾收集花掉 1 分鐘,那吞吐量就是 99%應用場景: 高吞吐量則可以高效率地利用 CPU 時間,盡快完成程式的運算任務,主要適合在后臺運算而不需要太多互動的任務,
Parallel old收集器Parallel Scavenge收集器的老年代版本,使用多執行緒+標記整理演算法
④ CMS(重點)
概述:CMS (Concurrent Mark Sweep)收集器是-種以獲取最短回收停頓時間為目標的收集器,
特點:
-
CMS 收集器是基于“標記-清除”演算法實作的
-
目前很大一部分的Java應用集中在互聯網站或者B/S系統的服務端上,這類應用尤其重視服務的回應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗,

步驟流程:
- 初始標記(CMS initial mark) -------- 標記一下 GC Roots 能直接關聯到的物件,速度很快(stop the world)
- 并發標記(CMS concurrent mark) -------- 對初始標記標記過的物件,進行trace(進行追蹤,得到所有關聯的物件,進行標記)
- 重新標記(CMS remark) -------- 為了修正并發標記期間因用戶程式導致標記產生變動的標記記錄(stop the world)
- 并發清除(CMS concurrent sweep)
缺點:會產生垃圾碎片
⑤ G1
概述: G1是一個分代的,并行與并發的"標記-整理"垃圾回收器, 它的設計目標是為了適應現在不斷擴大的記憶體和不斷增加的處理器數量,進一步降低暫停時間(pause time),同時兼顧良好的吞吐量,
相比于CMS:
-
G1垃圾回收器使用的是"標記-整理",因此其回收得到的空間是連續的,
-
G1回收器的記憶體與CMS回收器要求的記憶體模型有極大的不同,G1將記憶體劃分一個個固定大小的region,每個region可以是年輕代、老年代的一個,記憶體的回收是以region作為基本單位的;

16、你都用過G1垃圾回收器的哪幾個重要引數?
① -XX:MaxGCPauseMillis
暫停時間,默認值200ms,這是一個軟性目標,G1會盡量達成,如果達不成,會逐漸做自我調整,
② -XX:G1HeapRegionSize
Region大小,若未指定則默認最多生成2048塊,每塊的大小需要為2的冪次方,如1,2,4,8,16,32,最大值為32M,
③ -XX:G1NewSizePercent 和 -XX:G1MaxNewSizePercent
新生代比例有兩個數值指定,下限:-XX:G1NewSizePercent,默認值5%,上限:-XX:G1MaxNewSizePercent,默認值60%,
17、串行(serial)收集器和吞吐量(throughput)收集器的應用場景?
吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等規模和大規模資料的應用程式, 而串行收集器對大多數的小應用(在現代處理器上需要大概100M 左右的記憶體)就足夠了,
18、生產上如何配置垃圾收集器的?

1、首先是記憶體大小問題,基本上每一個記憶體區域我都會設定一個上限,來避免溢位問題,比如元空間,通常,堆空間我會設定成作業系統的2/3(這是想給其他行程和作業系統預留一些時間),超過8GB的堆優先選用G1,
2、接下來,我會對JVM進行初步優化,比如根據老年代的物件提升速度,來調整年輕代和老年代之間的比例,
3、再接下來,就是專項優化,主要判斷的依據就是系統容量、訪問延遲、吞吐量等,我們的服務是高并發的,所以對STW的時間非常敏感,我會通過記錄詳細的GC日志,來找到這個瓶頸點,借用gceasy(重點)https://gceasy.io/這樣的日志分析工具,很容易定位到問題,之所以選擇采用工具,是因為gc日志看起來實在是太麻煩了,gceasy號稱是AI學習分析問題,可視化做的較好,

5 類加載器
19、什么是類加載器,類加載器有哪些?(高頻)
類加載器的作用:負載將的class檔案加載到java虛擬機中,并為之創建一個Class物件
從Java虛擬機的角度來講,只存在如下兩種不同的類加載器:
- 啟動類加載器(Bootstrap ClassLoader), 這個類加載器使用C++語言實作,是虛擬機自身的一部分
- 其他類加載器,這些類加載器都由Java語言實作,獨立于虛擬機外部,并且全部都繼承自抽象類(java.lang.ClassLoader)
從Java開發人員的角度來講,類加載器還可以劃分的更細致一下,絕大部分Java程式都會使用到以下3種系統提供的類加載器:
-
啟動類加載器(Bootstrap class loader):它是虛擬機的內置類加載器,通過表示為null
-
平臺類加載器(Platform class loader) :它是平臺類加載器; 負責加載JDK中一些特殊的模塊;
-
系統類加載器(System class loader) :它也被稱為應用程式類加載器, 它負責加載用戶類路徑上所指定的類別庫,一般情況下這個就是程式中默
認的類加載器
20、Java的雙親委托機制是什么?(高頻)
概述
我們的應用程式都是由這三種類加載器互相配合進行加載的,如果有必要,還可以加入自定義的類加載器,這些類加載器之間的層次關系一般會如下圖所示:

上圖所展示的類加載器之間的這種層次關系,就稱之為類加載器的雙親委派模型,雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應該有自己的父類加載器,這里的類加載器的父子關系不是真正物理意義上的繼承,而是邏輯上的繼承,
作業程序
雙親委派模型的作業程序是:如果一個類加載器收到了類加載的請求,它首先不會自己嘗試加載這個類,而是把這請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳說到頂層的啟動類加載器中,只有當父類加載器回傳自己無法完成這個加載請求(它的搜索回傳中沒有找到所需的類)時,子類加載器才會嘗試自己去加載,
6 性能調優
21、調優命令有哪些?
1、jps,JVM Process Status Tool顯示指定系統內所有的HotSpot虛擬機行程,
2、jstat,JVM statistics Monitoring是用于監視虛擬機運行時狀態資訊的命令,它可以顯示出虛擬機行程中的類裝載、記憶體、垃圾收集、JIT編譯等運
行資料,
查詢幫助檔案:jstat -options


3、jmap,JVM Memory Map命令用于查看堆記憶體的分配情況以及生成heap dump檔案
查詢幫助檔案:jmap -h
示例1:jmap -heap 33193 查詢堆記憶體的分配情況
示例2:jmap -dump:format=b,file=thread-cup.log 33193
4、jhat,JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的
分析結果后,可以在瀏覽器中查看
查詢幫助檔案:
jhat -h
示例:jhat -J-Xmx512M thread-cup.log

5、jstack,用于生成java虛擬機當前時刻的執行緒快照,
查看幫助檔案:jstack -h
示例:jstack -l 33193
6、jinfo,JVM Configuration info 這個命令作用是實時查看和調整虛擬機運行引數,
查看幫助檔案:jinfo -h
示例:jinfo -flags 33193

22、你知道哪些JVM性能調優引數?(高頻)
1、設定堆記憶體大小:
? -Xms 設定最小堆記憶體大小(不能小于1024K); -Xms 堆記憶體初始大小,可以通過jmap工具進行查看
? -Xmx 設定最大堆記憶體大小(不能小于1024K); -Xmx 堆記憶體最大值,可以通過jmap工具進行查看
2、設定新生代大小:
? -XX:NewSize:新生代大小
? -XX:NewRatio 新生代和老生代占比
3、-XX:SurvivorRatio:伊甸園空間和幸存者空間的占比
4、設定垃圾回收器
? 年輕代用 -XX:+UseParNewGC
? 年老代用-XX:+UseConcMarkSweepGC
23、你用過哪些性能調優工具?(高頻)
常用調優工具分為兩類
1、jdk自帶監控工具
-
jconsole,Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制臺,用于對JVM中記憶體,執行緒和類等的監控
-
jvisualvm,jdk自帶全能工具,可以分析記憶體快照、執行緒快照;監控記憶體變化、GC變化等,
2、第三方
-
MAT,Memory Analyzer Tool,一個基于Eclipse的記憶體分析工具,是一個快速、功能豐富的Java heap分析工具,它可以幫助我們查找記憶體泄漏和減少記憶體消耗
-
GChisto,一款專業分析gc日志的工具
24、你都有哪些手段用來排查記憶體溢位?(高頻)
記憶體溢位包含很多種情況,我在平常作業中遇到最多的就是堆溢位,有一次線上遇到故障,重新啟動后,使用jstat命令,發現Old區在一直增長,我使用jmap命令,匯出了一份線上堆疊,然后使用MAT進行分析,通過對GC Roots的分析,我發現了一個非常大的HashMap物件,這個原本是有位同學做快取用的,但是一個無界快取,造成了堆記憶體占用一直上升,后來,將這個快取改成 Guava Cache,并設定了弱參考,故障就消失了,
本文來自博客園,作者:{Orator-xy},轉載請注明原文鏈接:{https://www.cnblogs.com/xy1857/}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/498690.html
標籤:Java
上一篇:IO流詳解
下一篇:GUI編程詳解
