2019年java中高級java面試題(九)java內存模型和jvm

1、運行時數據區包含那幾個部分?

Java運行時數據區分爲下面幾個內存區域:

 

程序計數器 

程序計數器是一塊較小的內存空間,可以看作是當前線程所執行的字節碼的行號指示器。

java虛擬機棧

每當創建一個線程,JVM就會爲該線程創建對應的Java棧,在這個Java棧中又會包含多個棧幀(Stack Frame),這些棧幀是與每個方法關聯起來的,每運行一個方法就創建一個棧幀,每個棧幀會含有一些局部變量、操作棧和方法返回值等信息。下圖是棧幀結構 

本地方法棧 

本地方法棧和虛擬機棧作用相似,本地方法執行的是native方法

java堆

堆是JVM所管理的內存中國最大的一塊,是被所有Java線程鎖共享的,不是線程安全的,在JVM啓動時創建。堆是存儲Java對象的地方,這一點Java虛擬機規範中描述是:所有的對象實例以及數組都要在堆上分配。Java堆是GC管理的主要區域,從內存回收的角度來看,由於現在GC基本都採用分代收集算法,所以Java堆還可以細分爲:新生代和老年代;新生代再細緻一點有Eden空間、From Survivor空間、To Survivor空間等。 

方法區

方法區是各個線程共享的內存區域,它用於存儲類加載信息、類中的靜態常量、類中定義爲final類型的常量、類中的Field信息、類中的方法信息,當在程序中通過Class對象的getName.isInterface等方法來獲取信息時,這些數據都來源於方法區。方法區是被Java線程鎖共享的,不像Java堆中其他部分一樣會頻繁被GC回收,它存儲的信息相對比較穩定,在一定條件下會被GC。 
 

運行時常量池

運行時常量池 ---- 存在於內存的元空間中

字符串常量池 ---- 存在於堆中

2、如何判斷對象是否存活?

引用計數法 

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器

值就減1;任何時刻計數器爲0的對象就是不可能再被使用的。

可達性分析算法(根搜索算法)

過一系列的稱爲“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲

引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的

在Java語言中,可作爲GC Roots的對象包括下面幾種:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象。
  • 方法區中類靜態屬性引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中JNI(即一般說的Native方法)引用的對象。

3、常用的垃圾回收算法

標記-清除算法 

標記-清除算法採用從根集合(GC Roots)進行掃描,對存活的對象進行標記,標記完畢後,再掃描整個空間中未被標記的對象,進行回收,此算法一般沒有虛擬機採用。

複製算法 
將內存分成兩塊容量大小相等的區域,每次只使用其中一塊,當這一塊內存用完了,就將所有存活對象複製到另一塊內存空間,然後清除前一塊內存空間。這樣一來就不容易出現內存碎片的問題。 

標記-整理算法 
思想:在完成標記之後,它不是直接清理可回收對象,而是將存活對象都向一端移動,然後清理掉端邊界以外的內存。 
不會產生內存碎片,但是依舊移動對象的成本

分代收集算法

分代收集算法是目前大部分JVM的垃圾收集器採用的算法。它的核心思想是根據對象存活的生命週期將內存劃分爲若干個不同的區域。一般情況下將堆區劃分爲老年代(Tenured Generation)和新生代(Young Generation),在堆區之外還有一個代就是永久代(Permanet Generation)。老年代的特點是每次垃圾收集時只有少量對象需要被回收,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收,那麼就可以根據不同代的特點採取最適合的收集算法
 

 4、常見的垃圾回收器

Serial收集器(複製算法) 
新生代單線程收集器,標記和清理都是單線程,優點是簡單高效。是client級別默認的GC方式,可以通過-XX:+UseSerialGC來強制指定。 
Serial Old收集器(標記-整理算法) 
老年代單線程收集器,Serial收集器的老年代版本。 
ParNew收集器(停止-複製算法)  
新生代收集器,可以認爲是Serial收集器的多線程版本,在多核CPU環境下有着比Serial更好的表現。 
Parallel Scavenge收集器(停止-複製算法)  
並行收集器,追求高吞吐量,高效利用CPU。吞吐量一般爲99%, 吞吐量= 用戶線程時間/(用戶線程時間+GC線程時間)。適合後臺應用等對交互相應要求不高的場景。是server級別默認採用的GC方式,可用-XX:+UseParallelGC來強制指定,用-XX:ParallelGCThreads=4來指定線程數。 
Parallel Old收集器(停止-複製算法) 
Parallel Scavenge收集器的老年代版本,並行收集器,吞吐量優先。 
CMS(Concurrent Mark Sweep)收集器(標記-清理算法) 
高併發、低停頓,追求最短GC回收停頓時間,cpu佔用比較高,響應時間快,停頓時間短,多核cpu 追求高響應時間的選擇。

 

垃圾回收器從線程運行情況分類有三種

串行回收,Serial回收器,單線程回收,全程stw;
並行回收,名稱以Parallel開頭的回收器,多線程回收,全程stw;
併發回收,cms與G1,多線程分階段回收,只有某階段會stw;
CMS收集器基於“標記-清除”算法實現,

優點:併發收集、低停頓

缺點:無法處理浮動垃圾導致Full GC產生容易出現大量空間碎片

G1從整體來看是基於“標記整理”算法實現的收集器;從局部上來看是基於“複製”算法實現的。

1、並行於併發:G1能充分利用CPU、多核環境下的硬件優勢,使用多個CPU(CPU或者CPU核心)來縮短stop-The-World停頓時間。

2、分代收集

3、建立可預測的停頓時間模型
 

5、 GC什麼時候被觸發的

由於對象進行了分代處理,因此垃圾回收區域、時間也不一樣。GC有兩種類型:Minor GC和Full GC

一般情況下,當新對象生成,並且在Eden申請空間失敗時,就會觸發Minor GC,對Eden區域進行GC,清除非存活對象,並且把尚且存活的對象移動到Survivor區。然後整理Survivor的兩個區。

Full GC 
對整個堆進行整理,包括Young、Tenured和Perm。Full GC因爲需要對整個堆進行回收,所以比Scavenge GC要慢,因此應該儘可能減少Full GC的次數。在對JVM調優的過程中,很大一部分工作就是對於Full GC的調節。有如下原因可能導致Full GC: 

  • 年老代(Tenured)被寫滿; 
  •  持久代(Perm)被寫滿; 
  •  System.gc()被顯示調用; 
  • 上一次GC之後Heap的各域分配策略動態變化

6、 jvm查看gc命令  

jstat -gc 12538 5000 


即會每5秒一次顯示進程號爲12538的java進成的GC情況

7、 類加載爲什麼要使用雙親委派模式,有沒有什麼場景是打破了這個模式?


在Java中判斷兩個類是否是同一個類,不僅僅是類的全限定名稱相同,還需要加載它們的類加載器相同。使用雙親委派模式,加載Object類時會始終使用啓動類加載器進行加載,而不會使用自定義類加載器,如果不使用雙親委派模式的話程序會混亂不堪。

JNDI服務打破了雙親委派模式。按照雙親委派模式,啓動類加載器會加載JNDI,此時啓動類加載器找到無法對各廠商具體實現,引入了ThreadContextClassLoader,父加載器會請求子加載器對其進行加載。


8、  類的實例化順序?


大致的順序是,先靜態方法、再構造方法,先父類後子類。

(1)父類靜態成員和靜態初始化塊,按代碼順序;

(2)子類靜態成員和靜態初始化塊,按代碼順序;

(3)父類實例成員和實例初始化塊,按代碼順序;

(4)父類構造方法;

(5)子類實例成員和實例初始化塊,按代碼順序;

(6)子類構造方法。
 

 

9、 java內存模型

java內存模型屏蔽了各種硬件和操作系統的內存訪問差異,讓Java達到一致的內存訪問效果。

java內存模型規定了所有變量都存儲在主內存中,每條線程都有自己的工作內存,線程對變量的操作都在工作內存中進行。

爲了主內存和工作內存的交互,java內存模型定義了8中原子操作

  • lock(鎖定):作用於主內存的變量,把一個變量標識爲一條線程獨佔狀態。
  • unlock(解鎖):作用於主內存變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定。
  • read(讀取):作用於主內存變量,把一個變量值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用
  • load(載入):作用於工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
  • use(使用):作用於工作內存的變量,把工作內存中的一個變量值傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值的字節碼指令時將會執行這個操作。
  • assign(賦值):作用於工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。
  • store(存儲):作用於工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以便隨後的write的操作。
  • write(寫入):作用於主內存的變量,它把store操作從工作內存中一個變量的值傳送到主內存的變量中。

 Java內存模型是圍繞着併發編程中原子性、可見性、有序性這三個特徵來建立的,那我們依次看一下這三個特徵:

  原子性(Atomicity):一個操作不能被打斷,要麼全部執行完畢,要麼不執行。在這點上有點類似於事務操作,要麼全部執行成功,要麼回退到執行該操作之前的狀態。

  基本類型數據的訪問大都是原子操作,long 和double類型的變量是64位,但是在32位JVM中,32位的JVM會將64位數據的讀寫操作分爲2次32位的讀寫操作來進行,這就導致了long、double類型的變量在32位虛擬機中是非原子操作,數據有可能會被破壞,也就意味着多個線程在併發訪問的時候是線程非安全的。

可見性:一個線程對共享變量做了修改之後,其他的線程立即能夠看到(感知到)該變量這種修改(變化)。

  Java內存模型是通過將在工作內存中的變量修改後的值同步到主內存,在讀取變量前從主內存刷新最新值到工作內存中,這種依賴主內存的方式來實現可見性的。

無論是普通變量還是volatile變量都是如此,區別在於:volatile的特殊規則保證了volatile變量值修改後的新值立刻同步到主內存,每次使用volatile變量前立即從主內存中刷新,因此volatile保證了多線程之間的操作變量的可見性,而普通變量則不能保證這一點。

  除了volatile關鍵字能實現可見性之外,還有synchronized,Lock,final也是可以的。

  使用synchronized關鍵字,在同步方法/同步塊開始時(Monitor Enter),使用共享變量時會從主內存中刷新變量值到工作內存中(即從主內存中讀取最新值到線程私有的工作內存中),在同步方法/同步塊結束時(Monitor Exit),會將工作內存中的變量值同步到主內存中去(即將線程私有的工作內存中的值寫入到主內存進行同步)。

  使用Lock接口的最常用的實現ReentrantLock(重入鎖)來實現可見性:當我們在方法的開始位置執行lock.lock()方法,這和synchronized開始位置(Monitor Enter)有相同的語義,即使用共享變量時會從主內存中刷新變量值到工作內存中(即從主內存中讀取最新值到線程私有的工作內存中),在方法的最後finally塊裏執行lock.unlock()方法,和synchronized結束位置(Monitor Exit)有相同的語義,即會將工作內存中的變量值同步到主內存中去(即將線程私有的工作內存中的值寫入到主內存進行同步)。

  final關鍵字的可見性是指:被final修飾的變量,在構造函數數一旦初始化完成,並且在構造函數中並沒有把“this”的引用傳遞出去(“this”引用逃逸是很危險的,其他的線程很可能通過該引用訪問到只“初始化一半”的對象),那麼其他線程就可以看到final變量的值。

  有序性:對於一個線程的代碼而言,我們總是以爲代碼的執行是從前往後的,依次執行的。這麼說不能說完全不對,在單線程程序裏,確實會這樣執行;但是在多線程併發時,程序的執行就有可能出現亂序。用一句話可以總結爲:在本線程內觀察,操作都是有序的;如果在一個線程中觀察另外一個線程,所有的操作都是無序的。前半句是指“線程內表現爲串行語義(WithIn Thread As-if-Serial Semantics)”,後半句是指“指令重排”現象和“工作內存和主內存同步延遲”現象。

Java提供了兩個關鍵字volatile和synchronized來保證多線程之間操作的有序性,volatile關鍵字本身通過加入內存屏障來禁止指令的重排序,而synchronized關鍵字通過一個變量在同一時間只允許有一個線程對其進行加鎖的規則來實現,

在單線程程序中,不會發生“指令重排”和“工作內存和主內存同步延遲”現象,只在多線程程序中出現。

 
10、 內存泄漏與內存溢出區別,產生原因?


內存溢出 out of memory,是指程序在申請內存時,沒有足夠的內存空間供其使用

內存泄露 memory leak,是指程序在申請內存後,無法釋放已申請的內存空間

內存溢出的原因:

1.內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;
2.集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;
3.代碼中存在死循環或循環產生過多重複的對象實體

4.啓動參數內存值設定的過小
 

 

11、如何解決內存溢出?

(1)用jmap生成堆信息

jmap -dump:format=b,file=dumpFileName pid

 (2)將文件導入Visual VM中進行分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章