JAVA複習筆記(面試向)

 

JVM 內存管理

內存區域

JVM內存分爲線程私有的和公共區域,前端

線程私有的由程序計數器,JAVA虛擬機棧和本地方法棧構成java

公共區域分爲JAVA堆和方法區,1.8以前方法區叫作永生代,以後改成元空間算法

程序計數器

程序計數器:能夠看做當前線程執行的字節碼的行號指示器,字節碼解釋器就是經過改變程序計數器,選取要運行的下一條字節碼。編程

程序計數器的兩個功能:api

  • 記錄線程運行狀態,在發生上下文切換的的時候記錄程序的狀態,以便恢復到原來的狀態安全

  • 控制程序的執行數據結構

程序計數器,線程私有,而且不會發生OOM併發

JAVA虛擬機棧

虛擬機棧:虛擬機棧描述方法執行的內存模型,存放局部變量表,操做數棧,動態連接和方法出口,一個方法調用到執行完成,就是棧幀在虛擬機棧中從入棧到出棧。佈局

局部變量表:存在局部變量和對象引用性能

存在StackOverFlowError和OOM

本地方法棧

本地方法棧,和JAVA虛擬機棧基本相同,可是執行的是本地方法。

虛擬機規範中未定義具體實現,HotSpot中和JAVA虛擬機棧合二爲一

存在StackOverFlowError和OOM

JAVA堆

JAVA堆用來存放實例對象的內存區域,幾乎全部對象都分配在堆

基於垃圾分代回收假說,爲了更好的回收垃圾,因此須要對JAVA堆分代。

經典分代:

  • 新生代(Eden,from survivor 和 to survivor)

  • 老年代

其餘分代:

將內存區域分爲多個region,每一個region既能夠做爲老年代也能夠做爲新生代

方法區

方法區存放已經加載的類信息、常量和靜態變量。

類的信息包括:版本、字段、方法、接口等 還有常量池表

1.7之後將常量和靜態變量移出了

1.8之後改做元空間

方法區也存在垃圾回收:常量回收和類卸載

運行時常量池

方法區的一部分,當類被加載後,類的常量信息存放在運行時常量池表

常量池包括:

  • 字面量:字符串值、基本數據類型、final的常量,其餘

  • 符號引用:類和結構的名字,字段和描述符,方法名稱和描述符

字面量就是值,好比int i = 1;String s =「abc」;1和「abc」就是字面量,s就是符號引用

靜態常量池的概念:靜態常量池就是在類沒被加載的時候,.class文件中的常量池

可是常量池不是必須是編譯時期產生的,好比string.intern()方法就能夠將字符串對象的字面量放入字符串常量池。

字符串常量池

存放字符串常量的地方,1.8之後在元空間

直接內存

native的方法直接在JVM之外的內存區域分配內存

內存中的對象

一個對象建立

對象建立分五步,

類加載檢查,內存分配、初始化、設置對象頭、執行init方法

  • 類加載檢查:檢查對象所屬的類是否已經被加載了,若是被加載了,就經過常量池的符號引用在方法區找到這個類。

  • 內存分配:爲對象在JAVA堆上分配空間。指針碰撞和空閒列表,對象整理複製和清除。同步性:CAS操做失敗重試或者本地線程分配緩衝

  • 初始化:賦0值

  • 設置對象頭:對對象必要的設置,好比屬於哪一個類、hash碼、分代年齡還有是否啓用偏向鎖等

  • 執行init,按照程序猿構造的方法,對對象初始化

對象內存佈局

對象在堆內存中,主要分爲三個部分:對象頭、實例數據、對齊填充

對象頭:

  • 對象自身運行數據:hashacode、分代、鎖狀態(Mark word記錄)

  • 對象指針:指向所屬類

對象訪問方式

句柄和直接訪問

句柄:JAVA棧的本地變量表中指向JAVA堆中一個句柄池中的指針,句柄池中指針指向對象實例數據和類型類型數據的指針

直接方法:JAVA棧的本地變量表中指向JAVA堆中實例對象,對象實例數據有指針指向對象類型數據

好處:直接指針,快,節省一次開銷,句柄,對象移動,只須要改變句柄。hotspot用直接訪問

一個String對象

new一個string對象會產生兩個string對象,一個字符串常量在常量池一個堆中string對象

不用new直接拼接,會在堆生成一個對象,而後引用指向常量池,其實是調用了stringbuilder拼接,而後返回一個對象。

不用new直接寫,在常量池,而後引用這個對象

string.intern:第一次在常量池生成一個對象的拷貝,第二次不操做

垃圾回收

引用計數算法和可達性分析算法

判斷一個對象是否存活。

引用計數:有一個引用就+1,消失就-1,爲0就死亡。hotspot沒用,用的是可達性分析算法

可達性分析:從一系列GC Roots出發,按照引用關係向下搜索,完成引用鏈,不可達的對象就是死亡對象。

GR roots:

  • 虛擬機棧中引用

  • 方法區中靜態屬性應用的對象

  • 常量區引用的對象

  • 本地方法區native 方法引用的對象

  • 鎖持有的對象

  • 虛擬機內部的對象還有類加載器

引用種類

強引用:不會被回收

弱引用:不夠了被回收

軟引用:下一次被回收

虛引用:不影響回收

finalize()方法

GC Roots不可達以後,進行一次標記,而後檢查是否有finalize方法,有就執行一次,沒有或者執行過了就能夠執行回收。

finalize方法可讓對象自救,好比在對象內引用上本身

方法區回收

同時知足:

  • 全部實例被回收

  • 加載器被回收

  • java.lang.Class對象沒有被引用,不能夠反射

跨代引用

不一樣年齡段的對象之間存在指向對方的引用。

垃圾回收的時候如何處理?

使用一個記憶集:一種從非回收區域,指向回收區域的的指針的抽象集合

回收算法

標記清除:效率不穩定、碎片化

標記整理:開銷很大

標記複製:浪費一部份內存空間,若是按照1:1分配就會浪費50。hotspot也用的是這種,hotspot中新生代分爲8:1:1,Eden和survivor,將一個Eden和survivor複製到另外一個survivor。分配不下就內存擔保機制。

移動對象開銷大,不移動對象分配大。從整個程序上看,移動比較划算。

碎片化問題能夠經過執行一段時間以後,標記整理一次。

Hotspot實現細節

GC roots枚舉

枚舉出GC roots。如何找到引用對象?使用的是Oopmap(ordinary object pointer)普通對象指針。使用OopMap能夠快速枚舉gc roots,可是致使OopMap變化的指令不少,開銷很大。

因此只在特定的位置,生成OopMap,這些位置叫作安全點。

安全點和安全區域

安全點:因此程序只有執行到安全點的時候,才能夠垃圾回收。

安全點選擇:不至於等待時間太長,也不至於太頻繁。因此在循環跳轉,方法調用等狀況下設置安全點

中斷方式:搶斷式和主動中斷,要準備垃圾收集了就中斷全部線程,不在安全點的就恢復執行到安全點,主動式設置標誌位,發現標誌位就主動執行到安全點中斷。

安全區域:安全點選擇結局了執行中線程的引用變化問題,可是對於不執行的程序,使用安全區域。線程執行到安全區域就標識一下,出區域的時候檢查垃圾回收中gcroot枚舉是否完成。

記憶集和卡表

記憶集:一種從非回收區域,指向回收區域的的指針的抽象集合

卡表:記憶集的一種實現,定義記錄進度和內存映射關係,就是記錄了某一塊內存中是否有對象被另外一塊內存引用

字長精度和對象精度,這倆精度更高

寫屏障

維護卡表,保證引用關係變化的時候,卡表能夠隨之變化。

併發的可達性分析

因爲和程序是併發執行的,防止引用關係變化,使用增量更新或者初始快照。

變化致使的錯誤:

A引用B,而後掃描A的時候,引用被刪除,因此B不在引用鏈,而後以後掃描過的C引用了B

增量更新:當掃過對象增長新的引用的時候,記錄下來,結束後,以這些對象爲root再掃一遍

初始快照:刪除正在掃描對象引用的時候,記下這些對象,結束後從新掃描一遍。

CMS使用增量,G1使用快照

經典垃圾收集器

serial

新生代串行收集器

標記複製

ParNew

新生代並行收集器,serial並行版本

標記複製

CMS默認的新生代收集器

parallel scanvge

新生代並行收集器

標記複製

注重於可預測的停頓時間,可控制的吞吐量,對於暫停時間能夠預測,吞吐量優先

經過設置最大停頓時間和和吞吐量大小的參數,調節收集新生代大小,從而決定停頓時間

還能夠開啓自適應調節

serial old

老年代序列收集器

標記整理

parallel old

老年代並行收集器

標記整理

CMS

四個階段:初始階段、併發標記、從新標記、併發清除

初始階段:GC roots枚舉,停頓

併發標記:可達性分析,查找引用鏈

從新標記:併發標記的時候變化的引用關係從新標記,停頓

併發清除:清除

浮動垃圾和從新標記?

從新標記的是引用鏈裏已經有的對象,可是對象的位置發生了改變,而浮動垃圾是標記過程之後產生的垃圾,不在引用鏈裏了。

標記清除算法

缺點:浮動垃圾、資源敏感、碎片化

G1

Garbage First 是面向局部的垃圾回收器,將內存區域分爲不一樣的region,1.8後支持對類的卸載。

四個階段:初始階段、併發標記、最終標記、篩選回收

初始階段:GC roots枚舉,借用minor gc時間完成

併發標記:可達性分析,查找引用鏈

最終標記:SATB中的記錄,再檢查一遍

篩選回收:按照能回收的空間回報回收region,直接將一個region中存活對象複製到另外一個空region,不要求一次清理乾淨,不影響執行就能夠

SATB 原始快照算法

TAMS指針,用於併發回收的時候分配對象

缺點:內存佔用過高,由於須要記憶集記錄跨代引用

局部上看標記複製,整體看是標記整理

Hotspot虛擬機實戰

-Xms20M -Xmx20M -Xmn10M

-printGCDetials等參數運行虛擬機

證實:

大對象直接進入老年代

內存擔保

動態年齡判斷:相同年齡全部對象大雨survivor一半

垃圾回收觸發條件

minor gc:Eden滿了

full gc:至少伴隨一次minor

  • 老年代滿了

  • 調用system.gc()可能會清理

  • 空間擔保失敗

  • 1.7以前永久代不足

  • CMS GC過程當中,分配新對象內存失敗,直接所有暫停full gc一次

CLASS文件結構

魔術和版本

常量池

訪問標誌

類、父類、接口索引

字段表

方法集合

屬性表:屬性表記錄,上面三個多一些特定場景,例如字段爲常量

類加載

類加載機制:虛擬機從class文件,從讀取、數據檢驗、轉換分析、初始化,最終造成能夠直接使用的JAVA類的過程

類的生存週期:

加載、驗證、準備、解析、初始化、使用、卸載

類加載器

將.class文件加載到內存中,變成二進制流,一個class對應一個類加載器

分類:

  • 啓動類加載器,C++實現,虛擬機一部分

  • 其餘全部類加載器,java.lang.classloader子類

分類2:

  • 啓動類加載器

  • 擴展類加載器:加載類庫

  • 應用程序類加載器:加載應用程序

雙親委派模型

一個類加載器首先將類加載請求轉發到父類加載器,只有當父類加載器沒法完成時才嘗試本身加載。

好處:避免從新加載和核心api被篡改

加載

加載階段完成三件事:

  • 將二進制流加載入內存,而且能夠經過一個類全名訪問

  • 將字節流表示的靜態數據結構,轉化爲方法區內運行時數據結構

  • 內存中生成一個java.lang.Class對象,做爲方法區中類的各類數據訪問入口。

驗證

確保字節流包含的信息符合規範,不會危害虛擬機,由於二進制流不必定都是編譯出來的。

準備

爲類中變量也就是靜態變量分配內存。不會賦值,初始化的時候纔會賦值,如今爲0

1.8之後隨着Class對象一塊兒存放在JAVA堆

解析

將JAVA虛擬機中常量池中符號引用,替換爲直接引用的過程。

初始化

按照程序猿的編寫的代碼,初始化變量和其餘資源。

必定要當即初始化

  • new

  • 反射調用reflect

  • 子類初始化時,若是父類沒有,先初始化父類

  • 虛擬機啓動,用戶指定執行的主類

  • default

虛擬機字節碼執行

虛擬機字節碼執行,是在線程中進行的,方法是執行的基本單元

棧幀是支持調用和執行的數據結構,執行一個方法,就將這個方法的棧幀壓入虛擬機棧

每個棧幀,都包括:本地方法表、操做數棧、動態連接和方法返回地址

只有棧頂的棧幀被執行。

操做數棧

方法執行的過程,就是各類字節碼指令往操做數棧中寫入和提取的內容

動態連接

棧幀中指向常量池中方法引用的調用過程。

有些類加載的時候就已經轉換成直接引用了,有些是運行時才變成直接引用的,這個過程叫作動態連接。

方法調用

肯定方法的版本,由於存在多態嘛,和方法執行的調用不是一個含義

解析和分派

前端編譯器、即時編譯器、提早編譯器

前端編譯器:編程成字節碼,.java編程.class。

即時編譯器:一邊執行一遍編譯熱點代碼成機器碼,運行時編譯器JIT just in time

提早編譯器:直接編譯成機器碼,AOT ahead of time complier

優缺點:

  • 即時編譯消耗資源

  • 提早編譯不能跨平臺

JAVA內存模型

內存模型:對內存進行讀寫訪問過程的抽象

主要目的:定義程序中各類變量的訪問規則,關注虛擬機如何吧變量值存儲到內存,又如何從內存中取出。

JAVA中分爲主內存和工做內存,

線程在工做內存執行,變量存儲在主內存,經過SAVE和LOAD操做從主內存獲取數據。

內存之間操做

lock 做用於主內存,鎖定,線程獨佔

unlock 做用於主內存,解鎖

read 做用於主內存,讀出數據

load 做用於工做內存,read出來的變量放入工做內存

use 做用於工做內存,給執行引擎

assign 做用於工做內存,執行引擎操做過的值給工做內存

store 做用於工做內存,從工做內存讀出,準備給主內存

write 做用於主內存,放入主內存

JAVA線程實現

系統線程實現,用戶線程實現和混合實現

線程安全和鎖優化

線程安全有互斥同步和非阻塞同步

互斥同步

臨界區、互斥量和信號量

synchronized和重入鎖

區別:

  • 重入鎖能夠等待可中斷:得不到能夠申請放棄

  • 公平鎖:按照申請順序得到,sychronized不公平

  • 綁定多個條件:condition條件控制阻塞

沒有特殊需求,推薦synchronized,緣由:

  • sychronized代碼易讀

  • lock須要finally中釋放,不釋放永久持有

  • 性能差很少

非阻塞同步

CAS指令的支持,完成比較和交換動做,原子性,比較變了嗎,沒有才執行,會有ABA問題。

樂觀鎖:就是不斷的嘗試,能夠就修改,不能夠就不斷嘗試

自旋鎖和自適應自旋鎖

請求不到就等待,可是不切換,不釋放資源,由於可能很快就能夠得到資源了,減小切換的開銷

鎖消除

編譯的時候,沒有發現訪問衝突,直接消除鎖

鎖粗化

同步代碼塊老是過小,頻繁切換會很浪費資源,因此讓同步代碼塊的範圍更大

輕量鎖

將資源標記與屬於一個線程,其餘資源訪問的時候,先看看有沒有人佔用,有的話變成傳統重量級鎖。

基於CAS操做,每次使用的時候CAS檢查本身是否擁有。

偏向鎖

CAS操做都不作了,得到偏向鎖之後,每次都不申請了,直接使用,直到其餘進程也要使用,升級成輕量或者重量級鎖。

相關文章
相關標籤/搜索