日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

秋招面试我去了拼多多,直接被问JVMGC底层原理和算法,我吊打面试官

發布時間:2023/12/4 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 秋招面试我去了拼多多,直接被问JVMGC底层原理和算法,我吊打面试官 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

JVM 常用參數設置積累

# 堆的初始值,默認物理內存的1/64 -Xms: # 堆的最大值,默認物理內存的1/4 -Xmx: # 年輕代大小「在整個堆內存大小確定的情況下,增大年輕代將會減小年老代,反之亦然。此值關系到JVM垃圾回收,對系統性能影響較大,官方推薦配置為整個堆大小的3/8-Xmn: # 設置年輕代初始值為 1024 M -XX:NewSize=1024 # 設置年輕代最大值為 1024 M -XX:MaxNewSize=1024m # 設置線程棧大小,設置越小,說明一個線程棧里面能分配的棧幀數就越少,但對于 JVM 來講,能開啟的線程數就越多; -Xss128k # 方法區大小設置「jdk1.8 之后使用元空間替換了方法區,也使用了其他命令」 -XX:MaxPermSize # 元空間大小設置 -XX:MetaspaceSize -XX:MaxMetaspaceSize # 設置大對象的大小,如果對象超過設置大小會直接進入老年代,不會進入年輕代「只在 Serial 和ParNew兩個收集 器下有效」 -XX:PretenureSizeThreshold=1000000 # 設定對象晉升到老年代的年齡閾值「設定能經歷 10 次拷貝,對象則晉升至老年代」 -XX:MaxTenuringThreshold=10 # jdk1.8 默認設置了下述參數,設置該參數,就會在每次 minor gc 之前看看老年代的可用內存大小,是否大于之前每一次 minor gc 后進入老年代的對象的平均大小,如果小于則那么就會觸發一次 Full gc -XX:-HandlePromotionFailure

JVM 排查問題命令積累

# 查詢實例個數和占用空間大小 jmap -histo pid # 導出堆內存信息 jmap -dump:format=b,file=test.hprof pid # 查找死鎖,打印出線程的狀態 jstack pid # 查看當前運行 java 應用的擴展參數 jinfo pid # 查看內存中各個部分的使用情況「eden、survivor、old」 jstat -gc pid # 堆內存統計 jstat -gccapacity pid # 新生代內存統計 jstat -gcnewcapacity pid # 新生代垃圾回收統計 jstat -gcnew pid # 老年代內存統計 jstat -gcoldcapacity pid # 老年代垃圾回收統計 jstat -gcold pid # 元數據空間統計 //加入Java開發交流君樣:593142328一起吹水聊天 jstat -gcmetacapacity pid # 總結垃圾回收統計 jstat -gcutil pid

JVM 的運行模式有三種:

  • 解釋模式「-Xint」:只使用解釋器,執行一行字節碼就編譯一次機器碼「不會去緩存」;
    • 優點:啟動塊;
    • 缺點:整體執行相比編譯模式慢;
  • 編譯模式「-Xcomp」:只使用編譯器,現將所有的字節碼文件一次性編譯為機器碼,然后一次性去執行所有的機器碼;
    • 優點:好處是執行快;
    • 缺點:啟動比解釋模式慢;
  • 混合模式:依舊采用解釋模式執行代碼,但是對于一些“熱點”代碼采用編譯模式執行,JVM 一般采用混合模式執行代碼;
    • 優點:相比解釋模式,執行會快,相比編譯模式,啟動會快;

    針對混合模式,JVM 有對應的技術去實現,比如 JIT,也就是即時編輯技術。

    JVM 內存分配與回收

    • jvm 內存區域圖

  • 堆內內存:堆內內存 = 年輕代 + 老年代 + 持久代『jdk1.8 之后沒有持久代』
    • 優點:
    • 缺點:
  • 堆外內存:把內存對象分配在 Java 虛擬機的堆以外的內存「比如:java.nio.DirectByteBuffer」
    • 優點:
  • 減少了垃圾回收機制(GC 會暫停其他的工作);
  • 加快了復制的速度「堆內在flush到遠程時, 會先復制到直接內存(非堆內存), 然后再發送,而堆外內存(本身就是物理機內存)幾乎省略了該步驟」。
    • 缺點:
  • 內存難以控制「使用了堆外內存就間接失去了JVM管理內存的可行性,改由自己來管理,當發生內存溢出時排查起來非常困難」。
  • 年輕代

    • 伊甸區:survivor from 區:survivor to 區 = 8:1:1
    伊甸區
    • 大部分對象都在這里誕生
    • 當Eden區滿時, 依然存活的對象將被復制到Survivor區, 當一個Survivor 區滿時, 此區的存活對象將被復制到另外一個Survivor區
    survivor 區
    • survivor from 區
    • survivor to區

    老年代

    方法區/元空間

    • 在 jdk1.8 之后取消了方法區,命名為元空間

    線程棧

    什么場景下對象會進入老年代

  • 即將存儲的大對象在eden 區域是發現存儲不下「就算 Minor gc后還是存儲不下」;
  • 長期存活下來的對象;
  • Minor gc 后存活的對象Survivor區放不下;
  • 什么是老年代空間分配擔保機制

    ? 年輕代每次 minor gc 之前 JVM 都會計算下老年代剩余可用空間如果這個可用空間小于年輕代里現有的所有對象大小之和(包括垃圾對象)就會看一個 “-XX:-HandlePromotionFailure”(jdk1.8 默認就設置了)的參數是否設置了,如果有這個參數,就會看看老年代的可用內存大小,是否大于之前每一次 minor gc 后進入老年代的對象的平均大小。

    如果上一步結果是小于或者之前說的參數沒有設置,那么就會觸發一次 Full gc,對老年代和年輕代一起回收一次垃圾,如果回收完還是沒有足夠空間存放新的對象就會發生 OOM,當然,如果 minor gc 之后剩余存活的需要挪動到老年代的對象大小還是大于老年代可用空間,那么也會觸發 full gc,full gc完之后如果還是沒用空間放 minor gc 之后的存活對象,則也會發生 “OOM”。


    觸發 full gc 的時機

  • 調用System.gc();
  • 老生代內存不足的時候;
  • 即將要放進老年代的對象過大,需要進行老年代回收;「和第二種類似」
  • 老年代空間分配擔保機制中有可能觸發;「也就是老年代空間分配擔保失敗」
  • 執行 jmap -histo:live 或者 jmap -dump:live 的時候;
  • 如何判斷對象可以被回收

    1. 引用計數法

    給對象添加一個引用計數器,沒增加一個地方引用它,計數器就加一,減少一個,計數器就減一,但是解決不了循環引用的問題「會導致內存泄露」,主流的虛擬機都沒有使用這個。

    2. 可達性分析

    通過一系列的稱為 GC Roots 的對象作為起點,從這些節點開始向下搜索,找到的對象都標記為非垃圾對象,其余未標記的對象都是垃圾對象。

    GC Roots 根節點:線程棧的本地變量、靜態變量、本地方法棧的變量等等。

    具體操作:從gc root根往下搜索,然后三色標記,黑灰白,剛開始是白色,如果搜索到A節點,A節點的子節點還沒被搜索,則A節點是灰色,A節點包括子節點全部搜索完畢標記為黑色,到最后白色的就回收了

    3. 依據引用類型
    java的引用類型一般分為四種:強引用、軟引用、弱引用、虛引用。

    • 強引用
      普通的變量引用
    public static Person person = new Person();
    • 軟引用
      將對象用 SoftReference 軟引用類型的對象包裹,正常情況不會被回收,但是GC做完后發現釋放不出空間存放新的對象,則會把這些軟引用的對象回收掉。軟引用可用來實現內存敏感的高速緩存。
    public static SoftReference<Person> person = new SoftReference<Person>(new Person());
    • 弱引用
      將對象用 WeakReference 軟引用類型的對象包裹,弱引用跟沒引用差不多,GC 會直接回收掉,很少用。
    public static WeakReference<Person> person = new WeakReference<Person>(new Person());
    • 虛引用
      虛引用也稱為幽靈引用或者幻影引用,是最弱的一種引用關系,幾乎不用。
      一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來獲取一個對象的實例。為一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。虛引用和弱引用對關聯對象的回收都不會產生影響,如果只有虛引用活著弱引用關聯著對象,那么這個對象就會被回收。它們的不同之處在于弱引用的get方法,虛引用的get方法始終返回null,弱引用可以使用ReferenceQueue,虛引用必須配合ReferenceQueue使用。

    jdk中直接內存的回收就用到虛引用,由于jvm自動內存管理的范圍是堆內存,而直接內存是在堆內存之外(其實是內存映射文件,自行去理解虛擬內存空間的相關概念),所以直接內存的分配和回收都是有Unsafe類去操作,java在申請一塊直接內存之后,會在堆內存分配一個對象保存這個堆外內存的引用,這個對象被垃圾收集器管理,一旦這個對象被回收,相應的用戶線程會收到通知并對直接內存進行清理工作。

    4. 通過 finalize() 方法最終判定對象是否存活

    即使在可達性分析算法中不可達的對象,也并非是“非死不可”的,這時候它們暫時處于“緩刑”階段,要真正宣告一個對象死亡,至少要經歷再次標記過程。標記的前提是對象在進行可達性分析后發現沒有與 GC Roots 相連接的引用鏈。
    • 第一次標記并進行一次篩選。
      篩選的條件是此對象是否有必要執行 finalize() 方法。當對象沒有覆蓋 finalize 方法,對象將直接被回收。
    • 第二次標記
      如果這個對象覆蓋了 finalize 方法,finalize 方法是對象脫逃死亡命運的最后一次機會,如果對象要在 finalize() 中成功拯救自己,只要重新與引用鏈上的任何的一個對象建立關聯即可,譬如把自己賦值給某個類變量或對象的成員變量,那在第二次標記時它將移除出“即將回收”的集合。如果對象這時候還沒逃脫,那基本上它就真的被回收了。

    垃圾回收算法


    1、標記-清除算法

    分為兩個階段,即標記和清除,首先會標記所有需要被回收的對象,在標記完成后統一對已經標記的對象進行回收,是最基礎的收集算法。

    • 優點
  • 實現簡單
    • 缺點
  • 內存碎片化
  • 效率不高
    • 使用場景:主流虛擬機不使用

    2、標記-整理算法「也叫標記-壓縮算法」

    針對老年代進行回收的一種算法,標記的過程和『標記-清除算法』一樣,只是在清除完成后,會將還存活的對象朝著一個方向移動,然后固定的清理靠近邊界的對象。

    • 優點
  • 解決了碎片化
    • 缺點
  • 效率不高
  • 移動了對象地址,需要更新對象的引用
    • 使用場景:用于老年代垃圾回收

    3、復制算法「比標記清理和標記整理快 10 倍以上」

    能解決「標記-清理算法」帶來碎片化問題,復制算法首先將內存分為大小相同的兩塊,每次只使用其中的一塊,但這一塊被使用完后「或者是沒法提供所需的連續長度的內存」,就會將這一塊的內存復制到另一塊去,然后再一次性將這塊的內存空間全部清理掉。

    • 優點
  • 解決了碎片化
  • 效率高
    • 缺點
  • 內存使用率不高
    • 使用場景:用于年輕代垃圾回收

    4、分代回收算法

    ? 這種算法不是新鮮的算法,而是針對不同的內存分區,采用不同的回收算法,比如在新生代中,每次收集都會有大量對象(近 99%)死去,所以可以選擇復制算法,只需要付出少量對象的復制成本就可以完成每次垃圾收集。而老年代的對象存活幾率是比較高的,而且沒有額外的空間對它進行分配擔保「老年代多是大對象,很可能是需要連續內存地址的對象」,所以我們必須選擇「標記清除算法」或「標記整理算法」進行垃圾收集。

    垃圾收集器「回收算法的具體實現」

    1、Serial 收集器「-XX:+UseSerialGC -XX:+UseSerialOldGC」

    新生代采用復制算法,老年代采用標記-整理算法

    Serial(串行)收集器是最基本、歷史最悠久的垃圾收集器了。是一個單線程收集器了。它的 “單線程” 的意義不僅僅意味著它只會使用一條垃圾收集線程去完成垃圾收集工作,更重要的是它在進行垃圾收集工作的時候必須暫停其他所有的工作線程「也就是應用程序線程」,直到它收集結束。

    Serial 收集器執行過程

    • 優點
  • 沒有多線程交互,單線程實現簡單;
  • 相比其他單線程收集器,效率最高「當然是比不上多線程收集器」;
    • 缺點:
  • STW 時間長,用戶體驗不好
    • 使用場景:

    ? 一種用途是在 JDK1.5 以及以前的版本中與 Parallel Scavenge 收集器搭配使用,另一種用途是作為 CMS 收集器的后備方案。

    2、ParNew 收集器「-XX:+UseParNewGC」

    新生代采用復制算法,老年代采用標記-整理算法

    ParNew 收集器其實就是 Serial 收集器的多線程版本,除了使用多線程進行垃圾收集外,其余行為(控制參數、收集算法、回收策略等等)和 Serial 收集器完全一樣。默認的收集線程數跟 CPU 核數相同,當然也可以用參數(-XX:ParallelGCThreads)指定收集線程數,但是一般不推薦修改。

    ParNew 收集器執行過程


    • 優點:
  • 相比 Serial 效率高
    • 缺點:
  • 實現稍復雜
    • 使用場景:

    3、Parallel 收集器「-XX:+UseParallelGC(年輕代) -XX:+UseParallelOldGC(老年代)」

    新生代采用復制算法,老年代采用標記-整理算法

    Parallel Scavenge 收集器關注點是吞吐量(高效率的利用CPU)。CMS 等垃圾收集器的關注點更多的是用戶線程的停頓時間(提高用戶體驗)。所謂吞吐量就是 CPU 中用于運行用戶代碼的時間與 CPU 總消耗時間的比值。 Parallel Scavenge 收集器提供了很多參數供用戶找到最合適的停頓時間或最大吞吐量,如果對于收集器運作不太了解的話,可以選擇把內存管理優化交給虛擬機去完成也是一個不錯的選擇。

    • 優點:

    • 缺點:

    • 使用場景:

    Parallel 收集器執行過程

    4、CMS 收集器「-XX:+UseConcMarkSweepGC(old)」

    新生代采用復制算法,老年代采用標記-整理算法

    CMS(Concurrent Mark Sweep)以獲取最短回收停頓時間為目標的收集器。它非常符合在注重用戶體驗的應用上使用,它是 HotSpot 虛擬機第一款真正意義上的并發收集器,它第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工作。

    CMS 收集器執行過程

    初始標記-》并發標記-》重新標記-》并發清理-》并發重置

    其中只有『初始標記』不能和用戶線程并發,其他的四個是可以的。

    • 優點:
  • 并發收集、低停頓;
  • 吞吐量高;
    • 缺點:
  • 對 CPU 資源敏感(會和服務搶資源);
  • 無法處理浮動垃圾(即在并發清理階段又產生垃圾,這種浮動垃圾只能等到下一次 GC 再清理了);
  • 它使用的回收算法“標記-清除”算法會導致收集結束時會有大量空間碎片產生,當然通過參數 -XX:+UseCMSCompactAtFullCollection 可以讓 jvm 在執行完標記清除后再做整理;
  • 執行過程中的不確定性,會存在上一次垃圾回收還沒執行完,然后垃圾回收又被觸發的情況,特別是在并發標記和并發清理階段會出現,一邊回收,系統一邊運行,也許沒回收完就再次觸發 full gc,也就是“concurrent mode failure”,此時會進入 stop the world,使用 serial old 垃圾收集器來回收;
    • 使用場景

    注重用戶體驗的系統,低延時。

    何為[ concurrent mode failure 錯誤

    5、G1 收集器「-XX:+UseG1GC」

    一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器,以極高概率滿足 GC 停頓時間要求的同時,還具備高吞吐量性能特征。會預測的停頓的時間,以及一些抉擇,比如 200ms 回收 10MB 和 50ms 回收 20MB 兩種選擇,會選擇第二種,用以達到有限時間內最大的回收效率;

    • 優點:

    • 缺點:

    • 使用場景:

    逃逸分析

    public void test() {Person person = new Person(); }

    上面的代碼,會經歷如下幾個步驟:

  • 加載 Person.class 到內存上;
  • 在棧中開辟一段空間,用于 test 方法的入棧,然后在 test 方法的棧空間分配一個變量 p;
  • 在堆內存中創建一塊區域空間,分配內存地址「new 關鍵字的作用」;
  • 對空間的屬性空間分配,默認初始化;
  • 構造函數初始化;
  • 將分配的地址賦值給變量 p,即 p 指向了剛剛劃分并且初始化好的堆地址;
  • 按照上面的步驟,每個對象的分配,對象會直接分配在堆上,但如果需要分配的對象非常多,并且生命周期都比較短,比如在某個循環中一直 new 某一個類的對象,并且創建的對象不會作為返回值『或者是返回值的一部分 』,返回給調用者,那么這些數量多且生命周期短的對象,將會占用較多的堆空間,這些被占用的會由 GC 定時去清理,但如果有一種手段,盡量的讓這些對象都存儲在棧里面,也就是方法棧,這些對象的銷毀會隨著方法的出棧而消亡,就不再需要 GC 去耗費寶貴的時間和資源去回收堆內存了,STW 的時間自然也會短,GC 的次數也會少,這種手段就是逃逸分析,在 JDK8 中逃逸分析是默認開啟。

    『但一種手段的出現,肯定是有利也有弊,開啟逃逸分析也會耗費時間和資源,就需要我們自己去測試分析,手上的項目是否合適,不能保證逃逸分析的性能收益必定高于它的消耗』

    逃逸分析的分類

    • 方法逃逸
    • 線程逃逸

    方法逃逸

    當一個對象在方法里面被定義后,它可能被外部方法所引用,例如作為調用參數傳遞到其它方法中。

    線程逃逸

    這個對象甚至可能被其它線程訪問到,例如賦值給類變量或可以在其它線程中訪問的實例變量。

    逃逸分析總結

    當一個對象,在其生命周期內,被其他對象所持有,那么就會發生逃逸。

    最后,祝大家早日學有所成,拿到滿意offer

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的秋招面试我去了拼多多,直接被问JVMGC底层原理和算法,我吊打面试官的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。