Java基礎(1)------JVM基礎

JVM基本結構

  • 線程私有:程序計數器、虛擬機棧、本地方法棧程序員

  • 線程共享:堆、方法區、直接內存算法

JVM各部分基礎分析

程序計數器

  • 字節碼解釋器經過改變程序計數器來依次讀取指令,從而實現代碼的流程控制,如:順序執行、選擇、循環、異常處理。數組

  • 在多線程的狀況下,程序計數器用於記錄當前線程執行的位置,從而當線程被切換回來的時候可以知道該線程上次運行到哪兒了。緩存

注意:程序計數器是不會出現 OutOfMemoryError 的內存區域,它的生命週期隨着線程的建立而建立,隨着線程的結束而死亡。多線程

虛擬機棧

  • Java 虛擬機棧也是線程私有的,每一個線程都有各自的Java虛擬機棧,並且隨着線程的建立而建立,隨着線程的死亡而死亡,描述的是 Java 方法執行的內存模型。函數

  • Java 內存能夠粗糙的區分爲堆內存(Heap)和棧內存(Stack)其中棧就是如今說的虛擬機棧,或者說是虛擬機棧中局部變量表部分。 (實際上,Java虛擬機棧是由一個個棧幀組成,而每一個棧幀中都擁有局部變量表、操做數棧、動態連接、方法出口信息)性能

  • 局部變量表主要存放了編譯器可知的各類數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不一樣於對象自己,多是一個指向對象起始地址的引用指針,也多是指向一個表明對象的句柄或其餘與此對象相關的位置)。spa

  • Java 虛擬機棧會出現兩種異常: StackOverFlowError 和 OutOfMemoryError。線程

  1. StackOverFlowError: 若Java虛擬機棧的內存大小不容許動態擴展,那麼當線程請求棧的深度超過當前Java虛擬機棧的最大深度的時候,就拋出StackOverFlowError異常。
  2. OutOfMemoryError: 若 Java 虛擬機棧的內存大小容許動態擴展,且當線程請求棧時內存用完了,沒法再動態擴展了,此時拋出OutOfMemoryError異常。

  • Java 虛擬機所管理的內存中最大的一塊,Java 堆是全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例以及數組都在這裏分配內存。設計

  • Java 堆是垃圾收集器管理的主要區域,所以也被稱做GC堆(Garbage Collected Heap).從垃圾回收的角度,因爲如今收集器基本都採用分代垃圾收集算法,因此Java堆還能夠細分爲:新生代和老年代:再細緻一點有:Eden空間、From Survivor、To Survivor空間等。進一步劃分的目的是更好地回收內存,或者更快地分配內存。

  • 在 JDK 1.8中移除整個永久代,取而代之的是一個叫元空間(Metaspace)的區域(永久代使用的是JVM的堆內存空間,而元空間使用的是物理內存,直接受到本機的物理內存限制)。

方法區

  • 方法區與 Java 堆同樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,可是它卻有一個別名叫作 Non-Heap(非堆),目的應該是與 Java 堆區分開來。

  • HotSpot 虛擬機中方法區也常被稱爲 「永久代」,本質上二者並不等價。僅僅是由於 HotSpot 虛擬機設計團隊用永久代來實現方法區而已,這樣 HotSpot 虛擬機的垃圾收集器就能夠像管理 Java 堆同樣管理這部份內存了。可是這並非一個好主意,由於這樣更容易遇到內存溢出問題。

  • 相對而言,垃圾收集行爲在這個區域是比較少出現的,但並不是數據進入方法區後就「永久存在」了。

直接內存

  • 直接內存並非虛擬機運行時數據區的一部分,也不是虛擬機規範中定義的內存區域,可是這部份內存也被頻繁地使用。並且也可能致使OutOfMemoryError異常出現。

  • JDK1.4中新加入的 NIO(New Input/Output) 類,引入了一種基於通道(Channel) 與緩存區(Buffer) 的 I/O 方式,它能夠直接使用Native函數庫直接分配堆外內存,而後經過一個存儲在 Java 堆中的 DirectByteBuffer 對象做爲這塊內存的引用進行操做。這樣就能在一些場景中顯著提升性能,由於避免了在 Java 堆和 Native 堆之間來回複製數據。

  • 本機直接內存的分配不會收到 Java 堆的限制,可是,既然是內存就會受到本機總內存大小以及處理器尋址空間的限制。

本地方法棧:

這部分主要與虛擬機用到的 Native 方法相關,通常狀況下, Java 應用程序員並不須要關心這部分的內容。

垃圾回收

判斷對象是否存活

(1)引用計數法

  • 引用計數是垃圾收集器中的早期策略。在這種方法中,堆中每一個對象實例都有一個引用計數。當一個對象被建立時,就將該對象實例分配給一個變量,該變量計數設置爲1。當任何其它變量被賦值爲這個對象的引用時,計數加1(a = b,則b引用的對象實例的計數器+1),但當一個對象實例的某個引用超過了生命週期或者被設置爲一個新值時,對象實例的引用計數器減1。任何引用計數器爲0的對象實例能夠被看成垃圾收集。當一個對象實例被垃圾收集時,它引用的任何對象實例的引用計數器減1。

  • 優勢:引用計數收集器能夠很快的執行,交織在程序運行中。對程序須要不被長時間打斷的實時環境比較有利。

  • 缺點:沒法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能爲0。

(2)可達性算法

  • 可達性分析算法是從離散數學中的圖論引入的,程序把全部的引用關係看做一張圖,從一個節點GC ROOT開始,尋找對應的引用節點,找到這個節點之後,繼續尋找這個節點的引用節點,當全部的引用節點尋找完畢以後,剩餘的節點則被認爲是沒有被引用到的節點,即無用的節點,無用的節點將會被斷定爲是可回收的對象。

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

a) 虛擬機棧中引用的對象(棧幀中的本地變量表)

b) 方法區中類靜態屬性引用的對象

c) 方法區中常量引用的對象

d) 本地方法棧中JNI(Native方法)引用的對象

垃圾回收算法

標記-清除算法(Mark-Sweep)

標記-清除算法採用從根集合(GC Roots)進行掃描,對存活的對象進行標記,標記完畢後,再掃描整個空間中未被標記的對象,進行回收,以下圖所示。標記-清除算法不須要進行對象的移動,只需對不存活的對象進行處理,在存活對象比較多的狀況下極爲高效,但因爲標記-清除算法直接回收不存活的對象,所以會形成內存碎片。

複製算法(Copying)

即將內存分爲兩部分 複製算法的提出是爲了克服句柄的開銷和解決內存碎片的問題。它開始時把堆分紅 一個對象 面和多個空閒面, 程序從對象面爲對象分配空間,當對象滿了,基於copying算法的垃圾 收集就從根集合(GC Roots)中掃描活動對象,並將每一個 活動對象複製到空閒面(使得活動對象所佔的內存之間沒有空閒洞),這樣空閒面變成了對象面,原來的對象面變成了空閒面,程序會在新的對象面中分配內存。

標記-整理算法(Mark-compact)

標記-整理算法採用標記-清除算法同樣的方式進行對象的標記,但在清除時不一樣,在回收不存活的對象佔用的空間後,會將全部的存活對象往左端空閒空間移動,並更新對應的指針。標記-整理算法是在標記-清除算法的基礎上,又進行了對象的移動,所以成本更高,可是卻解決了內存碎片的問題。

分代收集算法

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

  • 年老代(Old Generation)的回收算法(回收主要以Mark-Compact爲主)

  • 年輕代(Young Generation)的回收算法 (回收主要以Copying爲主)

相關文章
相關標籤/搜索