天天看點

大資料常見面試題之JVM一.請說明一下Java虛拟機地作用是是什麼二.Java記憶體結構三.解釋棧,堆,方法區的用法四.對象配置設定規則五.假設一個場景,要求stop the world時間非常短,你會怎麼會設計垃圾回收機制六.Eden區和Survial區的含義和工作原理七.垃圾回收算法八.垃圾回收器九.主要的JVM參數

一.請說明一下Java虛拟機地作用是是什麼

  • 解釋運作位元組碼程式消除平台相關性
  • JVM将Java位元組碼解釋為具體平台地具體指令.一般地進階語言如果在不同地平台上運作,至少需要編譯成不同地目标代碼.而引進JVM之後,Java語言在不同地平台上運作時不需要重新編譯.Java語言使用模式Java虛拟機屏蔽了與具體平台相關地資訊,使得Java語言編譯程式隻需生成在Java虛拟機上運作地目标代碼(位元組碼),就可以在多平台上不加修改地運作.Java虛拟機在執行位元組碼時,把位元組碼解釋成具體平台上地機器指令執行

二.Java記憶體結構

大資料常見面試題之JVM一.請說明一下Java虛拟機地作用是是什麼二.Java記憶體結構三.解釋棧,堆,方法區的用法四.對象配置設定規則五.假設一個場景,要求stop the world時間非常短,你會怎麼會設計垃圾回收機制六.Eden區和Survial區的含義和工作原理七.垃圾回收算法八.垃圾回收器九.主要的JVM參數
  • 方法區是所有線程共享地記憶體區域:而Java棧,本地方法棧和程式計數器是運作線上程私有地記憶體區域
  • Java堆(Heap)

    :是Java虛拟機所管理地記憶體中最大的一塊.Java堆是被所有線程共享地一塊記憶體區域,在虛拟機啟動時建立.此記憶體區域的唯一目的就是存放對象執行個體,幾乎所有的對象執行個體都在這裡配置設定記憶體
  • 方法區(Method Area)

    :與堆一樣,是各個線程共享的記憶體區域,它用于存儲已被虛拟機加載的類資訊,常量,靜态變量,即時編譯器編譯後的代碼等資料
  • 程式計數器(Program Counter Register)

    :是一塊較小的記憶體空間,它的作用可以看作是目前線程所執行的位元組碼的行号訓示器
  • JVM棧(JVM Stacks)

    :與程式計數器一樣也是私有的,它的生命周期與線程相同.虛拟機描述的是Java方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個

    棧幀(Stack Frame)

    用于存儲局部變量表,操作棧,動态連結,方法出口等資訊.每一個方法被調用直至執行完成的過程,就對應着一個棧幀在虛拟機中從入棧到出棧的過程
  • 本地方法棧(Native Method Stacks)

    :與虛拟機所發揮的作用是非常相似的,其差別不過是虛拟機棧為虛拟機執行Java方法(也就是位元組碼)服務,而本地方法棧則是為虛拟機使用到Native方法服務

三.解釋棧,堆,方法區的用法

  • 通常我們定義一個基本資料類型的變量,一個對象的引用,還有就是函數調用的現場儲存都是以JVM中的棧空間
  • 而通過

    new

    關鍵字和構造器建立的對象則放在堆空間,堆是垃圾收集器管理的主要區域,由于現在的垃圾收集器都采用分代收集算法,是以堆空間還可以細分為新生代和老生代,再具體一點可以分為Eden,Survivor(又可分為From Survivor,To Survivor),Tenured
  • 方法區和堆都是各個線程共享的記憶體區域,用于存儲已經被JVM加載的類資訊,常量,靜态變量,JIT編譯器編譯後的代碼等資料
  • 程式中的字面量(literal)如直接書寫的100,"hello"和常量都是放在常量池中,常量池是方法區的一部分
  • 棧空間操作起來最快但是棧很小,通常大量的對象都是放在堆空間内,棧和堆的大小都可以通過JVM的啟動參數來進行調整,棧空間用光了會引發StackOverflowError,而堆和常量池空間不足則會引發OutOfMemoryError

四.對象配置設定規則

  • 對象優先配置設定在

    Eden

    區,如果Eden區沒有足夠的空間時,虛拟機執行一次Minor GC.
  • 大對象直接進入老年代(大對象是指需要大量連續記憶體空間的對象),這樣做的目的是避免在Eden區和兩個Survivor區之間發生大量的記憶體拷貝(新生代采用複制算法收集記憶體)
  • 長期存活的對象進入老年代,虛拟機為每個對象定義了一個年齡計數器,如果對象經過了一次Minor GC 那麼對象會進入Survivor區,之後每經過一次Minor GC那麼對象的年齡加1,直到達到閥值對象進入老年區
  • 動态判斷對象的年齡,如果Survivor區中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于等于該年齡的對象可直接進入老年代
  • 空間配置設定擔保,每次進行Minor GC 時,JVM會計算Survivor區移至老年區的對象的平均大小,如果這個值大于老年區的剩餘值大小則進行一次 Full GC,如果小于檢查HandlePromotionFailure設定,如果true則隻進行Monitor GC,如果false則進行 Full GC

五.假設一個場景,要求stop the world時間非常短,你會怎麼會設計垃圾回收機制

  • 絕大多數新建立的對象配置設定在Eden區
  • 在Eden區發生一次GC後,存活的對象移到其中一個Survivor區
  • 在Eden區發生一次GC後,對象是放到Survivor區,這個Survivor區已經存在其他存活的對象
  • 一旦一個Survivor區已滿,存活的對象移動到另外一個Survivor區,然後之前那個空間已滿Survivor區将置空,沒有任何資料
  • 經過重複多次這樣的步驟後依舊存活的對象将被移動到老年代

六.Eden區和Survial區的含義和工作原理

  • 目前主流的虛拟機實作都采用了分代收集的思想,把整個堆區劃分為新生代和老年代,老年代又被劃分為Eden空間,From Survivor和 To Survivor 三塊區域
  • 我們把Eden:From Survivor:To Survivor空間大小設成8:1:1,對象總是在Eden區出生,From Survivor儲存目前的幸存對象,To Survivor為空.一次GC發生後:
  • 1.Eden區活着的對象+From Survivor存儲的對象被複制到To Survivor
  • 2.清空Eden 和 From Survivor
  • 3.颠倒From Survivor和To Survivor的邏輯關系:From變To,To變From.
  • 可以看出,隻有在Eden區快滿的時候才會觸發Minor GC.而Eden空間占據新生代的絕大部分,是以Minor GC的頻率得以降低.當然,使用兩個Survivor這種方式我們也付出了一定的代價,如

    10%的空間浪費,複制對象的開銷等

七.垃圾回收算法

标記清除

  • 标記-清除算法将垃圾回收分為兩個階段L标記階段和清除階段.在标記階段首先通過根結點,标記所有從根節點開始的對象,未被标記的對象就是未被引用的垃圾對象.然後在清除階段,清除所有未被标記的對象.标記清除算法帶來的一個問題是會存在大量的空間碎片,因為回收後的空間是不連續的,這樣給大對象配置設定記憶體的時候可能會提前觸發full gc

複制算法

  • 将現有的記憶體空間分為兩塊,每次隻使用其中一塊,在垃圾回收時将正在使用的記憶體中的存活對象複制到未被使用的記憶體塊中,之後,清除正在使用的記憶體塊中的所有對象,交換兩個記憶體的角色,完成垃圾回收
  • 現有的商業虛拟機都采用這種收集算法來回收新生代,IBM研究表明新生代中的對象98%都是朝夕生死的,是以并不需要按照1:1的比例劃分記憶體空間,而是将記憶體劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中的一塊Survivor.當回收時,将Eden和Survivor中還存活的對象一次性地拷貝到另外一個Survivor空間上,最後清理掉Eden和剛才用過的Survivor空間.HotSpot虛拟機預設Eden和Survivor地大小比例是8:1(可以通過SurvivorRattio來配置),也就是每次新生代中可用記憶體空間為整個新生代容量地90%,隻有10%地記憶體會被"浪費".當然,98%的對象可回收隻是一般場景下的資料,我們也沒有辦法保證回收都隻有不多于10%的對象存活,當Survivor空間不夠用時嗎,需要依賴其他記憶體(這裡指老年代)進行配置設定擔保

标記整理

  • 複制算法的高效性是建立在存活對象少,垃圾對象多的前提下的.這種情況在新生代經常發生,但是在老年代更常見的情況是大部分對象都是存活對象.如果依然使用複制算法,由于存活的對象較多,複制的成本也很高
  • 标記-壓縮算法是一種老年代的回收算法,他在标記-清除的算法的基礎上做了一些優化.首先也需要從根節點開始對所有可達對象做一次标記,但之後,他并不簡單地清理未标記的對象,而是将所有的存活對象壓縮到記憶體的一端.之後,清理邊界外所有的空間.這種方法既避免了碎片的産生,又不需要兩塊相同的記憶體空間,是以,成本效益比較高

增量算法

  • 增量算法的基本思想是,如果一次性将所有的垃圾進行處理,需要造成系統長時間的停頓,那麼就可以讓垃圾收集線程和應用程式線程交替運作.每次,垃圾收集線程隻收集一小片區域的記憶體空間,接着切換到應用程式線程,依次反複,直到垃圾收集完成.使用這種方法,由于在垃圾回收過程中,間斷性地還執行了應用程式代碼,是以能減少系統地停頓時間.但是,由于線程切換和上下文轉換的消耗,會使得垃圾回收的總體成本上升,造成系統吞吐量的下降

八.垃圾回收器

1.Serial收集器

  • Serial收集器是最古老的收集器,他的缺點是當Serial收集器想進行垃圾回收時,必須暫停使用者的所有程序,即stop the world.到現在為止,他依然是虛拟機運作在client模式下的預設新生代收集器,與其他收集器相比,對于限定在單個CPU的運作環境來說,Serial收集器由于沒有線程互動的開銷,專心做垃圾回收自然可以獲得最高的單線程收集效率\
  • Serial Old是Serial收集器的老年代版本,它同樣是一個單線程收集器,使用"标記-整理"算法.這個收集器的主要意義也是client模式下的虛拟機使用.Serial模式下,它主要還有兩個用途:一個是在jdk1.5以及以前的版本中與ParallelSscanvenge收集器搭配使用,另外一個就是作為CMS收集器的後備預案,在并發收集發生Concurrent Mode Failure的時候使用
  • 通過指定-UseSerialGC參數,使用Serial+Serial Old的串行收集器組合進行記憶體回收

2.ParNew 收集器

  • ParNew收集器是Serial收集器新生代的多線程實作,注意在進行垃圾回收的時候依然會stop the world,隻是相比較Serial收集器而言它會運作多條程序進行垃圾回收
  • ParNew收集器在單CPU的環境中絕對不會比Serial收集器有更好的效果,甚至由于存線上程互動的開銷,該收集器在通過超線程奇數實作的兩個CPU的環境中都不能百分百的保證能超越Serial收集器.當然,随着可以使用的CPU數量增加,它對于GC時系統資源的利用還是很有好處的.它預設開啟的收集線程數與CPU數量相同,在CPU非常多(比如32個,現在CPU最少4核加超線程,伺服器超過32個邏輯CPU的情況越來越多)的環境下,可以使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數
  • -UseParNewGC:打開此開關後,使用ParNew+Serial Old的收集器組合進行記憶體回收,這樣新生代使用并行收集器,老年代使用串行收集器

3.Parallel Scavenge收集器

  • Parallel是采用複制算法的多線程新生代垃圾回收器,似乎和ParNew收集器有很多相似的地方.但是Parallel Scanvenge收集器的一個特點是它所關注的目标是吞吐量(Throughput).所謂吞吐量就是CPU用于運作使用者代碼的時間與CPU總消耗時間的比值,即吞吐量=運作運作使用者代碼時間/(運作使用者代碼時間+垃圾收集時間).停頓時間越短就越适合需要與使用者互動的程式,良好的響應速度能夠提升使用者的體驗;而高吞吐量則可以最高效率地利用CPU時間,盡快地完成程式地運算任務,主要适合在背景運算而不需要太多互動地任務.
  • Parallel Old收集器 是Parallel Scavenge收集器的老年代版本,采用多線程和标記整理算法.這個收集器是在jdk1.6中才開始提供的.在此之前,新生代的Parallel Scavenge收集器一直處于比較尴尬的狀态.原因是如果新生代Parallel Scavenge收集器,那麼老年代除了Serial Old(PS MarkSweep)收集器外别無選擇.由于單線程的老年代Serial Old收集器在服務端應用性能上的拖累,即使使用了Parallel Scavenge收集器也未必能在整體應用上獲得吞吐量最大化的效果,又因為老年代收集中無法充分利用伺服器多CPU的處理能力,在老年代很大而且硬體比較進階的環境中,這種組合的吞吐量甚至還不一定有ParNew加CMS組合給力.直到Parallel Old收集器出現後,吞吐量優先收集器終于有了比較名副其實的應用祝賀,在注重吞吐量和CPU資源敏感的場合,都可以考慮Parallel Scavenge加Parallel Old收集器
  • -UseParallelGC:虛拟機運作在Server模式下的預設值,打開此開關後,使用Parallel Scavenge+Serial Old 的收集器組合進行記憶體回收.-UseParallelOldGC:打開此開關後,使用Parallel Scavenge+Parallel Old的收集器組合進行垃圾回收

4.CMS收集器

  • CMS(Concurrent Mark Sweep)收集器是一個比較重要的收集器,現在應用非常廣泛,我們重點來看下,CMS一種擷取最短回收停頓時間為目标的收集器,這使得它很适用于和使用者互動的業務,從名字就可以看出,CMS收集器是基于标記和清除算法實作的,分為四個步驟
  • 1)初始标記(initial mark)
  • 2)并發标記(concurrent mark)
  • 3)重新标記(remark)
  • 4)并發清除(concurrent sweep)
  • 注意初始标記和重新标記還是會stop the world,但是在耗費時間更長的并發标記和并發清除兩個階段都可以和使用者程序同時工作

5.G1收集器

  • G1收集器是一款面向服務端應用的垃圾收集器.HotSpot團隊賦予它的使命是在未來代替換掉jdk1.5中釋出的CMS收集器.與其他GC收集器相比,G1具備如下特點:
  • 并發與并發:G1能更充分的利用CPU,多核環境下的硬體優勢來縮短stop the world的停頓時間
  • 分代收集:和其他收集器一樣,分代的概念在G1中依然存在,不過G1不需要其他的垃圾回收器的配合就可以獨自管理整個GC堆
  • 空間整合:G1收集器有利于程式長時間運作,配置設定大對象時不會無法得到連續的空間而提前觸發一次GC
  • 可預測的非停頓:這是G1相對于CMS的另一大優勢,降低停頓時間是G1和CMS共同的關注點,能讓使用者明确指定在一個長度為M毫秒的時間片段内,消耗在垃圾收集器的時間不得超過N毫秒

九.主要的JVM參數

1.堆棧配置相關

java-Xmx3550m-Xms3550m-Xmn2g-Xss128k
-XX:MaxPermSize=16m-XX:NewRatio=4-XX:SurvivorRatio=4-XX:MaxTenuringThreshold=0
//-Xmx3550m:	最大堆大小為3550m
//-Xms3550m:	設定初始堆大小為3550m
//-Xmn2g:		設定年輕代大小為2g
//-Xss128k:		每個線程的堆棧大小為128k
//-XX:MaxPermSize:	設定持久代大小為16m
//-XX:NewRatio=4:	設定年輕代(包括Eden和兩個Survivor區)與老年代的比值(除去持久代)
//-XX:SurvivorRation=4:	設定年輕代中Eden區與Survivor區的大小比值.設定為4,則兩個Survivor區與一個Eden的比值為2:4,一個Survivor區占整個年輕代的1/6
//-XX:MaxTenuringThreshold=0:	設定垃圾最大年齡.如果設定為0的話,則年輕對象不經過Survivor區,直接進入老年代
           

2.垃圾收集器相關

-XX:+UseParallelGC 					//選擇垃圾收集器為并行收集器
-XX:ParallelGCThreads=20			//配置并行收集器的線程數
-XX:+UseConcMarkSweepGC				//設定老年代為并發收集
-XX:CMSFullGCsBeforeCompaction=5	//由于并發收集器不對記憶體空間進行壓縮整理,是以運作一段時間後會産生碎片,使得運作效率降低.此值設定運作多少次GC後對記憶體空間進行壓縮整理
-XX:+UseCMSCompactAtFullCollection:	//打開對老年代的壓縮.可能會影響性能,但可以消除碎片
           

3.輔助資訊相關

-XX:+PrintGC //輸出形式
-XX:+PrintGCDetails //輸出形式
           

繼續閱讀