爲了能讓您更加方便的閱讀
本文全部的面試題目均已整理至小程序《面試手冊》
能夠經過微信掃描(或長按)下圖的二維碼享受更好的閱讀體驗!java
目錄android
1. 有時候也成爲永久代,在該區內不多發生垃圾回收,可是並不表明不發生 GC,在這裏進行的 GC 主要是對方法區裏的常量池和對類型的卸載 2. 方法區主要用來存儲已被虛擬機加載的類的信息、常量、靜態變量和即時編譯器編譯後的代碼等數據。 3. 該區域是被線程共享的。 4. 方法區裏有一個運行時常量池,用於存放靜態編譯產生的字面量和符號引用。該常量池具備動態性,也就是說常量並不必定是編譯時肯定,運行時生成的常量也會存在這個常量池中。
虛擬機棧也就是咱們日常所稱的棧內存,它爲 java 方法服務,每一個方法在執行的時候都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接和方法出口等信息。程序員
虛擬機棧是線程私有的,它的生命週期與線程相同。面試
局部變量表裏存儲的是基本數據類型、returnAddress 類型(指向一條字節碼指令的地址)和對象引用,這個對象引用有多是指向對象起始地址的一個指針,也有多是表明對象的句柄或者與對象相關聯的位置。局部變量所需的內存空間在編譯器間肯定算法
操做數棧的做用主要用來存儲運算結果以及運算的操做數,它不一樣於局部變量表經過索引來訪問,而是壓棧和出棧的方式編程
每一個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接.動態連接就是將常量池中的符號引用在運行期轉化爲直接引用。小程序
本地方法棧和虛擬機棧相似,只不過本地方法棧爲 Native 方法服務。數組
java 堆是全部線程所共享的一塊內存,在虛擬機啓動時建立,幾乎全部的對象實例都在這裏建立,所以該區域常常發生垃圾回收操做。瀏覽器
內存空間小,字節碼解釋器工做時經過改變這個計數值能夠選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理和線程恢復等功能都須要依賴這個計數器完成。該內存區域是惟一一個 java 虛擬機規範沒有規定任何 OOM 狀況的區域。緩存
Java程序須要經過 JVM 棧上的引用訪問堆中的具體對象。對象的訪問方式取決於 JVM 虛擬機的實現。目前主流的訪問方式有 句柄 和 直接指針 兩種方式。
Java堆中劃分出一塊內存來做爲句柄池,引用中存儲對象的句柄地址,而句柄中包含了對象實例數據與對象類型數據各自的具體地址信息,具體構造以下圖所示:
優點:引用中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象是很是廣泛的行爲)時只會改變句柄中的實例數據指針,而引用自己不須要修改。
若是使用直接指針訪問,引用 中存儲的直接就是對象地址,那麼Java堆對象內部的佈局中就必須考慮如何放置訪問類型數據的相關信息。
優點:速度更快,節省了一次指針定位的時間開銷。因爲對象的訪問在Java中很是頻繁,所以這類開銷聚沙成塔後也是很是可觀的執行成本。HotSpot 中採用的就是這種方式。
java 內存模型(JMM)是線程間通訊的控制機制.JMM 定義了主內存和線程之間抽象關係。線程之間的共享變量存儲在主內存(main memory)中,每一個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是 JMM 的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其餘的硬件和編譯器優化。Java 內存模型的抽象示意圖以下:
線程 A 與線程 B 之間如要通訊的話,必需要經歷下面 2 個步驟:
垃圾回收器(garbage colector)決定回收某對象時,就會運行該對象的finalize()方法;
finalize是Object類的一個方法,該方法在Object類中的聲明protected void finalize() throws Throwable { }
在垃圾回收器執行時會調用被回收對象的finalize()方法,能夠覆蓋此方法來實現對其資源的回收。注意:一旦垃圾回收器準備釋放對象佔用的內存,將首先調用該對象的finalize()方法,而且下一次垃圾回收動做發生時,才真正回收對象佔用的內存空間
GC原本就是內存回收了,應用還須要在finalization作什麼呢? 答案是大部分時候,什麼都不用作(也就是不須要重載)。只有在某些很特殊的狀況下,好比你調用了一些native的方法(通常是C寫的),能夠要在finaliztion裏去調用C的釋放函數。
淺拷貝(shallowCopy)只是增長了一個指針指向已存在的內存地址,
深拷貝(deepCopy)是增長了一個指針而且申請了一個新的內存,使這個增長的指針指向這個新的內存,
使用深拷貝的狀況下,釋放內存的時候不會由於出現淺拷貝時釋放同一個內存的錯誤。
淺複製:僅僅是指向被複制的內存地址,若是原地址發生改變,那麼淺複製出來的對象也會相應的改變。
深複製:在計算機中開闢一塊新的內存地址用於存放複製的對象。
物理地址
堆的物理地址分配對對象是不連續的。所以性能慢些。在GC的時候也要考慮到不連續的分配,因此有各類算法。好比,標記-消除,複製,標記-壓縮,分代(即新生代使用複製算法,老年代使用標記——壓縮)
棧使用的是數據結構中的棧,先進後出的原則,物理地址分配是連續的。因此性能快。
內存分別
存放的內容
程序的可見度
隊列和棧都是被用來預存儲數據的。
內存泄漏是指再也不被使用的對象或者變量一直被佔據在內存中。理論上來講,Java是有GC垃圾回收機制的,也就是說,再也不被使用的對象,會被GC自動回收掉,自動從內存中清除。
可是,即便這樣,Java也仍是存在着內存泄漏的狀況,java致使內存泄露的緣由很明確:長生命週期的對象持有短生命週期對象的引用就極可能發生內存泄露,儘管短生命週期對象已經再也不須要,可是由於長生命週期對象持有它的引用而致使不能被回收,這就是java中內存泄露的發生場景。
Java對象由三個部分組成:對象頭、實例數據、對齊填充。
對象頭由兩部分組成
實例數據用來存儲對象真正的有效信息(包括父類繼承下來的和本身定義的)
對齊填充:JVM要求對象起始地址必須是8字節的整數倍(8字節對齊)
判斷一個對象是否存活有兩種方法:
所謂引用計數法就是給每個對象設置一個引用計數器,每當有一個地方引用這個對象時,就將計數器加一,引用失效時,計數器就減一。當一個對象的引用計數器爲零時,說明此對象沒有被引用,也就是「死對象」,將會被垃圾回收.
引用計數法有一個缺陷就是沒法解決循環引用問題,也就是說當對象 A 引用對象 B,對象 B 又引用者對象 A,那麼此時 A,B 對象的引用計數器都不爲零,也就形成沒法完成垃圾回收,因此主流的虛擬機都沒有采用這種算法。
該算法的思想是:從一個被稱爲 GC Roots 的對象開始向下搜索,若是一個對象到 GC Roots 沒有任何引用鏈相連時,則說明此對象不可用。在 java 中能夠做爲 GC Roots 的對象有如下幾種:
雖然這些算法能夠斷定一個對象是否能被回收,可是當知足上述條件時,一個對象不必定會被回收。當一個對象不可達 GC Root 時,這個對象並不會立馬被回收,而是出於一個死緩的階段,若要被真正的回收須要經歷兩次標記
若是對象在可達性分析中沒有與 GC Root 的引用鏈,那麼此時就會被第一次標記而且進行一次篩選,篩選的條件是是否有必要執行 finalize()方法。當對象沒有覆蓋 finalize()方法或者已被虛擬機調用過,那麼就認爲是不必的。
若是該對象有必要執行 finalize()方法,那麼這個對象將會放在一個稱爲 F-Queue 的對隊列中,虛擬機會觸發一個 Finalize()線程去執行,此線程是低優先級的,而且虛擬機不會承諾一直等待它運行完,這是由於若是 finalize()執行緩慢或者發生了死鎖,那麼就會形成 F-Queue 隊列一直等待,形成了內存回收系統的崩潰。GC 對處於 F-Queue 中的對象進行第二次被標記,這時,該對象將被移除」即將回收」集合,等待回收。
在 java 中,程序員是不須要顯示的去釋放一個對象的內存的,而是由虛擬機自行執行。在JVM 中,有一個垃圾回收線程,它是低優先級的,在正常狀況下是不會執行的,只有在虛擬機空閒或者當前堆內存不足時,纔會觸發執行,掃面那些沒有被任何引用的對象,並將它們添加到要回收的集合中,進行回收。
垃圾回收是在內存中存在沒有引用的對象或超過做用域的對象時進行的。
垃圾回收的目的是識別而且丟棄應用再也不使用的對象來釋放和重用資源。
不會,在下一個垃圾回調週期中,這個對象將是被可回收的。
也就是說並不會當即被垃圾收集器馬上回收,而是在下一次垃圾回收時纔會釋放其佔用的內存。
GC是垃圾收集的意思,內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會致使程序或系統的不穩定甚至崩潰,Java提供的GC功能能夠自動監測對象是否超過做用域從而達到自動回收內存的目的,Java語言沒有提供釋放已分配內存的顯示操做方法。Java程序員不用擔憂內存管理,由於垃圾收集器會自動進行管理。要請求垃圾收集,能夠調用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM能夠屏蔽掉顯示的垃圾回收調用。 垃圾回收能夠有效的防止內存泄露,有效的使用可使用的內存。垃圾回收器一般是做爲一個單獨的低優先級的線程運行,不可預知的狀況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或全部對象進行垃圾回收。在Java誕生初期,垃圾回收是Java最大的亮點之一,由於服務器端的編程須要有效的防止內存泄露問題,然而時過境遷,現在Java的垃圾回收機制已經成爲被詬病的東西。移動智能終端用戶一般以爲iOS的系統比Android系統有更好的用戶體驗,其中一個深層次的緣由就在於android系統中垃圾回收的不可預知性。
補充:垃圾回收機制有不少種,包括:分代複製垃圾回收、標記垃圾回收、增量垃圾回收等方式。標準的Java進程既有棧又有堆。棧保存了原始型局部變量,堆保存了要建立的對象。Java平臺對堆內存回收和再利用的基本算法被稱爲標記和清除,可是Java對其進行了改進,採用「分代式垃圾收集」。這種方法會跟Java對象的生命週期將堆內存劃分爲不一樣的區域,在垃圾收集過程當中,可能會將對象移動到不一樣區域:
-Xms / -Xmx:堆的初始大小 / 堆的最大大小
-Xmn:堆中年輕代的大小
-XX:-DisableExplicitGC:讓System.gc()不產生任何做用
-XX:+PrintGCDetails:打印GC的細節
-XX:+PrintGCDateStamps:打印GC操做的時間戳
-XX:NewSize / XX:MaxNewSize: 設置新生代大小/新生代最大大小
-XX:NewRatio :能夠設置老生代和新生代的比例
-XX:PrintTenuringDistribution :設置每次新生代GC後輸出倖存者樂園中對象年齡的分佈
-XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設置老年代閥值的初始值和最大值
-XX:TargetSurvivorRatio:設置倖存區的目標使用率
對於GC來講,當程序員建立對象時,GC就開始監控這個對象的地址、大小以及使用狀況。
不會;一般,GC採用有向圖的方式記錄和管理堆(heap)中的全部對象。經過這種方式肯定哪些對象是"可達的",哪些對象是"不可達的"。當GC肯定一些對象爲"不可達"時,GC就有責任回收這些內存空間。而這個回收操做時達到必定閾值或者條件以後纔會觸發回收;並非實時的。
能夠。程序員能夠手動執行System.gc(),通知GC運行,可是Java語言規範並不保證GC必定會執行。
垃圾回收不會發生在永久代,若是永久代滿了或者是超過了臨界值,會觸發徹底垃圾回收(Full GC)。若是你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是爲何正確的永久代大小對避免Full GC是很是重要的緣由。
java 8中,永久代被移除,取而代之的爲元空間。
標記-清除:
這是垃圾收集算法中最基礎的,根據名字就能夠知道,它的思想就是標記哪些要被回收的對象,而後統一回收。這種方法很簡單,可是會有兩個主要問題:1.效率不高,標記和清除的效率都很低;2.會產生大量不連續的內存碎片,致使之後程序在分配較大的對象時,因爲沒有充足的連續內存而提早觸發一次 GC 動做。
複製算法:
爲了解決效率問題,複製算法將可用內存按容量劃分爲相等的兩部分,而後每次只使用其中的一塊,當一塊內存用完時,就將還存活的對象複製到第二塊內存上,而後一次性清楚完第一塊內存,再將第二塊上的對象複製到第一塊。可是這種方式,內存的代價過高,每次基本上都要浪費通常的內存。
因而將該算法進行了改進,內存區域再也不是按照 1:1 去劃分,而是將內存劃分爲 8:1:1 三部分,較大那分內存交 Eden 區,其他是兩塊較小的內存區叫 Survior 區。每次都會優先使用 Eden 區,若 Eden 區滿,就將對象複製到第二塊內存區上,而後清除 Eden 區,若是此時存活的對象太多,以致於 Survivor 不夠時,會將這些對象經過分配擔保機制複製到老年代中。(java 堆又分爲新生代和老年代)
標記-整理
該算法主要是爲了解決標記-清除,產生大量內存碎片的問題;當對象存活率較高時,也解決了複製算法的效率問題。它的不一樣之處就是在清除對象的時候現將可回收對象移動到一端,而後清除掉端邊界之外的對象,這樣就不會產生內存碎片了。
分代收集
如今的虛擬機垃圾收集大多采用這種方式,它根據對象的生存週期,將堆分爲新生代和老年代。在新生代中,因爲對象生存期短,每次回收都會有大量對象死去,那麼這時就採用複製算法。老年代裏的對象存活率較高,沒有額外的空間進行分配擔保,因此可使用標記-整理 或者 標記-清除。
標記無用對象,而後進行清除回收。
標記-清除算法(Mark-Sweep)是一種常見的基礎垃圾收集算法,它將垃圾收集分爲兩個階段:
標記-清除算法之因此是基礎的,是由於後面講到的垃圾收集算法都是在此算法的基礎上進行改進的。
優勢:實現簡單,不須要對象進行移動。
缺點:標記、清除過程效率低,產生大量不連續的內存碎片,提升了垃圾回收的頻率。
標記-清除算法的執行的過程以下圖所示
爲了解決標記-清除算法的效率不高的問題,產生了複製算法。它把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾收集時,遍歷當前使用的區域,把存活對象複製到另一個區域中,最後將當前使用的區域的可回收的對象進行回收。
優勢:
按順序分配內存便可,實現簡單、運行高效,不用考慮內存碎片。
缺點:
可用的內存大小縮小爲原來的一半,對象存活率高時會頻繁進行復制。
複製算法的執行過程以下圖所示
在新生代中可使用複製算法,可是在老年代就不能選擇複製算法了,由於老年代的對象存活率會較高,這樣會有較多的複製操做,致使效率變低。標記-清除算法能夠應用在老年代中,可是它效率不高,在內存回收後容易產生大量內存碎片。所以就出現了一種標記-整理算法(Mark-Compact)算法,與標記-整理算法不一樣的是,在標記可回收的對象後將全部存活的對象壓縮到內存的一端,使他們緊湊的排列在一塊兒,而後對端邊界之外的內存進行回收。回收後,已用和未用的內存都各自一邊。
優勢:
解決了標記-清理算法存在的內存碎片問題。
缺點:
仍須要進行局部對象移動,必定程度上下降了效率。
標記-整理算法的執行過程以下圖所示
當前商業虛擬機都採用分代收集的垃圾收集算法。分代收集算法,顧名思義是根據對象的存活週期將內存劃分爲幾塊。通常包括年輕代、老年代 和 永久代
如圖所示:
若是說垃圾收集算法是內存回收的方法論,那麼垃圾收集器就是內存回收的具體實現。下圖展現了7種做用於不一樣分代的收集器,其中用於回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,還有用於回收整個Java堆的G1收集器。不一樣收集器之間的連線表示它們能夠搭配使用。
CMS 是英文 Concurrent Mark-Sweep 的簡稱,是以犧牲吞吐量爲代價來得到最短回收停頓時間的垃圾回收器。對於要求服務器響應速度的應用上,這種垃圾回收器很是適合。在啓動 JVM 的參數加上「-XX:+UseConcMarkSweepGC」來指定使用 CMS 垃圾回收器。
CMS 使用的是標記-清除的算法實現的,因此在 gc 的時候回產生大量的內存碎片,當剩餘內存不能知足程序運行要求時,系統將會出現 Concurrent Mode Failure,臨時 CMS 會採用 Serial Old 回收器進行垃圾清除,此時的性能將會被下降。
新生代垃圾回收器通常採用的是複製算法,複製算法的優勢是效率高,缺點是內存利用率低;老年代回收器通常採用的是標記-整理的算法進行垃圾回收。
分代回收器有兩個分區:老生代和新生代,新生代默認的空間佔比總空間的 1/3,老生代的默認佔比是 2/3。
新生代使用的是複製算法,新生代裏有 3 個分區:Eden、To Survivor、From Survivor,它們的默認佔比是 8:1:1,它的執行流程以下:
每次在 From Survivor 到 To Survivor 移動時都存活的對象,年齡就 +1,當年齡到達 15(默認配置是 15)時,升級爲老生代。大對象也會直接進入老生代。
老生代當空間佔用到達某個值以後就會觸發全局垃圾收回,通常使用標記整理的執行算法。以上這些循環往復就構成了整個分代垃圾回收的總體執行流程。
5種場景會觸發類加載:
遇到new,getstatic,putstatic或invokestatic這四條字節碼指令時,若是類沒有進行過初始化,則須要先觸發初始化
使用java.lang.reflect包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則須要先觸發其初始化,
當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化
當虛擬機啓動時,用戶須要指定一個要執行的主類(包含main方法的類),虛擬機會先初始化這個類
當時用,JDK1.7的動態語言支持時,若是一個java.lang.invoke.MethodHandle實例後的解析結果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且這個方法句柄對應的類尚未進行過初始化,則須要先觸發其初始化
類從被加載到虛擬機內存開始,到卸載出內存爲止,整個生命週期包括:加載,驗證,準備,解析,初始化,使用和卸載
加載是類加載過程的一個階段,在加載階段虛擬機須要完成三件事
驗證就是確保Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全
準備階段是正式爲類靜態變量分配內存並設置類變量初始值的階段,這些變量所使用的內存都在方法區中進行分配
public static int value = 123 //在準備階段 value的值是 0 並非123 public static final int value = 123 // 準備階段value 的值爲123
若是屬性有Constant Value 屬性,那麼在準備階段變量就會被初始化爲所指定的值
這時候進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨着對象一塊分配在Java堆中
這裏所設置的初始值一般狀況下是數據類型默認的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值
解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程
直接引用
直接引用可使直接指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄
符號引用
符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時能無歧義的定位到目標便可
主要包含:
類或接口的解析
字段解析
類方法解析
接口方法解析
初始化,爲類的靜態變量賦予正確的初始值,JVM負責對類進行初始化,主要對類變量進行初始化。在Java中對類變量進行初始值設定有兩種方式:
JVM初始化步驟:
類的初始化
Java中的全部類,都須要由類加載器裝載到JVM中才能運行。類加載器自己也是一個類,而它的工做就是把class文件從硬盤讀取到內存中。在寫程序的時候,咱們幾乎不須要關心類的加載,由於這些都是隱式裝載的,除非咱們有特殊的用法,像是反射,就須要顯式的加載所須要的類。
類裝載方式,有兩種 :
隱式裝載
程序在運行過程當中當碰到經過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中,
顯式裝載,
經過class.forname()等方法,顯式加載須要的類
Java類的加載是動態的,它並不會一次性將全部類所有加載後再運行,而是保證程序運行的基礎類(像是基類)徹底加載到jvm中,至於其餘類,則在須要的時候才加載。這固然就是爲了節省內存開銷。
實現經過類的權限定名獲取該類的二進制字節流的代碼塊叫作類加載器。
主要有一下四種類加載器:
在介紹雙親委派模型以前先說下類加載器。對於任意一個類,都須要由加載它的類加載器和這個類自己一同確立在 JVM 中的惟一性,每個類加載器,都有一個獨立的類名稱空間。類加載器就是根據指定全限定名稱將 class 文件加載到 JVM 內存,而後再轉化爲 class 對象。
類加載器分類:
雙親委派模型:若是一個類加載器收到了類加載的請求,它首先不會本身去加載這個類,而是把這個請求委派給父類加載器去完成,每一層的類加載器都是如此,這樣全部的加載請求都會被傳送到頂層的啓動類加載器中,只有當父加載沒法完成加載請求(它的搜索範圍中沒找到所需的類)時,子加載器纔會嘗試去加載類。
當一個類收到了類加載請求時,不會本身先去加載這個類,而是將其委派給父類,由父類去加載,若是此時父類不能加載,反饋給子類,由子類去完成類的加載。
經常使用調優工具分爲兩類,jdk自帶監控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。
摘錄GC日誌一部分(前部分爲年輕代gc回收;後部分爲full gc回收):
20xx-0x-0xT10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs] 20xx-0x-0xT10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]
經過上面日誌分析得出,PSYoungGen、ParOldGen、PSPermGen屬於Parallel收集器。其中PSYoungGen表示gc回收先後年輕代的內存變化;ParOldGen表示gc回收先後老年代的內存變化;PSPermGen表示gc回收先後永久區的內存變化。young gc 主要是針對年輕代進行內存回收比較頻繁,耗時短;full gc 會對整個堆內存進行回城,耗時長,所以通常儘可能減小full gc的次數
Sun JDK監控和故障處理命令有jps jstat jmap jhat jstack jinfo
設定堆內存大小
-Xmx:堆內存最大限制。
設定新生代大小。 新生代不宜過小,不然會有大量對象涌入老年代
-XX:NewSize:新生代大小
-XX:NewRatio 新生代和老生代佔比
-XX:SurvivorRatio:伊甸園空間和倖存者空間的佔比
設定垃圾回收器
年輕代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC
感謝您的點贊、評論、關注;
您還能夠掃碼關注「公衆號」獲取粉絲福利。