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參數等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章