JVM入門知識總結

1 堆棧

Java把內存分爲棧內存和堆內存。在函數中定義的一些基本類型的變量和對象的引用變量都在函數的棧內存中分配堆內存用於存放由new創建的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。在堆中產生了一個數組或者對象後,還可以在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,以後就可以在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量相當於爲數組或者對象起的一個別名,或者代號。

數組和對象在沒有引用變量指向它的時候,才變成垃圾,不能再被使用,但是仍然佔着內存,在隨後的一個不確定的時間被垃圾回收器釋放掉。這個也是java比較佔內存的主要原因。

從堆棧的功能和作用上來講,堆主要用來存放對象的,棧主要是用來執行程序的。

    1  New(年輕代)用來存放JVM剛分配的Java對象

       1.1 EdenEden用來存放JVM剛分配的對象

       1.2 Survivor1

       1.3 Survivor2:兩個Survivor空間一樣大,當Eden中的對象經過垃圾回收沒有被回收掉時,會在兩個Survivor之間來回Copy,當滿足某個條件,比如Copy次數,就會被CopyTenured。顯然,Survivor只是增加了對象在年輕代中的逗留時間,增加了被垃圾回收的可能性。

    2  Tenured(年老代)年輕代中經過垃圾回收沒有回收掉的對象將被Copy到年老代

    3  Perm(永久代)存放ClassMethod元信息,其大小跟項目的規模、類、方法的量有關,一般設置爲128M就足夠,設置原則是預留30%的空間。

NewTenured屬於堆內存,堆內存會從JVM啓動參數(-Xmx:3G)指定的內存中分配,Perm不屬於堆內存,有虛擬機直接分配,但可以通過-XX:PermSize -XX:MaxPermSize 等參數調整其大小。

2 垃圾回收的算法

2.1  Serial算法

2.2  並行算法:用多線程進行垃圾回收,回收期間會暫停程序的執行,

2.3  併發算法也是多線程回收,但期間不停止應用執行。所以,併發算法適用於交互性高的一些程序。

JVM98%的時間都花費在內存回收;每次回收的內存小於2%:滿足這兩個條件將觸發OutOfMemoryException,這將會留給系統一個微小的間隙以做一些Down之前的操作,比如手動打印Heap Dump

3 內存泄漏及解決方法

3.1  系統奔潰前的現象

每次垃圾回收的時間越來越長,由之前的10ms延長到50ms左右,FullGC的時間也有之前的0.5s延長到45s【垃圾回收分爲兩部分:內存標記和清除(複製),標記部分只要內存大小固定時間是不變的,變的是複製部分,因爲每次垃圾回收都有一些回收不掉的內存,所以增加了複製量,導致時間延長)

FullGC的次數越來越多,最頻繁時隔不到1分鐘就進行一次FullGC【因爲內存的積累,逐漸耗盡了年老代的內存,導致新對象分配沒有更多的空間】

年老代的內存越來越大並且每次FullGC後年老代沒有內存被釋放。【因爲年輕代的內存無法被回收,越來越多的被copy到年老代】

之後系統會無法響應新的請求,逐漸到達OutOfMemoryError的臨界值。

3.2 生成堆的dump文件

WeblogicJVM啓動參數添加以下參數:

   -XX:+HeapDumpOnCtrlBreak

   -XX:+HeapDumpOnOutOfMemoryError

   -XX:HeapDumpPath=/home/weblogic/log/heapdump/heapdump_20170420094956.hprof

3.3 分析dump文件

選用Eclipse專門的靜態內存分析工具:Mat

3.4 分析內存泄漏

通過Mat能清楚地看到,哪些對象被懷疑爲內存泄漏,哪些對象佔的空間最大及對象的調用關係。還可以分析線程狀態,可以觀察到線程被阻塞在哪個對象上,從而判斷系統的瓶頸。

4 性能調優

4.1 線程池:解決用戶響應時間長的問題

Java線程池(java.util.concurrent.ThreadPoolExecutor)有幾個重要的參數配置:

corePoolSize:核心線程數(最新線程數)

maximumPoolSize:最大線程數,超過這個數量的任務會被拒絕,用戶可以通過RejectedExecutionHandler接口自定義處理方式

keepAliveTime:線程保持活動的時間

workQueue:工作隊列,存放執行的任務。Java線程池需要傳入一個Queue參數(workQueue)用來存放執行的任務,對Queue的不同選擇,線程池有完全不同的行爲:

    SynchronousQueue: 一個無容量的等待隊列,一個線程的insert操作必須等待另一線程的remove操作,採用這個Queue線程池將會爲每個任務分配一個新線程

    LinkedBlockingQueue : 無界隊列,採用該Queue,線程池將忽略maximumPoolSize參數,僅用corePoolSize的線程處理所有的任務,未處理的任務便在LinkedBlockingQueue中排隊

    ArrayBlockingQueue: 有界隊列,在有界隊列和 maximumPoolSize的作用下,程序將很難被調優:更大的Queue和小的maximumPoolSize將導致CPU的低負載;小的Queue和大的池,Queue就沒起到應有的作用。

線程池的設計思路是,任務應該放到Queue中,當Queue放不下時再考慮用新線程處理,如果Queue滿且無法派生新線程,就拒絕該任務。設計導致“先放等執行”、“放不下再執行”、“拒絕不等待”。所以,根據不同的Queue參數,要提高吞吐量不能一味地增大maximumPoolSize

4.2 連接池

c3p0是一個開源的JDBC連接池,它實現了數據源和JNDI綁定,支持JDBC3規範和JDBC2的標準擴展。目前使用它的開源項目有Hibernatespring等。

4.3 JVM啓動參數:調整各代的內存比例和垃圾回收算法,提高吞吐量

設置啓動參數,希望達到一些目標:GC的時間足夠的小;GC的次數足夠的少;發生Full GC的週期足夠的長。前兩個目標是相悖的,要想GC時間短就必須有一個更小的堆,要保證GC次數少,就必須有一個更大的堆。所以

    4.4 程序算法:改進程序邏輯算法提高性能

5 JVM參數解析

-Xms100g     --設置JVM啓動時堆內存的初始化大小

-Xmx100g    --設置堆的最大值

-Xmn16g    --設置年輕代的空間大小,剩下的爲老年代的空間大小。

-Xdebug    --JVM調試參數,用於遠程調試

-Xnoagent

-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=15000

-XX:+ExplicitGCInvokesConcurrent    --避免顯式地調用GC,即在應用程序中調用system.gc()

-XX:+PrintFlagsFinal   --顯示所有可設置的參數及”參數處理”後的默認值

-XX:AutoBoxCacheMax=20000   --相當於int自動裝箱Integer的過程,設置爲20000後,每秒查詢率會提升。

-XX:+AlwaysPreTouch  --啓動時把參數中設置的內存全部清一遍,會導致啓動變慢,但之後訪問更流暢。

-XX:TargetSurvivorRatio=80   --允許80%Survivor區被佔用(JVM默認爲50%)。提高對於Survivor區的使用率。

-XX:+UseG1GC  --G1垃圾收集器(Garbage First)Java 7後纔可以使用的特性,它的長遠目標時代替CMS收集器。G1收集器是一個並行的、併發的和增量式壓縮短暫停頓的垃圾收集器。G1收集器和其他的收集器運行方式不一樣,不區分年輕代和年老代空間。它把堆空間劃分爲多個大小相等的區域。當進行垃圾收集時,它會優先收集存活對象較少的區域,因此叫 “Garbage First”。

-XX:MetaspaceSize=64m   --指元空間的初始大小

-XX:MaxMetaspaceSize=256m   --設置元空間的最大值

-Dcom.sun.management.jmxremote=true   --設置JVM允許遠程jmx進行調用查看的相關配置

-Dcom.sun.management.jmxremote.port=44708

-Dcom.sun.management.jmxremote.authenticate=false

-Dcom.sun.management.jmxremote.ssl=false

-Djava.rmi.server.hostname=192.168.238.138 --設置監控的服務器ip

-XX:+PrintGCDateStamps -- GC發生的時間信息

-XX:+PrintGCDetails --打開PrintGCDetails開關,瞭解GC詳細信息

-Xloggc:./gclogs  --設置日誌產生的路徑

-XX:+UnlockCommercialFeatures --開啓商業特性

-XX:+FlightRecorder   --飛行記錄,和-XX:+UnlockCommercialFeatures一起使用

 


6 垃圾回收器

1、串行回收器(Serial Collector)

Serial收集器是一個新生代收集器,單線程執行,回收期間會暫停所有應用線程的執行。所以可能不適合服務器環境。它最適合的是簡單的命令行程序。

通過JVM參數-XX:+UseSerialGC可以使用串行垃圾回收器。

2、並行回收器(Parallel Collector)

JVM默認的垃圾回收器,與串行垃圾回收期不同的是,它使用多線程進行垃圾回收。相似的是,在執行垃圾回收的時候,它也會凍結所有的應用程序線程。

通過-XX:+UseParallelGC命令行可選項強制指定。

3、併發標記掃描垃圾回收器(Concurrent Mark-Sweep Collector)

併發標記垃圾回收使用多線程掃描堆內存,是一種以獲取最短回收停頓時間爲目標的收集器。

-XX:+UseConcMarkSweepGC

CMS收集器是基於標記-清除算法實現的,整個收集過程大致分爲4個步驟:初始標記、併發標記、重新標記、併發清除。其中,初始標記和重新標記這兩個步驟需要停頓其他用戶線程,由於整個過程中耗時最長的併發標記和併發清除過程中,收集器線程都可以與用戶一起工作,所以整體來說,CMS收集器的內存回收過程是與用戶線程一起併發執行的。

4、G1垃圾回收器(G1 Garbage Collector

G1收集器是JDK1.7提供的一個新收集器,G1收集器基於標記-整理算法實現,也就是說不會產生內存碎片。G1收集器收集的範圍是整個Java堆(包括新生代,年老年)。

7 類加載機制

類加載過程:使用java編譯器可以把java代碼編譯爲存儲字節碼的Class文件,使用其他語言的編譯器一樣可以把程序代碼翻譯爲Class文件,java虛擬機不關心Class的來源是何種語言。

Class文件中描述的各種信息,最終都需要加載到虛擬機中才能運行和使用。JVM把描述類數據的字節碼.class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的java類型,這就是虛擬機的類加載機制。

類從被加載到虛擬機內存開始,到卸載出內存爲止,它的整個生命週期包括:加載、驗證、準備、解析、初始化、使用和卸載七個階段。


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