2019 Java 底層面試題下半場

2019 Java 底層面試題上半場(第一篇) - 簡書

2019 Java 底層面試題上半場(第二篇) - 簡書


JVM底層原理



JVM垃圾回收的時候如何確定垃圾?

? ? 內(nèi)存中已經(jīng)不再被使用到的空間就是垃圾


JVM常見的垃圾回收算法

引用計數(shù)

有對象引用+1,沒對象引用-1,到0為止說明回收

缺點(diǎn):?

? ? 1)每次對對象賦值時均要維護(hù)引用計數(shù)器,且計數(shù)器本身也有一定消耗。

? ? 2)較難處理循環(huán)引用

JVM的實(shí)現(xiàn)一般不采用這種方式

復(fù)制算法

Java堆從GC的角度還可以細(xì)分為:新生代(Eden區(qū),F(xiàn)rom Survivor區(qū)和To Survivor區(qū))和老年代(Old區(qū))

步驟:

????1)eden,SurvivorFrom復(fù)制到SurvivorTo,年齡+1

? ? ? ? 首先,當(dāng)Eden區(qū)滿的時候會觸發(fā)第一次GC,把還活著的對象拷貝到SurvivorFrom區(qū),當(dāng)Eden區(qū)再次觸發(fā)GC的時候會掃描Eden區(qū)和From區(qū)域,對這兩個區(qū)域進(jìn)行垃圾回收,經(jīng)過這次回收后還存活的對象則直接復(fù)制到To區(qū)(如果有對象的年齡已經(jīng)達(dá)到了老年的標(biāo)準(zhǔn),則賦值到老年代區(qū)),同時把這些對象的年齡+1

? ? 2)清空eden,SurvivorFrom

? ? ? ? 然后清空Eden和SurvivorFrom中的對象,也即復(fù)制之后有交換,誰空誰是To

? ? 3)SuvivorTo和SurvivorFrom互換

? ? ? ? 最后SurvivorTo和SurvivorFrom互換,原SurvivorTo成為下一次GC時的SurvivorFrom區(qū),部分對象會在From和To區(qū)域中復(fù)制來復(fù)制去,如此交換15次(由JVM參數(shù)MaxTenuringThreshold決定,這個參數(shù)默認(rèn)是15),最終如果還是存活就存入到老年代

標(biāo)記清除

分成標(biāo)記和清楚兩個階段,先標(biāo)記出要回收的對象,然后統(tǒng)一回收這些對象

缺點(diǎn)

? ? 1)會導(dǎo)致內(nèi)存碎片

標(biāo)記整理

與標(biāo)記清楚一樣,再次掃描后往一端滑動存活對象

缺點(diǎn):

? ? 1)移動對象需要成本


如何判斷一個對象是否可以被回收?

? ? 1)上面講的引用計數(shù)法

? ? 2)枚舉根節(jié)點(diǎn)做可達(dá)性分析(根搜索路徑)

? ? ? ? 為了解決引用計數(shù)法的循環(huán)引用問題,Java使用了可達(dá)性分析的方法。

? ? ? ? GC Roots 就是一組必須活躍的引用。基本思路就是通過一系列名為GC Roots的對象作為起始點(diǎn),從這個被稱為GC Roots的對象開始向下搜索,如果一個對象到GC Roots沒有任何引用鏈相連時,則說明此對象不可用。


Java中可以作為GC Roots的對象?

? ? 1)虛擬機(jī)棧(棧幀中的局部變量區(qū),也叫局部變量表)中引用的對象。

? ? 2)方法區(qū)中的類靜態(tài)屬性引用的對象。

? ? 3)方法區(qū)中常量引用的對象。

? ? 4)本地方法棧中JNI(Native方法)引用的對象。


JVM的參數(shù)類型

標(biāo)配參數(shù)

? ? java -version

? ? java -help

? ? java -showversion

x參數(shù)

? ? -Xint ?? 解釋執(zhí)行

? ? -Xcomp ?? 第一次使用就編譯成本地代碼

? ? -Xmixed ?? 混合模式

xx參數(shù)(重點(diǎn))

????Boolean類型

? ? ? ? 公式:

????????????-XX:+ 或者 - 某個屬性值

????????????+ 表示開啟 - 表示關(guān)閉

? ? ? ? 例子:

? ? ? ? ? ? 是否打印GC收集細(xì)節(jié):

????????????????-XX:-PrintGCDetails

????????????????-XX:+PrintGCDetails

? ? ? ? ? ? 是否使用串行垃圾回收器:

? ? ? ? ? ? ? ? -XX:-UseSerialGC

????????????????-XX:+UseSerialGC

????KV設(shè)值類型

? ? ? ? 公式:

? ? ? ? ? ? -XX:屬性key=屬性值value

? ? ? ? 例子:

? ? ? ? ? ? 元空間大?。?/p>

??????????????? -XX:MetaspaceSize=128M

? ? ? ? ? ? 年齡最大閾值(默認(rèn)15)達(dá)到后進(jìn)入年老代

? ? ? ? ? ? ? ? -XX:MaxTenuringThreshold=15

如何查看一個正在運(yùn)行中的Java程序?它的某個jvm參數(shù)是否開啟,具體值是多少?談?wù)?Xms和-Xmx?

jps -l ?? 查看運(yùn)行中的Java程序

jinfo -flag PrintGCDetails 29833 ?? 查看29833進(jìn)程的PrintGCDetails參數(shù)狀態(tài)

查看JVM所有出廠初始值?

java -XX:+PrintFlagsInitial

查看Xmx默認(rèn)值?

java -XX:+PrintFlagsFinal -version | grep MaxHeapSize

查看JVM修改過和更新過的內(nèi)容?

java -XX:+PrintFlagsFinal

= 等號表示初始值

:= 冒號等號表示JVM修改過或人為修改過后的值

查看JVM默認(rèn)垃圾回收器?

java -XX:+PrintCommandLineFlags


JVM的常用調(diào)優(yōu)參數(shù)?

-Xms ?? 初始堆的大小,也是堆大小的最小值,默認(rèn)值是總共的物理內(nèi)存的1/64。等價于-XX:InitialHeapSize

-Xmx ?? 最大分配內(nèi)存,默認(rèn)為物理內(nèi)存1/4。等價于-XX:MaxHeapSize

-Xss ?? 設(shè)置單個線程棧的大小,默認(rèn)1M,等價于-XX:ThreadStackSize

-Xmm ?? 設(shè)置年輕代大小

-XX:MetaspaceSize ?? 元空間大小

-XX:+PrintGCDetails

-XX:SurvivorRatio

-XX:NewRatio

-XX:MaxTenuringThreshold


JVM的-XX:PrintGCDetails參數(shù)?

-XX:PrintGCDetails ?? 輸出GC的詳細(xì)收集日志信息

GC:

Full GC:


JVM的-XX:SurvivorRatio、-XX:NewRatio、-XX:MaxTenuringThreshold參數(shù)?

-XX:SurvivorRatio

-XX:SurvivorRatio ?? 設(shè)置新生代中eden和S0/S1空間的比例

默認(rèn):

-XX:SurvivorRatio=8、Eden:S0:S1=8:1:1

假如:

-XX:SurvivorRatio=4、Eden:S0:S1=4:1:1

SurvivorRatio值就是設(shè)置eden區(qū)的比例占多少,S0/S1相同

-XX:NewRatio

-XX:NewRatio ?? 配置年輕代與老年代在堆結(jié)構(gòu)的占比

默認(rèn):

-XX:NewRatio=2????新生代占1,老年代2,年輕代占整個堆的1/3

假如:

-XX:NewRatio=4 ?? 新生代占1,老年代4,年輕代占整個堆的1/5

NewRatio值就是設(shè)置老年代的占比,剩下的1給新生代

-XX:MaxTenuringThreshold?

-XX:MaxTenuringThreshold ?? 設(shè)置垃圾最大年齡,默認(rèn)15,如果設(shè)置為0的話則年輕代對象不經(jīng)過Survivor區(qū),直接進(jìn)入年老代。對于年老代比較多的應(yīng)用,可以提高效率。如果將此值設(shè)置為一個較大值,則年輕代對象會在Survivor區(qū)進(jìn)行多次復(fù)制,這樣可以增加對象在年輕代的存活時間,增加在年輕代即被回收的概論。


強(qiáng)引用、軟引用、弱引用、虛引用分別是什么?

強(qiáng)引用:

當(dāng)內(nèi)存不足,JVM開始垃圾回收,對于強(qiáng)引用的對象,就算是出現(xiàn)了OOM(內(nèi)存泄漏)也不會對該對象進(jìn)行回收,即使對象以后永遠(yuǎn)都不會被用到JVM也不會回收。

對與一個普通的對象,如果沒有其他引用關(guān)系,或者顯式地將相應(yīng)的強(qiáng)引用賦值為null,一般認(rèn)為就是可以被垃圾收集的了。

強(qiáng)引用案例:

public static void main(String[] args) {

? ? ? ? Object obj1 = new Object(); //這樣定義的默認(rèn)引用就是強(qiáng)引用

? ? ? ? Object obj2 = obj1; //obj2引用賦值

? ? ? ? obj1 = null;? ? //置空

? ? ? ? System.gc();

? ? ? ? System.out.println(obj2);

? ? }

軟引用:

軟應(yīng)用需要用java.lang.ref.SoftReference類來實(shí)現(xiàn)。

當(dāng)系統(tǒng)內(nèi)存充足時它不會被回收,當(dāng)系統(tǒng)內(nèi)存不足時它會被回收。

軟引用通常用在對內(nèi)存敏感的程序中,比如高速緩存就有用到軟引用,內(nèi)存夠用的時候就保留,不夠用就回收。

軟引用案例:

public class SoftReferenceDemo {

? ? /**

? ? * 內(nèi)存夠用的時候就保留,不夠用就回收

? ? */

? ? public static void softRef_Memory_Enough(){

? ? ? ? Object o1 = new Object();

? ? ? ? SoftReference<Object> softReference = new SoftReference<Object>(o1);

? ? ? ? System.out.println(o1);

? ? ? ? System.out.println(softReference.get());

? ? ? ? o1 = null;

? ? ? ? System.gc();

? ? ? ? System.out.println(o1);

? ? ? ? System.out.println(softReference.get());

? ? }

? ? /**

? ? * JVM配置,故意產(chǎn)生大對象并配置小的內(nèi)存,讓它內(nèi)存不夠用了導(dǎo)致OOM,看軟引用的回收情況

? ? * -Xms5m? -Xmx5m? -XX:+PrintGCDetails

? ? */

? ? public static void softRef_Memory_NotEnough(){

? ? ? ? Object o1 = new Object();

? ? ? ? SoftReference<Object> softReference = new SoftReference<Object>(o1);

? ? ? ? System.out.println(o1);

? ? ? ? System.out.println(softReference.get());

? ? ? ? o1 = null;

? ? ? ? try {

? ? ? ? ? ? byte[] bytes = new byte[30 * 1024 * 1024];

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? } finally {

? ? ? ? ? ? System.out.println(o1);

? ? ? ? ? ? System.out.println(softReference.get());

? ? ? ? }

? ? }

? ? public static void main(String[] args) {

? ? ? ? //softRef_Memory_Enough();

? ? ? ? softRef_Memory_NotEnough();

? ? }

}

弱引用:

弱引用需要用java.lang.ref.WeakReference類來實(shí)現(xiàn),它比軟引用的生存期更短。

對于只有弱引用的對象來說,只要垃圾回收機(jī)制一運(yùn)行,不管JVM的內(nèi)存空間是否足夠,都會回收該對象占用的內(nèi)存。

弱引用案例:

public static void main(String[] args) {

? ? ? ? Object o1 = new Object();

? ? ? ? WeakReference<Object> weakReference = new WeakReference<>(o1);

? ? ? ? System.out.println(o1);

? ? ? ? System.out.println(weakReference.get());

? ? ? ? o1 = null;

? ? ? ? System.gc();

? ? ? ? System.out.println(o1);

? ? ? ? System.out.println(weakReference.get());

? ? }

虛引用:

虛引用需要用java.lang.ref.PhantomReference類來實(shí)現(xiàn)。

如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收,他不能單獨(dú)使用也不能通過它訪問對象,虛引用必須和引用隊(duì)列(ReferenceQueue)聯(lián)合使用。

虛引用的主要作用是跟蹤對象被垃圾回收的狀態(tài)。設(shè)置虛引用的唯一目的就是在這個對象被收集器回收的時候收到一個系統(tǒng)通知或者后續(xù)添加進(jìn)一步的處理。Java允許使用finalize()方法在垃圾收集器將對象從內(nèi)存中清理出去之前做必要的清理工作。

虛引用案例:

public static void main(String[] args) throws Exception {

? ? ? ? Object o1 = new Object();

? ? ? ? ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();

? ? ? ? PhantomReference<Object> phantomReference = new PhantomReference<>(o1, referenceQueue);

? ? ? ? System.out.println(o1);

? ? ? ? System.out.println(phantomReference.get());

? ? ? ? System.out.println(referenceQueue.poll());

? ? ? ? System.out.println("======================================================");

? ? ? ? o1 = null;

? ? ? ? System.gc();

? ? ? ? Thread.sleep(500);

? ? ? ? System.out.println(o1);

? ? ? ? System.out.println(phantomReference.get());

? ? ? ? System.out.println(referenceQueue.poll());

? ? }

引用隊(duì)列:

ReferenceQueue是用來配合引用工作的,沒有ReferenceQueue一樣可以運(yùn)行。

創(chuàng)建引用的時候可以指定關(guān)聯(lián)的隊(duì)列,當(dāng)GC釋放對象內(nèi)存的時候,會將引用加入到引用隊(duì)列。如果程序發(fā)現(xiàn)某個虛引用已經(jīng)被加入到引用隊(duì)列,那么就可以在所引用的對象內(nèi)存被回收之前采取必要的行動。

當(dāng)關(guān)聯(lián)的引用隊(duì)列中有數(shù)據(jù)的時候,意味著引用指向的堆內(nèi)存中的對象被回收。通過這種方式,JVM允許我們在對象被銷毀后做一些我們自己想做的事情。

引用隊(duì)列案例:

public static void main(String[] args) throws Exception {

? ? ? ? Object o1 = new Object();

? ? ? ? ReferenceQueue<Object> referenceQueue = new ReferenceQueue();

? ? ? ? WeakReference<Object> weakReference = new WeakReference<>(o1, referenceQueue);

? ? ? ? System.out.println(o1);

? ? ? ? System.out.println(weakReference.get());

? ? ? ? System.out.println(referenceQueue.poll());

? ? ? ? System.out.println("==============================================");

? ? ? ? o1 = null;

? ? ? ? System.gc();

? ? ? ? Thread.sleep(500);

? ? ? ? System.out.println(o1);

? ? ? ? System.out.println(weakReference.get());

? ? ? ? System.out.println(referenceQueue.poll());

? ? }


軟引用、弱引用的適用場景?

假如有一個應(yīng)用需要讀取大量的本地圖片:

????1)如果每次讀取圖片都從硬盤讀取則會嚴(yán)重影響性能。

? ? 2)如果一次性全部加載到內(nèi)存中又可能造成內(nèi)存溢出。

此時使用軟引用可以解決這個問題。

設(shè)計思路是:用一個HashMap來保存圖片的路徑和相應(yīng)圖片對象關(guān)聯(lián)的軟引用之間的映射關(guān)系,在內(nèi)存不足時,JVM會自動回收這些緩存圖片對象所占用的空間,從而有效的避免了OOM的問題。

Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SofrReference<Bitmap>>();


能談?wù)刉eakHashMap嗎?

WeakHashMap 繼承于AbstractMap,實(shí)現(xiàn)了Map接口,它是一個HashMap的弱引用。

案例:

public class WeakHashMapDemo {

? ? public static void main(String[] args) {

? ? ? ? myHashmap();

? ? ? ? System.out.println("=============================");

? ? ? ? myWeakHashmap();

? ? }

? ? /**

? ? * Hashmap 強(qiáng)引用HashMap案例

? ? */

? ? private static void myHashmap(){

? ? ? ? HashMap<Integer, String> map = new HashMap<>();

? ? ? ? Integer key = new Integer(1);

? ? ? ? String value = "HashMap";

? ? ? ? map.put(key, value);

? ? ? ? System.out.println(map);

? ? ? ? key = null;

? ? ? ? System.out.println(map);

? ? ? ? System.gc();

? ? ? ? System.out.println(map + "\t" + map.size());

? ? }

? ? /**

? ? * WeakHashmap 弱引用HashMap案例

? ? */

? ? private static void myWeakHashmap(){

? ? ? ? WeakHashMap<Integer, String> map = new WeakHashMap<>();

? ? ? ? Integer key = new Integer(2);

? ? ? ? String value = "HashMap";

? ? ? ? map.put(key, value);

? ? ? ? System.out.println(map);

? ? ? ? key = null;

? ? ? ? System.out.println(map);

? ? ? ? System.gc();

? ? ? ? System.out.println(map + "\t" + map.size());

? ? }

}


請談?wù)勀銓OM的認(rèn)識?

java.lang.StackOverflowError

導(dǎo)致原因:(??臻g溢出)

public class StackOverflowErrorDemo {

? ? public static void main(String[] args) {

? ? ? ? stackOverflowError();

? ? }

? ? private static void stackOverflowError() {

? ? ? ? stackOverflowError();

? ? }

}

java.lang.OutOfMemoryError: java heap space

導(dǎo)致原因:(堆空間溢出)

-Xms10m -Xmx10m

public class JavaHeapSpaceDemo {

? ? public static void main(String[] args) {

? ? ? ? byte[] bytes = new byte[80 * 1024 * 1024];

? ? }

}

java.lang.OutOfMemoryError: GC overhead limit exceeded

導(dǎo)致原因:

GC回收時間過長時會拋出OutOfMemoryError。過長的定義是,超過98%的時間用來做GC并且回收了不到2%的堆內(nèi)存

-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

public static void main(String[] args) {

? ? ? ? int i = 0;

? ? ? ? List<String> list = new ArrayList<>();

? ? ? ? try {

? ? ? ? ? ? while(true){

? ? ? ? ? ? ? ? list.add(String.valueOf(++i).intern());

? ? ? ? ? ? }

? ? ? ? }catch (Throwable e){

? ? ? ? ? ? System.out.println("**********i:" + i);

? ? ? ? ? ? e.printStackTrace();;

? ? ? ? ? ? throw e;

? ? ? ? }

? ? }

java.lang.OutOfMemoryError: Direct buffer memory

導(dǎo)致原因:

寫NIO程序經(jīng)常使用ByteBuffer來讀取或者寫入數(shù)據(jù),這是一種基于通道(channel)與緩沖區(qū)(Buffer)的I/O方式,他可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在Java堆里面的DirectByteBuffer對象作為這塊內(nèi)存的引用進(jìn)行操作。

ByteBuffer.allocate(capability)分配JVM堆內(nèi)存,屬于GC管轄范圍,由于需要拷貝所以速度相對較慢。

ByteBuffer.allocateDirect(capability)分配OS本地內(nèi)存,不屬于GC管轄范圍,由于不需要內(nèi)存拷貝所以速度相對較快。

如果不斷分配本地內(nèi)存,堆內(nèi)存很少使用,那么JVM就不需要執(zhí)行GC,DirectByteBuffer對象就不會被回收,這時候堆內(nèi)存充足,但本地內(nèi)存可能已經(jīng)用光了,再次嘗試分配本地內(nèi)存就會出現(xiàn)OutOfMemoryError

-Xms10m -Xmx10 -XX:+PrintGCDetails -XX:MaxDirectMemorySIze=5m

public static void main(String[] args) {

? ? ? ? System.out.println("配置maxDirectMemory:"+(VM.maxDirectMemory() / (double) 1024 / 1024) + "MB");

? ? ? ? try{

? ? ? ? ? ? Thread.sleep(3000);

? ? ? ? }catch (InterruptedException e){

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? ? ? //我們配置5MB 但實(shí)際使用6MB

? ? ? ? ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);

? ? }

java.lang.OutOfMemoryError: unable to create new native thread

導(dǎo)致原因:

1.你的應(yīng)用創(chuàng)建了太多線程,一個應(yīng)用進(jìn)程創(chuàng)建多個線程,超過系統(tǒng)承載極限。

2.你的服務(wù)器并不允許你的應(yīng)用程序創(chuàng)建這么多線程,Linux系統(tǒng)默認(rèn)允許單個進(jìn)程可以創(chuàng)建的線程數(shù)是1024個,你的應(yīng)用創(chuàng)建超過這個數(shù)量,就會報:java.lang.OutOfMemoryError: unable to create new native thread)

解決辦法:

1.想辦法降低你應(yīng)用程序創(chuàng)建線程的數(shù)量,分析應(yīng)用是否真的需要創(chuàng)建這么多線程,如果不是,改動代碼將線程數(shù)量降到最低。

2.對于有的應(yīng)用,確實(shí)需要創(chuàng)建很多線程,遠(yuǎn)超過Linux系統(tǒng)默認(rèn)的1024個線程限制,可以通過修改Linux服務(wù)器配置,擴(kuò)大Linux默認(rèn)限制。

查看修改Linux線程數(shù)

vim /etc/security/limits.d/90-nproc.conf

public static void main(String[] args) {

? ? ? ? for (int i = 0; ; i++) {

? ? ? ? ? ? System.out.println("********** i = " + i);

? ? ? ? ? ? new Thread(()->{

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? Thread.sleep(Integer.MAX_VALUE);

? ? ? ? ? ? ? ? }catch (InterruptedException e){

? ? ? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }, ""+i).start();

? ? ? ? }

? ? }

java.lang.OutOfMemoryError: Metaspace?

導(dǎo)致原因:(元空間溢出)

Java8及之后的版本使用Metaspace來替代永久代。

Metaspace是方法區(qū)在HotSpot中的實(shí)現(xiàn),它與持久代最大的區(qū)別在于:Metaspace并不在虛擬機(jī)內(nèi)存中而是使用本地內(nèi)存也即在java8中,被存儲在叫做Metaspace的native memory。

永久代(java8后被元空間取代了)存放了以下信息:

虛擬機(jī)加載的類信息 ?? 常量池 ?? 靜態(tài)變量 ?? 即時編譯后的代碼

模擬Metaspace空間溢出,我們不斷生成類汪元空間灌,類占據(jù)的空間總是會超過Metaspace指定的空間大小的。

Linux看元空間大小

java -XX:PrintFlagsInitial

.... -XX:MetaspaceSize 就是元空間大小

-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m

public class MetaspaceOOMTest {

? ? static class OOMTest{

? ? }

? ? public static void main(String[] args) {

? ? ? ? int i = 0;//模擬計數(shù)多少次以后發(fā)生異常

? ? ? ? try {

? ? ? ? ? ? while(true){

? ? ? ? ? ? ? ? i++;

? ? ? ? ? ? ? ? Enhancer enhancer = new Enhancer();

? ? ? ? ? ? ? ? enhancer.setSuperclass(OOMTest.class);

? ? ? ? ? ? ? ? enhancer.setUseCache(false);

? ? ? ? ? ? ? ? enhancer.setCallback(new MethodInterceptor() {

? ? ? ? ? ? ? ? ? ? @Override

? ? ? ? ? ? ? ? ? ? public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

? ? ? ? ? ? ? ? ? ? ? ? return methodProxy.invokeSuper(o, args);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? });

? ? ? ? ? ? ? ? enhancer.create();

? ? ? ? ? ? }

? ? ? ? }catch (Throwable e){

? ? ? ? ? ? System.out.println("**********多少次后發(fā)生了異常:" + i);

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

}


四種GC垃圾收集器?

串行垃圾回收器(UseSerialGC)

它為單線程環(huán)境設(shè)計且只使用一個線程進(jìn)行垃圾回收,會暫停所有的用戶線程,所以不適合服務(wù)器環(huán)境。

并行垃圾回收器(UseParallelGC)(默認(rèn)是這個)

多個垃圾收集線程并行工作,此時用戶線程是暫停的,適用于科學(xué)計算/大數(shù)據(jù)處理等弱交互場景。

并發(fā)垃圾回收器(UseConcMarkSweepGC)

用戶線程和垃圾收集線程同時執(zhí)行(不一定是并行,可能是交替執(zhí)行),不需要停頓用戶線程,互聯(lián)網(wǎng)公司多用它,適用對響應(yīng)時間有要求的場景。

G1垃圾回收器(UseG1GC)

將內(nèi)存分割成不同的區(qū)域然后并發(fā)的對其進(jìn)行垃圾回收。


如何查看默認(rèn)的GC垃圾收集器?

java -XX:+PrintCommandLineFlags -version


默認(rèn)垃圾收集器有哪些?

java的gc回收的類型主要有幾種:

UseSerialGC,UseParallelGC,UseConcMarkSweepGC,UseParNewGC,UseParallelOldGC,UseG1GC


Serial(串行):

一個單線程的收集器,在進(jìn)行垃圾收集的時候,必須暫停其他所有的工作線程直到它收集結(jié)束。

對應(yīng)的JVM參數(shù)是:-XX:+UseSerialGC

-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC

開啟后會使用:Serial(Young區(qū)) + Serial Old(Old區(qū)) 的收集器組合

表示:新生代、老年代都會使用串行回收收集器,新生代使用復(fù)制算法,老年代使用標(biāo)記-整理算法


ParNew(并行):

使用多線程進(jìn)行垃圾回收,在垃圾收集時,會暫停其他所有的工作線程直到它收集結(jié)束,ParNew是新生代的并行,老年代還是串行。

對應(yīng)的JVM參數(shù)是:-XX:+UseParNewGC ?? 啟動ParNew收集器,只影響新生代的收集,不影響老年代

開啟后會使用:ParNew(Young區(qū)) + Serial Old(Old區(qū)) 的收集器組合

表示:新生代使用復(fù)制算法,老年代使用標(biāo)記-整理算法


Parallel Scavenge(并行回收):

Parallel Scavenge類似與ParNew但是是新生代和老年代都使用并行。

對應(yīng)的JVM參數(shù)是:-XX:+UseParallelGC 或?-XX:+UseParallelOldGC

表示:新生代使用復(fù)制算法,老年代使用標(biāo)記-整理算法

多說一句:-XX:ParallelGCThreads=數(shù)字N ?? 表示啟動多少個GC線程

cpu>8 ?? N=5/8

cpu<8 ?? N=實(shí)際個數(shù)

CMS(并發(fā)標(biāo)記清除):

Concurrent Mark Sweep 并發(fā)標(biāo)記清除

是一種以獲取最短停頓時間為目標(biāo)的收集器。適合應(yīng)用在互聯(lián)網(wǎng)網(wǎng)站或者B/S系統(tǒng)的服務(wù)器上,這類應(yīng)用尤其重視服務(wù)器的響應(yīng)速度,希望系統(tǒng)停頓時間最短。適合內(nèi)存大、CPU多的服務(wù)器端應(yīng)用,也是G1出現(xiàn)之前大型應(yīng)用的首選收集器。

對應(yīng)的JVM參數(shù)是:-XX:+UseConcMarkSweepGC ?? 開啟該參數(shù)后會自動將-XX:+UseParNewGC打開

開啟后會使用:ParNew(Young區(qū)) + CMS(Old區(qū)) + Serial Old(Old區(qū)) 的收集器組合,Serial Old將作為CMS出錯的后備收集器

4步過程:

初始標(biāo)記(CMS initial mark)

? ? 標(biāo)記GC Roots能直接關(guān)聯(lián)的對象,速度很快,仍然需要暫停所有的工作線程。

并發(fā)標(biāo)記(CMS concurrent mark)和用戶線程一起

? ? 進(jìn)行GC Roots跟蹤的過程,和用戶線程一起工作,不需要暫停工作線程。主要標(biāo)記過程,標(biāo)記全部對象。

重新標(biāo)記(CMS remark)

? ? 修正在并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,仍然需要暫停所有工作線程。

? ? 由于并發(fā)標(biāo)記時,用戶線程依然運(yùn)行,因此在正式清理前,在做修正。

并發(fā)清除(CMS concurrent sweep)和用戶線程一起

? ? 清除GC Roots不可達(dá)對象,和用戶線程一起工作,不需要暫停工作線程?;跇?biāo)記結(jié)果,直接清理對象。


JVM的Server和Client模式分別是什么意思?

1)32位Window操作系統(tǒng),不論硬件如何都默認(rèn)使用Client的JVM模式

2)32位其他操作系統(tǒng),2G內(nèi)存同時有2個cpu以上用Server模式,低于該配置還是Client模式

3)64位默認(rèn)Server模式


如何選擇垃圾收集器?

單CPU或小內(nèi)存,單機(jī)程序

-XX:+UseSerialGC

多CPU,需要最大吞吐量,如后臺計算型應(yīng)用

-XX:+UseParllelGC

或者

-XX:+UseParallelOldGC

多CPU,追求低停頓時間,需快速響應(yīng)如互聯(lián)網(wǎng)應(yīng)用

-XX:+UseConcMarkSweepGC

-XX:+ParNewGC


G1垃圾收集器?

G1是什么:

G1是一種服務(wù)器端的垃圾收集器,應(yīng)用在多處理器和大容量內(nèi)存環(huán)境中,在實(shí)現(xiàn)高吞吐量的同時,盡可能的滿足垃圾收集暫停時間的要求。

以前收集器特點(diǎn):

1:年輕代和老年代是各自獨(dú)立且連續(xù)的內(nèi)存塊

2:年輕代收集使用單sden+S0+S1進(jìn)行復(fù)制算法

3:老年代收集必須掃描整個老年代區(qū)域

4:都是以盡可能少而快速的執(zhí)行GC為設(shè)計原則

G1特點(diǎn):

1:能與應(yīng)用程序線程并發(fā)執(zhí)行

2:整理空閑空間更快

3:不希望犧牲大量的吞吐性能

4:不需要更大的Java Heap

G1收集器的設(shè)計目標(biāo)是取代CMS收集器,它同CMS相比,在以下方面更出色:

1:G1有整理內(nèi)存過程,不會產(chǎn)生很多內(nèi)存碎片

2:G1的Stop The World(STW)更可控,G1在停頓時間上添加了預(yù)測機(jī)制,用戶可以指定期望停頓時間

為了解決CMS存在的內(nèi)存碎片問題同時又保留CMS垃圾收集器低暫停的優(yōu)點(diǎn),發(fā)布了一個新的垃圾收集器G1。

在JDK9中G1將變成默認(rèn)的垃圾收集器以替代CMS

特點(diǎn):

1:G1能充分利用多CPU、多核環(huán)境硬件優(yōu)勢,盡量縮短STW

2:G1整體上采用標(biāo)記清除算法,局部通過復(fù)制算法,不會產(chǎn)生內(nèi)存碎片

3:宏觀上看G1之中不再區(qū)分年輕代和老年代。把內(nèi)存劃分成多個獨(dú)立的子區(qū)域(Region),可以近似的理解為一個圍棋的棋盤。

4:G1收集器里面講整個的內(nèi)存區(qū)都混合在一起了,但其本身依然在小范圍內(nèi)要進(jìn)行年輕代和老年代的區(qū)分,保留了新生代和老年代,但他們不再是物理隔離的,而是一部分Region的集合且不需要Region是連續(xù)的,也就是說依然會采用不同的GC方式來處理不同的區(qū)域。

5:G1雖然也是分代收集器,但整個內(nèi)存分區(qū)不存在物理上的年輕代和老年代的區(qū)別,也不需要完全獨(dú)立的survivor(to space)堆做復(fù)制準(zhǔn)備。G1只有邏輯上的分代概念,或者說每個分區(qū)都可能隨G1的運(yùn)行在不同代之間前后切換。


G1底層原理?

Region區(qū)域化垃圾收集器:

最大好處是化整為零,避免全內(nèi)存掃描,只需要按照區(qū)域來進(jìn)行掃描即可。

Region區(qū)域化垃圾收集器思想:

區(qū)域化內(nèi)存劃片Region,整體編為了一些列不連續(xù)的內(nèi)存區(qū)域,避免了全內(nèi)存區(qū)的GC操作。

核心思想是將整個堆內(nèi)存區(qū)域分成大小相同的子區(qū)域(Region),在JVM啟動時會自動設(shè)置這些子區(qū)域大小,在堆的使用上G1并不要求對象的存儲一定是物理上連續(xù)的只要邏輯上連續(xù)即可,每個分區(qū)也不會固定的為某個代服務(wù),可以按需在年輕代和老年代之間切換。啟動時可以通過參數(shù)-XX:G1HeapRegionSize=n可指定分區(qū)大?。?MB~32MB,且必須是2的冪),默認(rèn)將整堆劃分為2048個分區(qū)。

大小范圍在1MB~32MB,最多能設(shè)置2048個區(qū)域,也即能夠支持的最大內(nèi)存為32MB * 2048 = 65536MB = 64G內(nèi)存?

G1算法:

G1算法將堆劃分為若干個區(qū)域(Region),它仍然屬于分代收集器

這些Region的一部分包含新生代,新生代的垃圾收集依然采用暫停所有應(yīng)用線程的方式,將存活對象拷貝到老年代或者Survivor空間。

這些Region的一部分包含老年代,G1收集器通過將對象從一個區(qū)域復(fù)制到另外一個區(qū)域,完成了清理工作。這就意味著,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有CMS內(nèi)存碎片問題的存在了。

在G1中,還有一種特殊的區(qū)域,叫做Humongous(巨大的)區(qū)域如果一個對象占用的空間超過了分區(qū)容量的50%以上,G1收集器就認(rèn)為這是一個巨型對象。這些巨型對象默認(rèn)直接會被分配在老年代,但是如果他是一個短期存在的巨型對象,就會對垃圾收集器造成負(fù)面影響。為了解決這個問題,G1劃分了一個Humongous區(qū),它用來專門存放巨型對象。如果一個H區(qū)裝不下一個巨型對象,那么G1會尋找連續(xù)的H分區(qū)來存儲。為了能找到連續(xù)的H區(qū),有時候不得不啟動Full GC。


G1回收步驟?

G1收集器下的Young GC

針對Eden區(qū)進(jìn)行收集,Eden區(qū)耗盡后會被處觸發(fā),主要是小區(qū)域收集 + 形成連續(xù)的內(nèi)存塊,避免內(nèi)存碎片

Survivor區(qū)的數(shù)據(jù)移動到新Survivor區(qū),加入出現(xiàn)Survivor區(qū)空間不夠,Eden區(qū)數(shù)據(jù)會部分普升到Old區(qū)

最后Eden區(qū)收拾干凈了,GC結(jié)束,用戶的應(yīng)用程序繼續(xù)執(zhí)行。


G1收集器常用參數(shù)配置?

-XX:+UseG1GC

-XX:G1HeapRegionSize=n ?? 設(shè)置G1區(qū)域大小,值是2的冪,范圍1MB到32MB。目標(biāo)是根據(jù)最小的Java堆大小劃分出約2048個區(qū)域。

-XX:MaxGcPauseMillis=n ?? 最大GC停頓時間,這是個軟目標(biāo),JVM將盡可能(但不保證)停頓小于這個時間

-XX:InitiatingHeapOccupancyPercent=n ?? 堆占用了多少的時候就觸發(fā)GC,默認(rèn)為45

-XX:ConcGCThreads=n ?? 并發(fā)GC使用的線程數(shù)

-XX:G1ReservePercent=n ?? 設(shè)置作為空閑空間的預(yù)留內(nèi)存百分比,以降低目標(biāo)空間溢出的風(fēng)險默認(rèn)值是10%

一般情況開發(fā)人員僅僅需要聲明以下參數(shù)即可:

開始G1 + 設(shè)置最大內(nèi)存 + 設(shè)置最大停頓時間

-XX:+UseG1Gc ?? -Xmx32g ?? -XX:MaxGCPauseMillis=100


JVMGC結(jié)合SpringBoot微服務(wù)的生產(chǎn)部署和調(diào)參優(yōu)化?

java -server jvm的各種參數(shù) -jar jar/war包名字

例:

java -server -Xms1024m -Xmx1024m -XX:+UseG1GC -jar test.war

查看配置的參數(shù)

jps -l

jinfo -flags 端口號


生產(chǎn)環(huán)境服務(wù)器變慢,診斷思路和性能評估談?wù)劊?/h4>

top和uptime 命令

top ?? 用于實(shí)時監(jiān)測系統(tǒng)資源使用狀況,按1可以顯示幾核CPU

uptime????用于查看系統(tǒng)的負(fù)載信息

主要看CPU 和 MEM內(nèi)存 和 load average 負(fù)載均衡

load average 如果三個值相加除以3乘以100%如果大于60%則系統(tǒng)壓力過大

vmstat 命令

vmstat -n 2 3????對操作系統(tǒng)的虛擬內(nèi)存、進(jìn)程、CPU活動進(jìn)行監(jiān)控,第一個參數(shù)是采樣的時間間隔數(shù)單位是秒,第二個參數(shù)是采樣的次數(shù)

- procs

? ? r:運(yùn)行和等待CPU時間片的進(jìn)程數(shù),原則上1核的CPU運(yùn)行隊(duì)列不要超過2,整個系統(tǒng)的運(yùn)行隊(duì)列不能超過總核數(shù)的2倍,否則代表系統(tǒng)壓力過大

? ? b:等待資源的進(jìn)程數(shù),比如正在等待磁盤I/O、網(wǎng)絡(luò)I/O等

- cpu

? ? us:用戶進(jìn)程消耗CPU時間百分比,us值高,用戶進(jìn)程消耗CPU時間多,如果長期大于50%,優(yōu)化程序

? ? sy:內(nèi)核進(jìn)程小號的CPU時間百分比

? ? us + sy參考值為80%,如果us + sy大于80%,說明可能存在CPU不足

- id:處于空閑的CPU百分比

- wa:系統(tǒng)等待IO的CPU時間百分比

- st:來自于一個虛擬機(jī)偷取的CPU時間的百分比

mpstat 命令

mpstat -P ALL 2 每兩秒采樣一次,顯示各個CPU進(jìn)程消耗數(shù)

- idle ?? CPU空閑率 越高越好

pidstat 命令

pidstat -u 1 -p 進(jìn)程編號 ?? 顯示各個進(jìn)程的cpu使用統(tǒng)計

pidstat -p?進(jìn)程編號 -r 2????顯示各個進(jìn)程的內(nèi)存使用統(tǒng)計

pidstat -d 2 -p 進(jìn)程編號????示各個進(jìn)程的IO使用情況

free 命令

free -m 查看內(nèi)存

應(yīng)用程序可用內(nèi)存/系統(tǒng)物理內(nèi)存>70% ?? 內(nèi)存充足

應(yīng)用程序可用內(nèi)存/系統(tǒng)物理內(nèi)存<20% ?? 內(nèi)存不足,需要增加內(nèi)存

20%<應(yīng)用程序可用內(nèi)存/系統(tǒng)物理內(nèi)存<70%????內(nèi)存基本夠用

df 命令

df -h ?? 查看磁盤剩余空間

iostat 命令

iostat -xdk 2 3 ?? 磁盤I/O性能評估,每2秒取樣一次,共取3次

rkB/s每秒讀取數(shù)據(jù)量kB

wkB/s每秒寫入數(shù)據(jù)量kB

svctm I/O請求的平均服務(wù)時間,單位毫秒

await I/O請求的平均等待時間,單位毫秒;值越小,性能越好

util ?? 一秒鐘有百分幾的時間用于I/O操作。接近100%時,表示磁盤帶寬跑滿,需要優(yōu)化程序或者增加磁盤;

rkB/s、wkB/s根據(jù)系統(tǒng)應(yīng)用不同會有不同的值,但有規(guī)律遵循:長期、超大數(shù)據(jù)讀寫,肯定不正常,需要優(yōu)化程序讀取。

svctm的值與await的值很接近,表示幾乎沒有I/O等待,磁盤性能好,如果await的值遠(yuǎn)高于svctm的值,則表示I/O隊(duì)列等待太長,需要優(yōu)化程序或更換更快的磁盤。

ifstat 命令

ifstat l ?? 查看網(wǎng)絡(luò)I/O


加入生產(chǎn)環(huán)境出現(xiàn)CPU過高,請談?wù)勀愕姆治鏊悸泛投ㄎ唬?/h4>

1.用top命令找出CPU占用最高的程序

2. ps -ef或者jps進(jìn)一步定位

ps -ef|grep java|grep -v grep

3.定位到具體的線程或者代碼

ps -mp 進(jìn)程 -o THREAD,tid,time

參數(shù)解釋:

????-m????顯示所有的線程

????-p pid ?? 進(jìn)程使用cpu的時間

????-o ?? 該參數(shù)后是用戶自定義格式

????tid ?? 線程號

????time ?? 運(yùn)行時間

4.將需要的線程ID轉(zhuǎn)換為16進(jìn)制格式(英文小寫格式)

printf "%x\n" 線程ID

5.jstack 進(jìn)程ID | grep tid(16進(jìn)制線程ID小寫英文) -A60

A60????打印出前60行





GitHub


常用詞含義

watch:會持續(xù)收到該項(xiàng)目的動態(tài)

fork:復(fù)制某個項(xiàng)目到自己的Github倉庫中

star:點(diǎn)贊

clone:將項(xiàng)目下載至本地

follow:關(guān)注作者

in關(guān)鍵詞限制搜索范圍

公式:關(guān)鍵字 in:name或description或readme

xxx in:name ?? 項(xiàng)目名包含xxx的

xxx in:description ?? 項(xiàng)目描述包含xxx的

xxx in:readme ?? 項(xiàng)目的readme文件中包含xxx的

組合使用:xxx in:name,readme ?? 搜索項(xiàng)目名或者readme中包含xxx的項(xiàng)目

stars或fork數(shù)量關(guān)鍵詞查找

springboot stars:>=5000 ?? 查找stars數(shù)大于等于5000的springboot項(xiàng)目

springcloud forks:>500 ?? 查找forks數(shù)大于500的springcloud項(xiàng)目

springboot forks:100..200 stars:80..100????查找fork在100到200之間并且stars數(shù)在80到100之間的springboot項(xiàng)目

awesome搜索

awesome系列一般是用來收集學(xué)習(xí)、工具、書籍類相關(guān)的項(xiàng)目

awesome redis ?? 搜索優(yōu)秀的redis相關(guān)的項(xiàng)目,包括框架、教程等

#L數(shù)字 高亮顯示代碼

1行 ?? 地址后面緊跟????#L數(shù)字

多行 ?? 地址后面緊跟????#L數(shù)字-L數(shù)字

項(xiàng)目內(nèi)搜索

英文 t

https://help.github.com/en/articles/using-keyboard-shortcuts

搜索某個地區(qū)的大佬

公式:location:地區(qū) ?? language:語言

location:beijing language:java ?? 地區(qū)北京的Java方向的用戶

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容