Java虛擬機知識點總結

1. 內存模型以及分區,需要詳細到每個區放什麼。

  • 運行時數據區域:虛擬機棧,本地方法棧,程序計數器,堆,方法區,具體如圖所示:
  • 程序計數器:
    • 線程私有的,他是一塊較小的內存空間,他相當字節碼於解釋器中的指針,也就是該內存存放下一條即將執行指令的地址。字節碼解釋器就是通過改變 這個計數器的值來選擇下一條即將執行的指令。每一個線程都有一個程序計數器(內存),這樣線程切換的時候就能找到自己各個線程各自即將執行的下一條指令。 所以說是線程私有的。
  • java虛擬機棧:
    • 線程私有的,每一個方法在執行的時候就會創建一個棧幀來存放方法的局部變量,操作數棧,返回地址等,當方法執行完成的時候就釋放該棧幀。 棧幀:虛擬機棧中是一棧幀爲單位存儲的,所以一個虛擬機棧中有很多棧幀,每一個棧幀中分爲:局部變量區(存放方法的參數和局部變量),操作數棧,方法的返回地址,動態鏈接(一般解析解階段是將部分符號引用轉換成直接應用(類加載),而動態鏈接是另外一部分的符號引用轉換成直接引用(運行時))
  • 本地方法棧:
    • 線程私有,本地方法指的是那種不是用java語言寫的方法,java虛擬機棧只針java方法,而不是本地方法。hotspot虛擬機支持別的語言寫的方法在虛擬機上運行,本法方法棧和java虛擬機棧一樣。只是他們服務的對象不一樣而已,一個爲java方法服務,一個爲native方法服務。
  • Java堆:
    • 線程共享的,不過也可能爲多個線程分配私有的buffer,也就是每個線程有自己的緩存器,java堆可以是物理上連續的,也可以是不連續的。java堆是垃圾回收器管理的主要區域,所以也叫gc堆。java堆可以分爲:新生代和老年代
  • 方法區:
    • 線程共享的,可以理解爲gcc中所所說的靜態區,不過也不是確切的準確,因爲在hotspot虛擬機中他存放的是類中靜態變量和常量(注意是常量哦)。因爲他能存儲常量,所以還有存儲常量的區域有一個特別的名稱,叫做常量池(包括引用和基本數據類型的常量),方法區並不是堆,這一點和靜態區很相似。所以別名叫non-heap,java堆中可以選擇不實現gc回收,但是實際上呢還是會的,只能說垃圾回收器在這個區域不活躍而已,但是回收都是回收常量池中的常量,而不是靜態變量。可以稱爲永久代。
  • 運行時常量池:
    • 他是方法區的一部分,但是和方法區的常量池有區別,他存放的常量是在運行時產生的,而不是編譯時產生的。注意與普通方法區的區別

2.堆裏面的分區:Eden,survival from to,老年代,各自的特點。

  • Eden區的對象都是朝生夕死,發生minor gc的時候會清除eden區和survival區的,把存活的對象移到另一個Survival區,該survial區由老年代保證。當在年輕代中對象經過多次minor gc以後還存活,達到老年代的年紀,就會移動到老年代,還有就是大對象在年輕代無法存儲,直接轉到老年代,還有可能因爲擔保而進入老年代的

3.對象創建方法,對象的內存分配,對象的訪問定位

  • 1對象的創建包括三步驟:
    • ①當遇到new命令的時候,會在常量池中檢查該對象的符號引用是否存在,不存在則進行類的加載,否則執行下一步
    • ②分配內存,將將要分配的內存都清零。
    • ③虛擬機進行必要的設置,如設置hashcode,gc的分代年齡等,此時會執行命令在執行之前所有的字段都爲0,執行指令以後,安裝程序的意願進行初始化字段。
  • 2:對象的內存分配:包括對象頭,實例數據,對齊填充
    • ①對象頭:包括對象的hascode,gc分代年齡,鎖狀態標等。
    • ②實例數據:也就是初始化以後的對象的字段的內容,包括父類中的字段等
    • ③對齊填充:對象的地址是8字節,虛擬機要求對象的大小是對象的整數倍(1倍或者兩倍)。因此就會有空白區。
  • 3:對象的訪問:
    • hotspan中 是採用對象直接指向對象地址的方式(這樣的方式訪問比較快)(還有一種方式就是句柄,也就是建一張表維護各個指向各個地址的指針,然後給指針設置一個句柄 (別名),然後引用直接指向這個別名,就可以獲得該對象,這種的優勢就是,實例對象地址改變了,只要修改句柄池中的指針就可以了,而不用引用本身不會發生 改變)。

4.GC的兩種判別方法:引用計數於引用鏈

  • 引用計數
    • 給一個對象設置一個計數器,當被引用一次就加1,當引用失效的時候就減1,如果該對象長時間保持爲0值,則該對象將被標記爲回收。優點:算法簡單,效率高,缺點:很難解決對象之間的相互循環引用問題。
  • 引用鏈:
    • 現在主流的gc都採用可達性分析算法來判斷對象是否已經死亡。可達性分析:通過一系列成爲GC Roots的對象作爲起點,從這些起點向下搜索,搜索所走過的路徑成爲引用鏈,當一個對象到引用鏈沒有相連時,則判斷該對象已經死亡。
  • 可作爲gc roots的對象:
    • 虛擬機棧(本地方法表)中引用的對象(因爲在棧內,被線程引用),方法區中類靜態屬性引用的對象,方法區中常量引用的(常量存放在常量池中,常量池是方法區的一部分)對象,native方法引用的對象
  • 引用計數和引用鏈是隻是用來標記,判斷一個對象是否失效,而不是用來清除

5.GC的三種收集方法:標記清除、標記整理、複製算法的原理與特點,分別用在什麼地方,如果讓你優化收集方法,有什麼思路?

  • 標記清除:
    • 直接將要回收的對象標記,發送gc的時候直接回收:特點回收特別快,但是回收以後會造成很多不連續的內存空間,因此適合在老年代進行回收,CMS(current mark-sweep),就是採用這種方法來會後老年代的。
  • 標記整理:
    • 就是將要回收的對象移動到一端,然後再進行回收,特點:回收以後的空間連續,缺點:整理要花一定的時間,適合老年代進行會後,parallel Old(針對parallel scanvange gc的) gc和Serial old就是採用該算法進行回收的。
  • 複製算法:
    • 將內存劃分成原始的是相等的兩部分,每次只使用一部分,這部分用完了,就將還存活的對象複製到另一塊內存,將要回收的內存全部清除。這樣只要進行少量的賦值就能夠完成收集。比較適合很多對象的回收,同時還有老年代對其進行擔保。(serial new和parallel new和parallel scanvage)
  • 優化手機方法:
    • 優化收集方法:對複製算法的優化:並不是將兩塊內存分配同等大小,可以將存活率低的區域大一些,而讓回收後存活的對象所佔的區域小一些,不夠的內存由老年代的內存來保證,這樣複製算法的空閒的空間減少了。 兩個survival區域的是爲了減少風險率,有一個survivor區要參與回收,也要參與存儲,只要只有10%的空間浪費,同時也減少對老年代的依賴。

6.GC收集器有哪些?CMS收集器與G1收集器的特點。

  • 串行的,也就是採用單線程(比較老了),分類:serial new(收集年輕代,複製算法)和serial old(收集老年代,標記整理),缺點:單線程,進行垃圾回收時暫時所有的用戶線程。優點:實現簡單。
  • 並行的,採用多線程,對於年輕代有兩個: parallel new(簡稱ParNew)(參考serial new的多線程版本)和parallel scavenge;parallel scavenge是一個針對年輕代的垃圾回收器,採用複製算法,主要的優點是進行垃圾回收時不會停止用戶線程(不會發生stop all world) 老年代回收器也有兩種:Parallel old是parallel scavenge的我老年代設計的。CMS(併發標記清除),他採用標記清除算法,採用這種的優點就是快咯,因此會盡快的進行回收,減少停頓時間。
  • 高級殺手:G1收集器,年輕代和老年代通吃,最新一代的技術。面向服務器端的垃圾收集器(並行+併發的垃圾收集器)。

7.Minor GC與Full GC分別在什麼時候發生?

  • Minor GC發生:當jvm無法爲新的對象分配空間的時候就會發生minor gc,所以分配對象的頻率越高,也就越容易發生minor gc。
  • Full GC:發生GC有兩種情況,①當老年代無法分配內存的時候,會導致MinorGC,②當發生Minor GC的時候可能觸發Full GC,由於老年代要對年輕代進行擔保,由於進行一次垃圾回收之前是無法確定有多少對象存活,因此老年代並不能清除自己要擔保多少空間,因此採取採用動態估算的方法:也就是上一次回收發送時晉升到老年代的對象容量的平均值作爲經驗值,這樣就會有一個問題,當發生一次Minor GC以後,存活的對象劇增(假設小對象),此時老年代並沒有滿,但是此時平均值增加了,會造成發生Full GC

8.類加載的五個過程:加載、驗證、準備、解析、初始化。

  • 加載:
    • 加載有兩種情況,①當遇到new關鍵字,或者static關鍵字的時候就會發生(他們對應着對應的指令)如果在常量池中找不到對應符號引用時,就會發生加載 ,②動態加載,當用反射方法(如class.forName(“類名”)),如果發現沒有初始化,則要進行初始化。(注:加載的時候發現父類沒有被加載,則要先加載父類)
  • 驗證:
    • 這一階段的目的是確保class文件的字節流中包含的信息符合當前虛擬機的要求,並不會危害虛擬機自身的安全(雖然編譯器會嚴格的檢查java代碼並生成class文件,但是class文件不一定都是通過編譯器編譯,然後加載進來的,因爲虛擬機獲取class文件字節流的方式有可能是從網絡上來的,者難免不會存在有人惡意修改而造成系統崩潰的問題,class文件其實也可以手寫16進制,因此這是必要的)
  • 準備:
    • 該階段就是爲對象分派內存空間,然後初始化類中的屬性變量,但是該初始化只是按照系統的意願進行初始化,也就是初始化時都爲0或者爲null。因此該階段的初始化和我們常說初始化階段的初始化時不一樣的
  • 解析:
    • 解析就是虛擬機將常量池中的符號引用替換成直接引用的過程。符號引用其實就是class文件常量池中的各種引用,他們按照一定規律指向了對應的類名,或者字段,但是並沒有在內存中分配空間,因此符號因此就理解爲一個標示,而在直接引用直接指向內存中的地址
  • 初始化:
    • 簡單講就是執行對象的構造函數,給類的靜態字段按照程序的意願進行初始化,注意初始化的順序。(此處的初始化由兩個函數完成,一個是,初始化所有的類變量(靜態變量),該函數不會初始化父類變量,還有一個是實例初始化函數,對類中實例對象進行初始化,此時要如果有需要,是要初始化父類的)

9.雙親委派模型:Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。

  • 類 加載器的工作過程:如果一個類加載器收到類類加載的請求,他首先不會自己去加載這個類,而是把類委派個父類加載器去完成,因此所有的請求最終都會傳達到頂 層的啓動類加載器中,只有父類反饋無法加載該類的請求(在自己的搜索範圍類沒有找到要加載的類)時候,子類纔會試圖去加載該類。

10.分派:靜態分派與動態分派:

  • 靜態分派和動態分派都是多態的內容,多態的實現依賴於編譯階段和運行時階段:在編譯階段主要表現在靜態分派,
  • 靜態分派就是通過靜態類型和方法參數個數來選擇哪一個方法版本,這就是主要體現了方法的重載;因爲他在編譯的時候就能確定調用哪一個函數,所以叫靜態分派。
  • 在運行時階段體現在動態分派(動態綁定),也就是當一個父類引用指向子類對象,通過該父類引用去調用一個該方法,由於在編譯階段生產的調用函數代碼的字節碼指向的是父類(靜態類型)被調用方法,並不知道具體要去調用哪一個實際類型的方法,因 此會發生這樣一個過程,虛擬機找到操作數棧中位於棧頂獲取該操作數的指所指向的類,然後到常量池中去搜索與被調用的方法匹配的方法名和描述符,如果找到, 就進行權限校驗(校驗失敗就拋出異常),如果可以訪問,則返回該方法的符號引用,並轉換成直接引用,調用該執行,如果找不到就到父類中去找,然後重複上面 動作,最後找不到就拋出異常。
  • 對動態綁定的優化:由於要去常量池中搜索每一類的方法名和描述符,因此效率比較低,所以最後進行了優化,就是在方法區爲每一類維護一張虛方法表或者接口方 法表(虛表中存放了該方法的實際入口地址),讓該類的所有方法都維護進去(包括父類的方法),因此要查找方法名的時候,直接去該虛表中去搜索到該方法名對 應的直接地址然後執行。對於沒有被重寫的方法,直接存放父類的入口地址,如果該方法被重寫,在存放子類的方法入口地址。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章