JVM內存管理

物理內存和虛擬內存

(1)在java中,分配內存和回收內存都由JVM自動完成,甚至不須要寫和內存相關的代碼
(2)物理內存即RAM還有寄存器(一種存儲單元,用於存儲計算機單元執行指令(如整形浮點等運算)的中間結果)是處理器經過地址總線鏈接的。地址總線:其寬度決定了一次能夠存寄存器或者RAM中獲取多少個bit和處理器最大的能夠尋址的範圍,每一個地址會引用一個字節,因此若是是32位的總線則能夠有4G的內存空間。(一般狀況下地址總線和RAM或寄存器有相同的位數)
(3)一般操做系統的內存申請空間是按照進程來管理的,每一個進程間不會互相重合,操做系統保證每一個進程擁有一段獨立的地址空間。(邏輯上獨立,物理空間不必定獨立,如虛擬內存,虛擬內存是計算機系統內存管理的一種技術。它使得應用程序認爲它擁有連續的可用的內存(一個連續完整的地址空間),而實際上,它一般是被分隔成多個物理內存碎片,還有部分暫時存儲在外部磁盤存儲器上,在須要時進行數據交換。)
(4)因爲程序愈來愈龐大何設計的多任務性,物理內存沒法知足要求,出現了虛擬內存,虛擬內存使得多個進程能夠共享物理內存,而且邏輯上獨立。虛擬內存提升了內存利用率,而且能夠擴展內存空間,使得一個虛擬的地址能夠映射到物理內存,文件或者其餘能夠尋址的存儲上。如一個進程在不活動的狀況下,操做系統將這個物理內存中的數據移到一個磁盤文件下(頻繁地交換物理內存和磁盤上的數據,會致使效率低下,須要關注)。java

內核空間和用戶空間

(1)電腦的內存地址空間將被劃分爲內核地址空間和用戶空間,程序只能使用用戶空間的內存(指程序可以申請的內存)(如windows32爲默認內核空間和用戶空間的比例是1:1,linux32爲默認的比例是1:3)
(2)內核空間主要指操做系統用於程序調度、虛擬內存或者鏈接硬件資源等的程序邏輯。程序不能訪問操做系統的空間,而且不能直接訪問硬件資源,必須經過系統提供的接口調用。(每一次系統調用都會引發內核空間和內存空間的切換,這一操做比較耗時)linux

Java中的那些組件須要使用內存

(1).java堆:用於存儲java對象的內存區域,能夠經過Xmx(最大大小)和Xms(初始大小)來控制大小,默認空餘堆內存少於40%時就擴大到Xmx,空餘堆內存大於70%時就縮小到Xms,所以,服務器通常把xmx和xms設置成同樣,避免在GC後調節堆的大小。
(2).每一個線程建立時,JVM都會爲它建立一個運行方法棧、局部變量的堆還有操做棧。
(3).類和類加載器:類和類的加載器自己一樣須要存儲空間,存儲在永久代PermGen(屬於方法區,即java堆的永久區部分)
ps:算法

  1. JVM是按需加載類的,隱式加載只會加載那些應用程序中明確使用到的。
  2. 加載類超過PermGen區大小的話可能會致使內存溢出,因此對於本身實現的類加載器可能會致使類的重複加載時,可能須要實現對類的卸載,需知足:
    • Java堆中沒有對錶示該類的類加載器的java.lang.ClassLoader對象的引用,
    • Java對中沒有該類的對應加載器的java.lang.class對象的引用,
    • Java堆上任何該類的類加載器的任何類的全部對象都不存活。

而JVM的默認類加載器都不知足該條件,因此他們加載的類都不能卸載。windows

(4)NIO:NIO使用ByteBuffer.allocateDirect()方法分配內存,能夠避免數據從內核空間到用戶空間的複製,提升效率,可是該方法直接使用的是本機內存而不是java堆內存,直接的ByteBuffer對象能夠自動清理本機緩存區,可是其只是做爲GC時的一部分執行,而GC只在Java堆被填滿或者顯示調用System.gc()來執行(也就是自動的GC只檢查Java堆是否滿,而不知道NIO操做的本機內存是否須要釋放。),以致於NIO在不少框架中是經過顯示調用System.gc()執行NIO內存的釋放的(其實2對象自己有clean方法能夠釋放,見jdk隨筆額,heap&direct)
(5)JNI:JNI使得本機代碼(如C語言)能夠調用java方法,JVM會準備空間以供運行本地方法,也會增長java運行時的本機內存佔用。緩存

JVM內存結構

JVM是按照運行時數據的存儲結構來劃份內存結構的,根據不一樣的格式存儲在不一樣的區域。運行時數據包括java程序自己的數據信息和JVM運行Java程序須要的額外數據信息,java虛擬機規範將Java運行時數據分爲6種:PC寄存器數據、Java棧、堆、方法區、本地方法區、運行時常量池。服務器

PC寄存器數據:多線程

用於保存當前執行程序的內存地址,也就是記錄某線程當前執行的方法的那一條指令,如線程的執行被中斷後就會依靠這些數據來恢復(JVM規範之定義了對java方法須要記錄指針,對本地方法則沒有規範)併發

Java棧:框架

java棧與線程相關聯,每建立一個線程就會爲該線程建立一個棧,而線程中運行的每個方法則與棧中的每個棧幀關聯起來,棧幀中包含局部變量,操做棧,方法返回值等。每個方法完成,就會彈出棧幀的元素(操做棧的棧頂元素),做爲返回值,清除這個棧幀。java棧的棧頂就是當前正在執行的活動棧,PC寄存器會指向這個方法的地址。Java棧和線程對應起來,這些數據不是線程共享的,不存在一致性問題jvm

堆:

存儲Java對象的地方,因爲時全部線程共享的,因此須要關心數據的一致性問題。

方法區:

用於存儲類結構信息,如常量池、域,方法數據、方法體,構造函數、包括類中的專用方法、實力初始化、接口初始化等

  • 方法區一樣屬於java堆的永久代
  • 若是使用動態編譯時要注意這部分是否能知足類的存儲
  • 這個區域並不像其餘java堆同樣頻繁地被GC回收

運行時常量池:

包括編譯器的數字常量,方法或者域的引用。(注意,這一區域屬於方法區)

本地方法棧:

JVM爲運行native方法準備的空間。因爲不少native方法是用c語言實現的,因此又叫C棧。這個區域jvm並無嚴格的限制,由不一樣的JVM實現者自由實現。

JVM內存分配策略

  1. 靜態內存分配策略:在編譯期間必須知道內存空間(8個基本類型)的大小才能夠分配(因此能夠在編譯期間分配內存,但java棧中的局部變量和引用等數據一樣使用靜態內存分配,該空間大小是在編譯期間知道,可是在程序加載時才正式分配的,而且這一部份內存在java棧上分配),不容許可變數據類型或者遞歸、嵌套等結構的出現。
  2. 棧內存分配:不須要在編譯時知道程序對數據的需求、但在進入程序模塊時必須知道數據的要求才能夠分配內存。而且按照後進先出的原則進行內存的分配
  3. 堆內存分配:能夠在運行到相應代碼才知道內存空間的大小,靈活可是效率較差

JVM的內存分配主要基於堆和棧,

棧:

  1. 棧的分配時和線程綁定的,爲每個線程建立一個棧,爲線程每調用一個新的方法建立一個棧幀
  2. 棧中主要保存基本類型數據和對象的句柄(引用、指針),棧的數據大小和生存期都必須是肯定的,而本地變量和操做棧的大小均可以在編譯時(class字節碼)肯定
  3. 存取速度比堆要快,僅次於寄存器,這也是爲何運算要留在操做棧中執行
  4. 棧的內存分配是在程序運行時進行的,只是分配的大小是在編譯時肯定的

  1. 堆可供全部線程訪問,主要存放實例數據,因爲時動態分配內存大小的,因此存取速度較慢,一樣經過GC回收內存
  2. 新對象如何分配內存:根據對應Constant_Class_info類型數據執行new指令,賦值,調用init初始化構造器最後才賦值給變量(因此在初始化完成前不該該把實例指針公佈,可類比「對象逸出」的問題),棧中存放的只是指針(引用),而真正的實例數據是存放在堆中的
  3. 堆在運行時請求操做系統分配內存,靈活但效率低

JVM內存回收策略

靜態內存的分配和回收:類中的局部變量和對象的引用都是靜態內存分配的(這一部份內存空間在棧上分配),在編譯時這一部分空間已經肯定,只是在程序被加載時一次性分配,而當方法運行結束時隨着對應棧幀的撤銷回收。

動態內存分配和回收:像實例等數據只有在JVM解析類對象後才能知道具體須要分配多少空間,而且堆中的這些數據只有在對象再也不被引用時纔會被回收。只要某個對象再也不被其餘活動對象所引用就能夠被回收,而活動對象是指能夠被根集合對象所到達的對象。根集合對象所包含的對象跟jvm具體實現有關,可是大都會包含以下一些元素:方法中局部變量的引用、java操做棧中的對象引用、常量池中的對象引用、本地方法中持有的對象引用、類的class對象(當該Class對象再也不被使用時一樣會被回收)。

基於分代的垃圾收集算法:分爲young、old、perm三個區

young區分爲eden區和兩個survivor區,eden區滿後會觸發minorGC,minorGC後仍存活的對象將放到survivor區(若另外一個survivor區存在活動對象將放到同一個區中,保證一個survivor區是空的)
old區中已滿將會觸發FullGC,old區中存放的是:

  • Young的survivor區中已滿後minorGC仍然存活的對象
  • survivor區中足夠老的對象
  • Eden中已滿,而且minorGC後存在,並由於servivor已滿沒法存放的對象。

perm區主要存放類的class對象,只有在FullGC時纔會被回收

三類垃圾收集算法,Serial Collector、Parallel Colllector、CMS Collector

Serial Collector

JVM在client模式下的默認的GC方式(能夠經過配置jvm參數 -XX:+UserSerialGC來配置實用該算法)-XX:+PrintGCDetails 能夠配置打印GC日誌。全部建立的對象都將在Eden區分配,若是建立的對象超過Eden區的大小或者超過PretenureSizeThreshold配置(-XX:PretenureSizeThreshold=123)參數的大小都只能在old區分配當Eden區空間不足時會觸發minorGC,可是觸發minorGC之間會檢查晉升到Old區的平均對象大小是否大於old的剩餘空間,若是大於則觸發FullGC,若是小於則根據HandlePromotionFailure(是否容許擔保失敗)參數,若是爲true則僅觸發MinorGC,不然觸發FullGC。MinorGC時除了將Eden區的非活動對象回收外,還會把一些年老的對象晉升到Old區,而這個年老對象的‘歲數’則經過 -XX:MaxTenuringThreshold=10設置(在survivor的from/to區之間移動一次則爲一歲),另外若是To的Servivor區空間不足移入對象時,這些對象也會直接放入Old區。若是old區或者Perm區空間不足時就會觸發FullGC。GC時由於是串行的,因此動做是單線程完成的,JVM中的其餘應用程序會所有中止。

Parallel Collector

Parallel GC根據MinorGC和FullGC的不一樣分爲三種,分別是ParNewGC、ParallelGC和ParallelOldGC。

ParNewGC:
能夠經過參數 -XX:+UseParNewGC參數來指定,與Serail Collector類似,只是回收是多線程並行的,而且經過一個UseAdaptiveSizePolicy配置參數來控制對象通過多少次回收後能夠直接放入old區。
ParallelGC:
是server模式JVM下的默認GC方式,能夠經過 -XX:+UserParallelGC參數來強制指定,並行回收的線程數能夠經過 -XX:ParallelGCThreads來指定,這個值有個計算公式,若是cpu核數小於8,則能夠和核數同樣,若是大於8值爲:3+(核數*5)/8,能夠經過 -Xmn:10m來控制Young區的大小,而Eden、FROM區的大小比例能夠經過 -XX:SurvivorRatio=8來設置Eden和FromSpace的比值是8:1(固然To區也佔1)。當在Eden區中申請內存空間時,若是Eden區不夠,則比較當前申請空間時否大於Eden的一半,是的話則直接在old中分配,不是的話則會執行MinorGC,可是執行MinorGC以前會檢查old區的平均晉升大小是否大於剩餘空間,大於則觸發FullGC,而且在執行FullGC後會再一次檢查old的晉升的平均大小是否大於剩餘空間,不是的話會再次觸發FullGC,也就是說可能會觸發兩次FullGC。,Young區的晉升規則能夠經過如下參數設置:AlwaysTenure:默認爲false,爲true則表示只要在MinorGC時存活則晉升,NeverTenure,默認爲false,是true則永不晉升。若是上面兩個參數都沒有配置的狀況下設置UseAdaptiveSizePolicy,則啓動時將以InitialTenuringThreshold值做爲存活次數的閥值,而且在每次GC後調整。若是不使用UseAdaptiveSizePolicy則將以MaxTenuringThreshold爲準(經過-XX:-UseAdaptiveSizePolicy設置)另外若是MinorGC時Servivor的To區空間不夠,也會直接放到old區。old或者Perm區滿時會觸發FullGC,若是配置了參數ScavengeBeforeFullGC則在FullGC以前會觸發MinorGC
PrarllelOldGC:
能夠經過 -XX:+UseParallelOldGC參數來強制指定,一樣能夠經過-XX:ParallelGCThreads來指定線程數,這個值有個計算公式,若是cpu核數小於8,則能夠和核數同樣,若是大於8值爲: 3+(核數*5)/8。與ParallelGC的不一樣在於FullGC,它的FullGC動做爲清空整個Heap對中的垃圾對象,清楚Perm區中已經被卸載的類信息,並進行壓縮,而ParallelGC只清楚部分heap堆中的垃圾對象,並對部分空間進行壓縮。

CMS Collector

能夠經過 -XX:+UseConcMarkSweepGC來指定,併發的默認線程爲4,也能夠經過ParallelCMSThreads指定。CMS Collector使用CMS GC、Minor GC、FullGC。而CMS GC不一樣於其餘兩種GC,觸發規律是基於Old區、和Perm區的使用率(觸發後回收對應old或perm區的內存),達到必定比例就會觸發(默認是92%),該比例能夠經過CMSInitiatingOccupancyFraction來指定,另外設置讓Perm區也使用CMS GC能夠經過參數 -XX:+CMSClassUnloadingEnabled來指定。這個模式下的minorGC與Serial Collector基本一致,只是採用多線程。FullGC只在兩種狀況觸發,一種是Eden分配失敗後分配到To區,To區滿分配到Old區,Old區不夠則觸發FullGC,另一種是當CMS GC向Old申請內存失敗時會觸發FullGC。Hotspot1.6下使用這種算法並顯示調用System.gc(如Nio可能須要顯示調用),且設置了ExplicitFCInvokesConcurrent參數,將會致使內存泄露。

內存問題分析

日誌格式:
[GC [<Collector>:<starting occupancy1> -> <ending occupancy1> (total size1) , <paise time1> secs ] <starting occupancy2> -> <ending occupancy2> (total size2), <paise time2> secs ]

<Collector> 收集器的名稱
<starting occupancy1>Young區GC前內存
<endingoccupancy1>Young區GC後內存
<paise time1>YOUNG區局部收集時JVM的暫停時間
<starting occupancy2>表示JVMHeap GC前內存
<endingoccupancy2>表示JVMHeap GC後內存
<paise time2>GC過程當中JVM的暫停總時間

GC日誌對內存泄露的判斷:

  1. 根據<starting occupancy1> - <ending occupancy1>獲得young區被回收或晉升的內存大小,根據<starting occupancy2> - <ending occupancy2>獲得當前整個堆的大小變化,二者的差值就是young區晉升到Old區的值
  2. 假如<ending occupancy2>隨時間的延長一直增加,而且伴隨頻繁的GC,則頗有多是內存泄露,可以使用jstat工具分析
  3. 堆快照的分析,能夠經過參數: -XX:+HeadDumpOnOutOfMemoryError來配置在內存耗盡時記錄下內存快照,同時能夠經過-XX:HeadDumpPath來指定文件路徑,這個文件的命名格式如java_[pid].hprof

JVM Crash日誌分析:

  1. 能夠經過-XX:ErrorFile = /tmp/log/hs_error_%p.log來指定jvm的日誌文件
  2. 文件信息主要分爲四種,退出緣由分類、致使退出的Thread信息(棧信息,具體哪行代碼出錯)、退出時的Process狀態信息(全部線程及線程處於的狀態,jvm的堆信息)、退出時與操做系統相關信息
  3. 退出緣由主要三種:
    • EXCEPTION_ACCESS_VIOLATION:運行的是JVM本身的代碼,極可能是JVM的BUG
    • SIGSEGV:JVM在執行本地代碼或者JNI的代碼,極可能是第三方本地庫有問題
    • EXCEPTION_STACK_OVERFLOW:這個是本地棧溢出的錯誤,能夠將JVM的棧的尺寸調大,主要是兩個參數-Xss 和 -XX:StackShadowPages=n
相關文章
相關標籤/搜索