1. 程式人生 > >java中JVM的原理

java中JVM的原理

一、java虛擬機器的生命週期:

  Java虛擬機器的生命週期 一個執行中的Java虛擬機器有著一個清晰的任務:執行Java程式。程式開始執行時他才執行,程式結束時他就停止。你在同一臺機器上執行三個程式,就會有三個執行中的Java虛擬機器。 Java虛擬機器總是開始於一個main()方法,這個方法必須是公有、返回void、直接受一個字串陣列。在程式執行時,你必須給Java虛擬機器指明這個包換main()方法的類名。 Main()方法是程式的起點,他被執行的執行緒初始化為程式的初始執行緒。程式中其他的執行緒都由他來啟動。Java中的執行緒分為兩種:守護執行緒 (daemon)和普通執行緒(non-daemon)。守護執行緒是Java虛擬機器自己使用的執行緒,比如負責垃圾收集的執行緒就是一個守護執行緒。當然,你也可 以把自己的程式設定為守護執行緒。包含Main()方法的初始執行緒不是守護執行緒。 只要Java虛擬機器中還有普通的執行緒在執行,Java虛擬機器就不會停止。如果有足夠的許可權,你可以呼叫exit()方法終止程式。

二、java虛擬機器的體系結構:

在Java虛擬機器的規範中定義了一系列的子系統、記憶體區域、資料型別和使用指南。這些元件構成了Java虛擬機器的內部結構,他們不僅僅為Java虛擬機器的實現提供了清晰的內部結構,更是嚴格規定了Java虛擬機器實現的外部行為。 
     每一個Java虛擬機器都由一個類載入器子系統(class loader subsystem),負責載入程式中的型別(類和介面),並賦予唯一的名字。每一個Java虛擬機器都有一個執行引擎(execution engine)負責執行被載入類中包含的指令。
     程式的執行需要一定的記憶體空間,如位元組碼、被載入類的其他額外資訊、程式中的物件、方法的引數、返回值、本地變數、處理的中間變數等等。Java虛擬機器將 這些資訊統統儲存在資料區(data areas)中。雖然每個Java虛擬機器的實現中都包含資料區,但是Java虛擬機器規範對資料區的規定卻非常的抽象。許多結構上的細節部分都留給了 Java虛擬機器實現者自己發揮。不同Java虛擬機器實現上的記憶體結構千差萬別。一部分實現可能佔用很多記憶體,而其他以下可能只佔用很少的記憶體;一些實現可 能會使用虛擬記憶體,而其他的則不使用。這種比較精煉的Java虛擬機器記憶體規約,可以使得Java虛擬機器可以在廣泛的平臺上被實現。
     資料區中的一部分是整個程式共有,其他部分被單獨的執行緒控制。每一個Java虛擬機器都包含方法區(method area)和堆(heap),他們都被整個程式共享。Java虛擬機器載入並解析一個類以後,將從類檔案中解析出來的資訊儲存與方法區中。程式執行時建立的 物件都儲存在堆中。 
     當一個執行緒被建立時,會被分配只屬於他自己的PC暫存器“pc register”(程式計數器)和Java堆疊(Java stack)。當執行緒不掉用本地方法時,PC暫存器中儲存執行緒執行的下一條指令。Java堆疊儲存了一個執行緒呼叫方法時的狀態,包括本地變數、呼叫方法的 引數、返回值、處理的中間變數。呼叫本地方法時的狀態儲存在本地方法堆疊中(
native method stacks),可能再暫存器或者其他非平臺獨立的記憶體中。 Java堆疊有堆疊塊(stack frames (or frames))組成。堆疊塊包含Java方法呼叫的狀態。當一個執行緒呼叫一個方法時,Java虛擬機器會將一個新的塊壓到Java堆疊中,當這個方法執行結束時,Java虛擬機器會將對應的塊彈出並拋棄。 Java虛擬機器不使用暫存器儲存計算的中間結果,而是用Java堆疊在存放中間結果。這是的Java虛擬機器的指令更緊湊,也更容易在一個沒有暫存器的裝置上實現Java虛擬機器。 圖中的Java堆疊中向下增長的,PC暫存器中執行緒三為灰色,是因為它正在執行本地方法,他的下一條執行指令不儲存在PC暫存器中。

三、類載入器子系統:

Java虛擬機器中的類載入器分為兩種:原始類載入器(primordial class loader)和類載入器物件(class loader objects)。原始類載入器是Java虛擬機器實現的一部分,類載入器物件是執行中的程式的一部分。不同類載入器載入的類被不同的名稱空間所分割。
     類載入器呼叫了許多Java虛擬機器中其他的部分和java.lang包中的很多類。比如,類載入物件就是java.lang.ClassLoader子類 的例項,ClassLoader類中的方法可以訪問虛擬機器中的類載入機制;每一個被Java虛擬機器載入的類都會被表示為一個 java.lang.Class類的例項。像其他物件一樣,類載入器物件和Class物件都儲存在堆中,被載入的資訊被儲存在方法區中。
     1、載入、連線、初始化(Loading, Linking and Initialization)
類載入子系統不僅僅負責定位並載入類檔案,他按照以下嚴格的步驟作了很多其他的事情:(具體的資訊參見第七章的“類的生命週期”)
          1)、載入:尋找並匯入指定型別(類和介面)的二進位制資訊
          2)、連線:進行驗證、準備和解析
               ①驗證:確保匯入型別的正確性
               ②準備:為型別分配記憶體並初始化為預設值
               ③解析:將字元引用解析為直接飲用
          3)、初始化:呼叫Java程式碼,初始化類變數為合適的值
     2、原始類載入器(The Primordial Class Loader)
     每個Java虛擬機器都必須實現一個原始類載入器,他能夠載入那些遵守類檔案格式並且被信任的類。但是,Java虛擬機器的規範並沒有定義如何載入類,這由 Java虛擬機器實現者自己決定。對於給定型別名的型別,原始萊載入器必須找到那個型別名加“.class”的檔案並載入入虛擬機器中。
     3、類載入器物件
     雖然類載入器物件是Java程式的一部分,但是ClassLoader類中的三個方法可以訪問Java虛擬機器中的類載入子系統。
          1)、protected final Class defineClass(…):使用這個方法可以出入一個位元組陣列,定義一個新的型別。
          2)、protected Class findSystemClass(String name):載入指定的類,如果已經載入,就直接返回。
          3)、protected final void resolveClass(Class c):defineClass()方法只是載入一個類,這個方法負責後續的動態連線和初始化。
     具體的資訊,參見第八章“連線模型”( The Linking Model)。
     4、名稱空間
     當多個類載入器載入了同一個類時,為了保證他們名字的唯一性,需要在類名前加上載入該類的類載入器的標識。具體的資訊,參見第八章“連線模型”( The Linking Model)。

四、方法區:

在Java虛擬機器中,被載入型別的資訊都儲存在方法區中。這寫資訊在記憶體中的組織形式由虛擬機器的實現者定義,比如,虛擬機器工作在一個“little- endian”的處理器上,他就可以將資訊儲存為“little-endian”格式的,雖然在Java類檔案中他們是以“big-endian”格式保 存的。設計者可以用最適合並地機器的表示格式來儲存資料,以保證程式能夠以最快的速度執行。但是,在一個只有很小記憶體的裝置上,虛擬機器的實現者就不會佔用 很大的記憶體。
     程式中的所有執行緒共享一個方法區,所以訪問方法區資訊的方法必須是執行緒安全的。如果你有兩個執行緒都去載入一個叫Lava的類,那隻能由一個執行緒被容許去載入這個類,另一個必須等待。
     在程式執行時,方法區的大小是可變的,程式在執行時可以擴充套件。有些Java虛擬機器的實現也可以通過引數也訂製方法區的初始大小,最小值和最大值。
     方法區也可以被垃圾收集。因為程式中的內由類載入器動態載入,所有類可能變成沒有被引用(unreferenced)的狀態。當類變成這種狀態時,他就可 能被垃圾收集掉。沒有載入的類包括兩種狀態,一種是真正的沒有載入,另一個種是“unreferenced”的狀態。詳細資訊參見第七章的類的生命週期 (The Lifetime of a Class)。
     1、型別資訊(Type Information)
          每一個被載入的型別,在Java虛擬機器中都會在方法區中儲存如下資訊:
          1)、型別的全名(The fully qualified name of the type)
          2)、型別的父型別的全名(除非沒有父型別,或者弗雷形式java.lang.Object)(The fully qualified name of the typeís direct superclass)
          3)、給型別是一個類還是介面(class or an interface)(Whether or not the type is a class4)、型別的修飾符(publicprivateprotectedstaticfinalvolatile,transient等)(The typeís modifiers)
          5)、所有父介面全名的列表(An ordered list of the fully qualified names of any direct superinterfaces)
          型別全名儲存的資料結構由虛擬機器實現者定義。除此之外,Java虛擬機器還要為每個型別儲存如下資訊:
          1)、型別的常量池(The constant pool for the type)
          2)、型別欄位的資訊(Field information)
          3)、型別方法的資訊(Method information)
          4)、所有的靜態類變數(非常量)資訊(All class (static) variables declared in the type, except constants)
          5)、一個指向類載入器的引用(A reference to class ClassLoader)
          6)、一個指向Class類的引用(A reference to class Class)

          1)、型別的常量池(The constant pool for the type)
          常量池中儲存中所有型別是用的有序的常量集合,包含直接常量(literals)如字串、整數、浮點數的常量,和對型別、欄位、方法的符號引用。常量池 中每一個儲存的常量都有一個索引,就像陣列中的欄位一樣。因為常量池中儲存中所有型別使用到的型別、欄位、方法的字元引用,所以它也是動態連線的主要對 象。詳細資訊參見第六章“The Java Class File”。
          2)、型別欄位的資訊(Field information)
          欄位名、欄位型別、欄位的修飾符(publicprivateprotectedstaticfinalvolatile,transient等)、欄位在類中定義的順序。
          3)、型別方法的資訊(Method information)
          方法名、方法的返回值型別(或者是void)、方法引數的個數、型別和他們的順序、欄位的修飾符(publicprivateprotectedstaticfinalvolatile,transient等)、方法在類中定義的順序
          如果不是抽象和本地本法還需要儲存
          方法的位元組碼、方法的運算元堆疊的大小和本地變數區的大小(稍候有詳細資訊)、異常列表(詳細資訊參見第十七章“Exceptions”。)
          4)、類(靜態)變數(Class Variables)
          類變數被所有類的例項共享,即使不通過類的例項也可以訪問。這些變數繫結在類上(而不是類的例項上),所以他們是類的邏輯資料的一部分。在Java虛擬機器使用這個類之前就需要為類變數(non-final)分配記憶體
          常量(final)的處理方式於這種類變數(non-final)不一樣。每一個型別在用到一個常量的時候,都會複製一份到自己的常量池中。常量也像類變 量一樣儲存在方法區中,只不過他儲存在常量池中。(可能是,類變數被所有例項共享,而常量池是每個例項獨有的)。Non-final類變數儲存為定義他的 型別資料(data for the type that declares them)的一部分,而final常量儲存為使用他的型別資料(data for any type that uses them)的一部分。詳情參見第六章“The Java Class FileThe Java Class File”
          5)、指向類載入器的引用(A reference to class ClassLoader)
          每一個被Java虛擬機器載入的型別,虛擬機器必須儲存這個型別是否由原始類載入器或者類載入器載入。那些被類載入器載入的型別必須儲存一個指向類載入器的引 用。當類載入器動態連線時,會使用這條資訊。當一個類引用另一個類時,虛擬機器必須儲存那個被引用的型別是被同一個類載入器載入的,這也是虛擬機器維護不同命 名空間的過程。詳情參見第八章“The Linking Model”
          6)、指向Class類的引用(A reference to class Class)
          Java虛擬機器為每一個載入的型別建立一個java.lang.Class類的例項。你也可以通過Class類的方法:
public static Class forName(String className)來查詢或者載入一個類,並取得相應的Class類的例項。通過這個Class類的例項,我們可以訪問Java虛擬機器方法區中的資訊。具體參照Class類的JavaDoc。
     2、方法列表(Method Tables)
     為了更有效的訪問所有儲存在方法區中的資料,這些資料的儲存結構必須經過仔細的設計。所有方法區中,除了儲存了上邊的那些原始資訊外,還有一個為了加快存 取速度而設計的資料結構,比如方法列表。每一個被載入的非抽象類,Java虛擬機器都會為他們產生一個方法列表,這個列表中儲存了這個類可能呼叫的所有例項 方法的引用,報錯那些父類中呼叫的方法。詳情參見第八章“The Linking Model”

五、堆:

當Java程式建立一個類的例項或者陣列時,都在堆中為新的物件分配記憶體。虛擬機器中只有一個堆,所有的執行緒都共享他。
     1、垃圾收集(Garbage Collection)
     垃圾收集是釋放沒有被引用的物件的主要方法。它也可能會為了減少堆的碎片,而移動物件。在Java虛擬機器的規範中沒有嚴格定義垃圾收集,只是定義一個Java虛擬機器的實現必須通過某種方式管理自己的堆。詳情參見第九章“Garbage Collection”。
     2、物件儲存結構(Object Representation)
     Java虛擬機器的規範中沒有定義物件怎樣在堆中儲存。每一個物件主要儲存的是他的類和父類中定義的物件變數。對於給定的物件的引用,虛擬機器必須嫩耨很快的 定位到這個物件的資料。另為,必須提供一種通過物件的引用方法物件資料的方法,比如方法區中的物件的引用,所以一個物件儲存的資料中往往含有一個某種形式 指向方法區的指標。
     一個可能的堆的設計是將堆分為兩個部分:引用池和物件池。一個物件的引用就是指向引用池的本地指標。每一個引用池中的條目都包含兩個部分:指向物件池中對 象資料的指標和方法區中物件類資料的指標。這種設計能夠方便Java虛擬機器堆碎片的整理。當虛擬機器在物件池中移動一個物件的時候,只需要修改對應引用池中 的指標地址。但是每次訪問物件的資料都需要處理兩次指標。下圖演示了這種堆的設計。在第九章的“垃圾收集”中的HeapOfFish Applet演示了這種設計。 
     另一種堆的設計是:一個物件的引用就是一個指向一堆資料和指向相應物件的偏移指標。這種設計方便了物件的訪問,可是物件的移動要變的異常複雜。下圖演示了這種設計 
     當程式試圖將一個物件轉換為另一種型別時,虛擬機器需要判斷這種轉換是否是這個物件的型別,或者是他的父型別。當程式適用instanceof語句的時候也 會做類似的事情。當程式呼叫一個物件的方法時,虛擬機器需要進行動態繫結,他必須判斷呼叫哪一個型別的方法。這也需要做上面的判斷。
     無論虛擬機器實現者使用哪一種設計,他都可能為每一個物件儲存一個類似方法列表的資訊。因為他可以提升物件方法呼叫的速度,對提升虛擬機器的效能非常重要,但 是虛擬機器的規範中比沒有要求必須實現類似的資料結構。下圖描述了這種結構。圖中顯示了一個物件引用相關聯的所有的資料結構,包括:
          1)、一個指向型別資料的指標
          2)、一個物件的方法列表。方法列表是一個指向所有可能被呼叫物件方法的指標陣列。方法資料包括三個部分:操作碼堆疊的大小和方法堆疊的本地變數區;方法的位元組碼;異常列表。
          每一個Java虛擬機器中的物件必須關聯一個用於同步多執行緒的lock(mutex)。同一時刻,只能有一個物件擁有這個物件的鎖。當一個擁有這個這個物件 的鎖,他就可以多次申請這個鎖,但是也必須釋放相應次數的鎖才能真正釋放這個物件鎖。很多物件在整個生命週期中都不會被鎖,所以這個資訊只有在需要時才需 要新增。很多Java虛擬機器的實現都沒有在物件的資料中包含“鎖定資料”,只是在需要時才生成相應的資料。除了實現物件的鎖定,每一個物件還邏輯關聯到一 個“wait set”的實現。鎖定幫組執行緒獨立處理共享的資料,不需要妨礙其他的執行緒。“wait set”幫組執行緒協作完成同一個目標。“wait set”往往通過Object類的wait()和notify()方法來實現。 
     垃圾收集也需要堆中的物件是否被關聯的資訊。Java虛擬機器規範中指出垃圾收集一個執行一個物件的finalizer方法一次,但是容許 finalizer方法重新引用這個物件,當這個物件再次不被引用時,就不需要再次呼叫finalize方法。所以虛擬機器也需要儲存finalize方法 是否執行過的資訊。更多資訊參見第九章的“垃圾收集”
     3、陣列的儲存(Array Representation)
在Java 中,陣列是一種完全意義上的物件,他和物件一樣儲存在堆中、有一個指向Class類例項的引用。所有同一維度和型別的陣列擁有同樣的Class,陣列的長 度不做考慮。對應Class的名字表示為維度和型別。比如一個整型資料的Class為“[I”,位元組型三維陣列Class名為“[[[B”,兩維物件資料 Class名為“[[Ljava.lang.Object”。
     陣列必須在堆中儲存陣列的長度,陣列的資料和一些物件陣列型別資料的引用。通過一個數組引用的,虛擬機器應該能夠取得一個數組的長度,通過索引能夠訪問特定 的資料,能夠呼叫Object定義的方法。Object是所有資料類的直接父類。更多資訊參見第六章“類檔案”。

六、基本結構:

從Java平臺的邏輯結構上來看,我們可以從下圖來了解JVM:

 

從上圖能清晰看到Java平臺包含的各個邏輯模組,也能瞭解到JDK與JRE的區別。

JVM自身的物理結構

 

此圖看出jvm記憶體結構

JVM記憶體結構主要包括兩個子系統和兩個元件。兩個子系統分別是Classloader子系統和Executionengine(執行引擎)子系統;兩個元件分別是Runtimedataarea(執行時資料區域)元件和Nativeinterface(本地介面)元件。

Classloader子系統的作用:

根據給定的全限定名類名(如java.lang.Object)來裝載class檔案的內容到Runtimedataarea中的methodarea(方法區域)。Java程式設計師可以extendsjava.lang.ClassLoader類來寫自己的Classloader。

Executionengine子系統的作用:

執行classes中的指令。任何JVMspecification實現(JDK)的核心都是Executionengine,不同的JDK例如Sun的JDK和IBM的JDK好壞主要就取決於他們各自實現的Executionengine的好壞。

Nativeinterface元件:

與nativelibraries互動,是其它程式語言互動的介面。當呼叫native方法的時候,就進入了一個全新的並且不再受虛擬機器限制的世界,所以也很容易出現JVM無法控制的nativeheapOutOfMemory。

RuntimeDataArea元件:

這就是我們常說的JVM的記憶體了。它主要分為五個部分——

1、Heap(堆):一個Java虛擬例項中只存在一個堆空間

2、MethodArea(方法區域):被裝載的class的資訊儲存在Methodarea的記憶體中。當虛擬機器裝載某個型別時,它使用類裝載器定位相應的class檔案,然後讀入這個class檔案內容並把它傳輸到虛擬機器中。

3、JavaStack(java的棧):虛擬機器只會直接對Javastack執行兩種操作:以幀為單位的壓棧或出棧

4、ProgramCounter(程式計數器):每一個執行緒都有它自己的PC暫存器,也是該執行緒啟動時建立的。PC暫存器的內容總是指向下一條將被執行指令的餓地址,這裡的地址可以是一個本地指標,也可以是在方法區中相對應於該方法起始指令的偏移量。

5、Nativemethodstack(本地方法棧):儲存native方法進入區域的地址

對於JVM的學習,在我看來這麼幾個部分最重要:

  • Java程式碼編譯和執行的整個過程
  • JVM記憶體管理及垃圾回收機制

Java程式碼編譯和執行的整個過程

Java程式碼編譯是由Java原始碼編譯器來完成,流程圖如下所示:

 

Java位元組碼的執行是由JVM執行引擎來完成,流程圖如下所示:

 

Java程式碼編譯和執行的整個過程包含了以下三個重要的機制:

  • Java原始碼編譯機制
  • 類載入機制
  • 類執行機制

Java原始碼編譯機制

Java 原始碼編譯由以下三個過程組成:(javac –verbose  輸出有關編譯器正在執行的操作的訊息)

  • 分析和輸入到符號表
  • 註解處理
  • 語義分析和生成class檔案

 

最後生成的class檔案由以下部分組成:

  • 結構資訊。包括class檔案格式版本號及各部分的數量與大小的資訊
  • 元資料。對應於Java原始碼中宣告與常量的資訊。包含類/繼承的超類/實現的介面的宣告資訊、域與方法宣告資訊和常量池
  • 方法資訊。對應Java原始碼中語句和表示式對應的資訊。包含位元組碼、異常處理器表、求值棧與區域性變數區大小、求值棧的型別記錄、除錯符號資訊

類載入機制

JVM的類載入是通過ClassLoader及其子類來完成的,類的層次關係和載入順序可以由下圖來描述:

 

1)Bootstrap ClassLoader /啟動類載入器

$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類

2)Extension ClassLoader/擴充套件類載入器

負責載入java平臺中擴充套件功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

3)App ClassLoader/ 系統類載入器

負責記載classpath中指定的jar包及目錄中class

4)Custom ClassLoader/使用者自定義類載入器(java.lang.ClassLoader的子類)

屬於應用程式根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader

載入過程中會先檢查類是否被已載入,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已載入就視為已載入此類,保證此類只所有ClassLoader載入一次。而載入的順序是自頂向下,也就是由上層來逐層嘗試載入此類。

類載入雙親委派機制介紹和分析

在這裡,需要著重說明的是,JVM在載入類時預設採用的是雙親委派機制。通俗的講,就是某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,依次遞迴,如果父類載入器可以完成類載入任務,就成功返回;只有父類載入器無法完成此載入任務時,才自己去載入。

類執行機制

  JVM是基於棧的體系結構來執行class位元組碼的。執行緒建立後,都會產生程式計數器(PC)和棧(Stack),程式計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次呼叫,而棧幀又是有區域性變數區和運算元棧兩部分組成,區域性變數區用於存放方法中的區域性變數和引數,運算元棧中用於存放方法執行過程中產生的中間結果。

記憶體管理和垃圾回收

JVM記憶體組成結構

JVM棧由堆、棧、本地方法棧、方法區等部分組成,結構圖如下所示:

 

JVM記憶體回收

Sun的JVMGenerationalCollecting(垃圾回收)原理是這樣的:把物件分為年青代(Young)、年老代(Tenured)、持久代(Perm),對不同生命週期的物件使用不同的演算法。(基於對物件生命週期分析)

 

1.Young(年輕代)

年輕代分三個區。一個Eden區,兩個Survivor區。大部分物件在Eden區中生成。當Eden區滿時,還存活的物件將被複制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活物件將被複制到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的物件,將被複制年老區(Tenured。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來物件,和從前一個Survivor複製過來的物件,而複製到年老區的只有從第一個Survivor去過來的物件。而且,Survivor區總有一個是空的。

2.Tenured(年老代)

年老代存放從年輕代存活的物件。一般來說年老代存放的都是生命期較長的物件。

3.Perm(持久代)

用於存放靜態檔案,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者呼叫一些class,例如Hibernate等,在這種時候需要設定一個比較大的持久代空間來存放這些執行過程中新增的類。持久代大小通過-XX:MaxPermSize=進行設定。

舉個例子:當在程式中生成物件時,正常物件會在年輕代中分配空間,如果是過大的物件也可能會直接在年老代生成(據觀測在執行某程式時候每次會生成一個十兆的空間用收發訊息,這部分記憶體就會直接在年老代分配)。年輕代在空間被分配完的時候就會發起記憶體回收,大部分記憶體會被回收,一部分倖存的記憶體會被拷貝至Survivor的from區,經過多次回收以後如果from區記憶體也分配完畢,就會也發生記憶體回收然後將剩餘的物件拷貝至to區。等到to區也滿的時候,就會再次發生記憶體回收然後把倖存的物件拷貝至年老區。

通常我們說的JVM記憶體回收總是在指堆記憶體回收,確實只有堆中的內容是動態申請分配的,所以以上物件的年輕代和年老代都是指的JVM的Heap空間,而持久代則是之前提到的MethodArea,不屬於Heap。

關於JVM記憶體管理的一些建議

1、手動將生成的無用物件,中間物件置為null,加快記憶體回收。

2、物件池技術如果生成的物件是可重用的物件,只是其中的屬性不同時,可以考慮採用物件池來較少物件的生成。如果有空閒的物件就從物件池中取出使用,沒有再生成新的物件,大大提高了物件的複用率。

3、JVM調優通過配置JVM的引數來提高垃圾回收的速度,如果在沒有出現記憶體洩露且上面兩種辦法都不能保證JVM記憶體回收時,可以考慮採用JVM調優的方式來解決,不過一定要經過實體機的長期測試,因為不同的引數可能引起不同的效果。如-Xnoclassgc引數等。