1. 程式人生 > 遊戲資訊 >都2022年,不會還覺得PS新會員訂閱貴吧

都2022年,不會還覺得PS新會員訂閱貴吧

程式計數器:

程式計數器屬於執行緒的私有記憶體區域,記錄當前執行緒的執行位置,以供多執行緒執行時上下文切換,位元組碼直譯器通過程式計數器的增減來執行順序分支迴圈等結構。獨立於堆之外,因此程式計數器是唯一不會導致OutOfMemoryError的區域。

虛擬機器棧:

JAVA中說得棧其實就是虛擬機器棧,執行緒私有,每個java方法在呼叫時會建立一個棧幀,儲存區域性變量表 運算元棧 動態連結 返回地址 等資訊。

  • 區域性變量表:基本型別以及引用型別,引用型別是指堆中物件的引用。

Stackoverflowerror指的是虛擬機器棧不被允許動態擴充套件大小時,執行緒請求棧深度超過了虛擬機器棧的最大深度

OutOfMemoryError是虛擬機器棧允許動態擴充套件大小時,無法申請到記憶體空間。

本地方法棧:同虛擬機器棧類似。在Hotspot中合二為一。

方法如何呼叫?

每一次方法呼叫,都會向虛擬機器棧壓入一個棧幀,而每次return或丟擲異常,都會導致棧幀彈出。

堆:

執行緒共用,也就是執行緒不安全。是虛擬機器最大的一塊記憶體空間,此記憶體區域的唯一目的就是存放物件例項,幾乎所有的物件例項以及陣列都在這裡分配記憶體。是垃圾收集器管理的主要區域,因此也被稱作GC堆。根據垃圾回收機制,將堆分為新生區、老生區。

最容易出現兩類OutOfMemoryError,一類是垃圾回收代價太高、另一類是記憶體不夠。

靜態常量池:以class檔案形式儲存的常量,在類載入後,變為執行時常量池。

方法區(元空間、永久代):

執行緒共用,存放執行時常量池、靜態變數、類資訊等資料,在Hotspot中的實現之前為永久代,被元空間取代。元空間可以指定初始和最大空間。

元空間使用直接記憶體,若不指定最大空間,則元空間會盡可能使用實體記憶體,減少記憶體溢位

直接記憶體:

是在虛擬機器之外的記憶體,由NIO類使用native方法分配,然後通過在堆中的一個引用直接記憶體的buffer訪問

 

物件的建立:

  1. 虛擬機器在遇到new的時候,會首先在方法區中尋找該物件的類資訊是否載入,若沒有載入,則首先要載入該類。
  2. 給物件分配記憶體,有兩種方式。

指標碰撞:堆記憶體規整時,使用的記憶體和沒使用的記憶體中間有個邊界指標,把該指標往沒使用過的記憶體上移動要分配記憶體的大小那麼長的位置就行。

空閒列表:堆記憶體不規整時,虛擬機器維護一個列表記錄哪些區域是空閒的,分配的時候在列表上中找一塊足夠大的,然後更新記錄表。

  1. 給分配的記憶體空間初始化為0。
  2. 執行初始化方法。

 

物件的記憶體佈局:

物件的記憶體區域分為:物件頭、例項資料、對齊填充。

物件頭:一部分是物件的執行時資料,hashcode、分代年齡、鎖狀態等、一部分指向類原始資料。

例項資料:物件包含的真正的資料。

對齊填充:佔位作用,使得物件佔用空間為8位元組的整數倍。

 

物件訪問定位:

控制代碼:在堆中分配一塊控制代碼池,reference指向這個控制代碼,控制代碼包含兩個指標,一個指向物件例項資料、一個指向型別資料。

直接指標: reference直接指向物件例項。

前者:物件的記憶體空間被移動時只需要更改控制代碼而不需要更改reference

後者:節省了一次定址。

 

字串常量池:

對於編譯期可確定值的字串,也就是字串常量,jvm會將其存入字串常量池,在堆中。其他常量池在方法區

並且拼接得到的字串也在編譯器就存入了常量池.

常量摺疊:將在編譯期內可確定值的常量存入常量池:

  • 基本資料型別以及字串常量
  • final 修飾的基本資料型別和字串變數
  • 字串通過 “+”拼接得到的字串、基本資料型別之間算數運算(加減乘除)、基本資料型別的位運算(<<、>>、>>> )

因此,要 儘量避免使用new在堆上建立字串,而使用雙引號,可以引入編譯器的優化。

 

String s = new String("abc");這句話建立了幾個字串物件?

會建立 1 或 2 個字串:

  • 如果字串常量池中已存在字串常量“abc”,則只會在堆空間建立一個字串常量“abc”。
  • 如果字串常量池中沒有字串常量“abc”,那麼它將首先在字串常量池中建立,然後在堆空間中建立,因此將建立總共 2 個字串物件。

垃圾回收

堆是垃圾收集器管理的主要區域,因此也被稱作GC堆。為了更好的分配、回收記憶體,由分代垃圾回收演算法分為新生代(Eden、From、To)和老生代。

大部分情況,物件都會首先在 Eden 區域分配,若Eden區耗盡,觸發一次Minor GC,Eden區被清空,如果物件還存活,則會進入s區,年齡+1,s區中分為兩塊輪換存放,用來增加物件年齡,當它的年齡增加到一定程度,就會進入老年代中。

也有例外,若某物件應當從Eden挪到survivor,但虛擬機發現survivor放不下該物件,那麼該物件就會提前進入老年代,分配擔保機制。需要大塊連續記憶體的物件如字串、陣列等,會直接進入老年代,以避免分配擔保機制不必要挪動的開支。除了Minor GC,也有Major GC,也就是針對老年代。而上述兩種都屬於partial GC,當然也有Full GC,當老年代的連續空間小於新生代物件或分配擔保失敗 ,觸發Full GC。

 

垃圾回收演算法:

  • 標記清除:標記所有不需要被清除的物件,然後清除所有沒有被標記的物件。
  • 標記複製:將記憶體區域一分為二,標記所有不需要被清楚的物件,並將其複製到空的那一塊,然後將當前塊清空。像from to
  • 標記整理:標記後讓存活物件向一端集中,然後清理邊界外的記憶體
  • 分代收集:根據各代特點選取合適的演算法,如新生代每次收集都會存在大量垃圾,使用標記複製。老聖代的物件存活率很高,也沒有額外空間可以使用分配擔保,使用整理或清楚。

 

垃圾回收器:

是垃圾回收演算法的具體實現。

  • Serial:單執行緒收集,新生代使用標記複製,老生代使用標記整理,在工作時會停止JVM所有的執行緒。
  • Parnew: serial的多執行緒實現。
  • Parallel:與Parnew相似,但能夠維持較高的CPU吞吐。
  • CMS:能夠獲得最短的使用者執行緒停頓時間。基本上實現了垃圾回收和使用者執行緒的併發。使用標記清除演算法,會產生大量碎片。
  • G1:既能滿足高吞吐 也能滿足低停頓時間。可以設定預估停頓時間,根據這個時間,維護一個優先列表,力爭在預估時間內收取最大塊的垃圾。標記複製。
  • G1可獨立管理整個堆,摒棄了傳統分代區域,G1中的新生代老年代在物理上不是連續的,而維護了多個相同大小的獨立區域,這些區域可以獨立進行垃圾回收。

如何判斷物件已經死亡?

引用計數法:

給物件中新增一個引用計數器,每當有一個地方引用它,計數器就加 1;當引用失效,計數器就減 1;任何時候計數器為 0 的物件就是不可能再被使用的。這個方法實現簡單,效率高,但是目前主流的虛擬機器中並沒有選擇這個演算法來管理記憶體,其最主要的原因是它很難解決物件之間相互迴圈引用的問題。也就是兩個物件相互引用,計數器值都不為0,那麼兩個物件都不會被回收。

可達性分析演算法:

通過根節點GC root向下搜尋,結點走過的路徑稱為引用鏈,引用鏈到達不了的物件,也就是不可用的物件,應當被回收。

哪些物件可作為GC roots

  • 虛擬機器棧(棧幀中的本地變量表)中引用的物件
  • 本地方法棧(Native 方法)中引用的物件
  • 方法區中類靜態屬性引用的物件
  • 方法區中常量引用的物件
  • 所有被同步鎖持有的物件

 

強引用(StrongReference)

最普遍的引用。垃圾回收器絕不會回收它。當記憶體空間不足,Java 虛擬機器寧願丟擲 OutOfMemoryError 錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足問題。

2.軟引用(SoftReference)

如果記憶體空間足夠,垃圾回收器就不會回收它,如果記憶體空間不足了,就會回收這些物件的記憶體。只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的快取記憶體。軟引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收,JAVA 虛擬機器就會把這個軟引用加入到與之關聯的引用佇列中。

3.弱引用(WeakReference)

弱引用與軟引用的區別在於:只具有弱引用的物件擁有更短暫的生命週期。在垃圾回收器執行緒掃描它所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於垃圾回收器是一個優先順序很低的執行緒, 因此不一定會很快發現那些只具有弱引用的物件。

 

4.虛引用(PhantomReference)

"虛引用"顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定物件的生命週期。如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。

 

記憶體洩露的根本原因:持有強引用的變數沒有被釋放

  • 解決方法:都說java是不能手動進行記憶體管理的,其實間接上來講是可以的,手動標記,將不需要的引用型別變數賦值為null,解除強引用。
    • 這個刷題的時候經常用到,有時候能帶來可觀的記憶體使用量減少。

 

如何判斷一個常量是廢棄常量?當不存在任何物件引用該常量時。

如何判斷一個類是無用的類?

  • 該類所有的例項都已經被回收,也就是 Java 堆中不存在該類的任何例項。
  • 載入該類的 ClassLoader 已經被回收。
  • 該類對應的 java.lang.Class 物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

 

 

類載入機制:

 

 載入:

  1. 通過全類名獲取定義此類的二進位制位元組流
  2. 將位元組流所代表的靜態儲存結構轉換為方法區的執行時資料結構
  3. 在記憶體中生成一個代表該類的 Class 物件,作為方法區這些資料的訪問入口

驗證:載入未完成,驗證可能開始。驗證檔案結構、 語義等是否符合java規範、是否能正常工作。

準備:在方法區分配類靜態變數的記憶體,並給這些記憶體賦0.

解析:將符號引用解析為方法區中的常量引用

初始化:如果該類具有父類就進行對父類進行初始化,執行其靜態程式碼塊和靜態成員變數初始化。

 

類載入器:

  • BootstrapClassLoader(啟動類載入器) :最頂層的載入類,由 C++實現,負責載入 %JAVA_HOME%/lib目錄下的 jar 包和類或者被 -Xbootclasspath引數指定的路徑中的所有類。
  • ExtensionClassLoader(擴充套件類載入器) :主要負責載入 %JRE_HOME%/lib/ext 目錄下的 jar 包和類,或被 java.ext.dirs 系統變數所指定的路徑下的 jar 包。
  • AppClassLoader(應用程式類載入器) :面向我們使用者的載入器,負責載入當前應用 classpath 下的所有 jar 包和類。

 

雙親委派模型 :

是預設使用的類載入機制。系統首先判斷類如果沒有載入過,會把載入請求提交給父類載入器,因此載入時都會最終被送至頂層的BootstrapClassLoader,當父類載入器無法載入,再依次向下處理。也就是從下往上判斷類是否載入過,而從上往下去嘗試能否載入。

優勢:保證所有的類都在統一模型下進行載入,保證了JAVA程式的正常執行,避免類的重複載入,也保證了核心API的安全。