真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

JVM與問題定位工具的介紹

JVM的內(nèi)存區(qū)域是怎么劃分的?

JVM的內(nèi)存劃分中,有部分區(qū)域是線程私有的,有部分是屬于整個JVM進(jìn)程;有些區(qū)域會拋出OOM異常,有些則不會,了解JVM的內(nèi)存區(qū)域劃分以及特征,是定位線上內(nèi)存問題的基礎(chǔ)。那么JVM內(nèi)存區(qū)域是怎么劃分的呢?

目前創(chuàng)新互聯(lián)建站已為近千家的企業(yè)提供了網(wǎng)站建設(shè)、域名、虛擬主機(jī)、網(wǎng)站托管運(yùn)營、企業(yè)網(wǎng)站設(shè)計(jì)、坪山網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。

首先是程序計(jì)數(shù)器(Program Counter Register),在JVM規(guī)范中,每個線程都有自己的程序計(jì)數(shù)器。這是一塊比較小的內(nèi)存空間,存儲當(dāng)前線程正在執(zhí)行的Java方法的JVM指令地址,即字節(jié)碼的行號。如果正在執(zhí)行Native方法,則這個計(jì)數(shù)器為空。該內(nèi)存區(qū)域是唯一一個在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OOM情況的內(nèi)存區(qū)域。

第二,Java虛擬機(jī)棧(Java Virtal Machine Stack),同樣也是屬于線程私有區(qū)域,每個線程在創(chuàng)建的時候都會創(chuàng)建一個虛擬機(jī)棧,生命周期與線程一致,線程退出時,線程的虛擬機(jī)棧也回收。虛擬機(jī)棧內(nèi)部保持一個個的棧幀,每次方法調(diào)用都會進(jìn)行壓棧,JVM對棧幀的操作只有出棧和壓棧兩種,方法調(diào)用結(jié)束時會進(jìn)行出棧操作。

該區(qū)域存儲著局部變量表,編譯時期可知的各種基本類型數(shù)據(jù)、對象引用、方法出口等信息。

第三,本地方法棧(Native Method Stack)與虛擬機(jī)棧類似,本地方法棧是在調(diào)用本地方法時使用的棧,每個線程都有一個本地方法棧。

第四,(Heap),幾乎所有創(chuàng)建的Java對象實(shí)例,都是被直接分配到堆上的。堆被所有的線程所共享,在堆上的區(qū)域,會被垃圾回收器做進(jìn)一步劃分,例如新生代、老年代的劃分。Java虛擬機(jī)在啟動的時候,可以使用“Xmx”之類的參數(shù)指定堆區(qū)域的大小。

第五,方法區(qū)(Method Area)。方法區(qū)與堆一樣,也是所有的線程所共享,存儲被虛擬機(jī)加載的元(Meta)數(shù)據(jù),包括類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。這里需要注意的是運(yùn)行時常量池也在方法區(qū)中。根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,將拋出OutOfMemoryError異常。由于早期HotSpot JVM的實(shí)現(xiàn),將CG分代收集拓展到了方法區(qū),因此很多人會將方法區(qū)稱為永久代。Oracle JDK8中已永久代移除永久代,同時增加了元數(shù)據(jù)區(qū)(Metaspace)。

第六,運(yùn)行時常量池(Run-Time Constant Pool),這是方法區(qū)的一部分,受到方法區(qū)內(nèi)存的限制,當(dāng)常量池?zé)o法再申請到內(nèi)存時,會拋出OutOfMemoryError異常。
在Class文件中,除了有類的版本、方法、字段、接口等描述信息外,還有一項(xiàng)信息是常量池。每個Class文件的頭四個字節(jié)稱為Magic Number,它的作用是確定這是否是一個可以被虛擬機(jī)接受的文件;接著的四個字節(jié)存儲的是Class文件的版本號。緊挨著版本號之后的,就是常量池入口了。常量池主要存放兩大類常量:

  • 字面量(Literal),如文本字符串、final常量值
  • 符號引用,存放了與編譯相關(guān)的一些常量,因?yàn)镴ava不像C++那樣有連接的過程,因此字段方法這些符號引用在運(yùn)行期就需要進(jìn)行轉(zhuǎn)換,以便得到真正的內(nèi)存入口地址。
    class文件中的常量池,也稱為靜態(tài)常量池,JVM虛擬機(jī)完成類裝載操作后,會把靜態(tài)常量池加載到內(nèi)存中,存放在運(yùn)行時常量池。

第七,直接內(nèi)存(Direct Memory),直接內(nèi)存并不屬于Java規(guī)范規(guī)定的屬于Java虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū)的一部分。Java的NIO可以使用Native方法直接在java堆外分配內(nèi)存,使用DirectByteBuffer對象作為這個堆外內(nèi)存的引用。

下面這張圖,反映了運(yùn)行中的Java進(jìn)程內(nèi)存占用情況:

JVM與問題定位工具的介紹

OOM可能發(fā)生在哪些區(qū)域上?

根據(jù)javadoc的描述,OOM是指JVM的內(nèi)存不夠用了,同時垃圾收集器也無法提供更多的內(nèi)存。從描述中可以看出,在JVM拋出OutOfMemoryError之前,垃圾收集器一般會出馬先嘗試回收內(nèi)存。
從上面分析的Java數(shù)據(jù)區(qū)來看,除了程序計(jì)數(shù)器不會發(fā)生OOM外,哪些區(qū)域會發(fā)生OOM的情況呢?

第一,堆內(nèi)存。堆內(nèi)存不足是最常見的發(fā)送OOM的原因之一,如果在堆中沒有內(nèi)存完成對象實(shí)例的分配,并且堆無法再擴(kuò)展時,將拋出OutOfMemoryError異常,拋出的錯誤信息是“java.lang.OutOfMemoryError:Java heap space”。當(dāng)前主流的JVM可以通過-Xmx和-Xms來控制堆內(nèi)存的大小,發(fā)生堆上OOM的可能是存在內(nèi)存泄露,也可能是堆大小分配不合理。

第二,Java虛擬機(jī)棧和本地方法棧,這兩個區(qū)域的區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù),在內(nèi)存分配異常上是相同的。在JVM規(guī)范中,對Java虛擬機(jī)棧規(guī)定了兩種異常:1.如果線程請求的棧大于所分配的棧大小,則拋出StackOverFlowError錯誤,比如進(jìn)行了一個不會停止的遞歸調(diào)用;2. 如果虛擬機(jī)棧是可以動態(tài)拓展的,拓展時無法申請到足夠的內(nèi)存,則拋出OutOfMemoryError錯誤。

第三,直接內(nèi)存。直接內(nèi)存雖然不是虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū)的一部分,但既然是內(nèi)存,就會受到物理內(nèi)存的限制。在JDK1.4中引入的NIO使用Native函數(shù)庫在堆外內(nèi)存上直接分配內(nèi)存,但直接內(nèi)存不足時,也會導(dǎo)致OOM。

第四,方法區(qū)。隨著Metaspace元數(shù)據(jù)區(qū)的引入,方法區(qū)的OOM錯誤信息也變成了“java.lang.OutOfMemoryError:Metaspace”。對于舊版本的Oracle JDK,由于永久代的大小有限,而JVM對永久代的垃圾回收并不積極,如果往永久代不斷寫入數(shù)據(jù),例如String.Intern()的調(diào)用,在永久代占用太多空間導(dǎo)致內(nèi)存不足,也會出現(xiàn)OOM的問題,對應(yīng)的錯誤信息為“java.lang.OutOfMemoryError:PermGen space

內(nèi)存區(qū)域是否線程私有是否可能發(fā)生OOM
程序計(jì)數(shù)器
虛擬機(jī)棧
本地方法棧
方法區(qū)
直接內(nèi)存

堆內(nèi)存結(jié)構(gòu)是怎么樣的?

可以借助一些工具來了解JVM的內(nèi)存內(nèi)容,具體到特定的內(nèi)存區(qū)域,應(yīng)該用什么工具去定位呢

  • 圖形化工具。圖形化工具的優(yōu)點(diǎn)是直觀,連接到Java進(jìn)程后,可以顯示堆內(nèi)存、堆外內(nèi)存的使用情況,類似的工具有JConsole,VisualVm等。
  • 命令行工具。這類工具可以在運(yùn)行時進(jìn)行查詢,包括jstat,jmap等,可以對堆內(nèi)存、方法區(qū)等進(jìn)行查看。定位線上問題時也多會使用這些工具。jmap也可以生成堆轉(zhuǎn)儲文件(Heap Dump)文件,如果是在linux上,可以將堆轉(zhuǎn)儲文件拉到本地來,使用Eclipse MAT進(jìn)行分析,也可以使用jhap進(jìn)行分析。

關(guān)于內(nèi)存的監(jiān)控與診斷,在后面會進(jìn)行深入了解。現(xiàn)在來看下一個問題:堆內(nèi)的結(jié)構(gòu)是怎么的呢?
站在垃圾收集器的角度來看,可以把內(nèi)存分為新生代與老年代。內(nèi)存的分配規(guī)則取決于當(dāng)前使用的是哪種垃圾收集器的組合,以及內(nèi)存相關(guān)的參數(shù)配置。往大的方向說,對象優(yōu)先分配在新生代的Eden區(qū)域,而大對象直接進(jìn)入老年代。

第一, 新生代的Eden區(qū)域,對象優(yōu)先分配在該區(qū)域,同時JVM可以為每個線程分配一個私有的緩存區(qū)域,稱為TLAB(Thread Local Allocation Buffer),避免多線程同時分配內(nèi)存時需要使用加鎖等機(jī)制而影響分配速度。TLAB在堆上分配,位于Eden中。TLAB的結(jié)構(gòu)如下:

// ThreadLocalAllocBuffer: a descriptor for thread-local storage used by
// the threads for allocation.
//            It is thread-private at any time, but maybe multiplexed over
//            time across multiple threads. The park()/unpark() pair is
//            used to make it avaiable for such multiplexing.
class ThreadLocalAllocBuffer: public CHeapObj {
  friend class VMStructs;
private:
  HeapWord* _start;                              // address of TLAB
  HeapWord* _top;                                // address after last allocation
  HeapWord* _pf_top;                             // allocation prefetch watermark
  HeapWord* _end;                                // allocation end (excluding alignment_reserve)
  size_t    _desired_size;                       // desired size   (including alignment_reserve)
  size_t    _refill_waste_limit;                 // hold onto tlab if free() is larger than this

從本質(zhì)上來說,TLAB的管理是依靠三個指針:start、end、top。start與end標(biāo)記了Eden中被該TLAB管理的區(qū)域,該區(qū)域不會被其他線程分配內(nèi)存所使用,top是分配指針,開始時指向start的位置,隨著內(nèi)存分配的進(jìn)行,慢慢向end靠近,當(dāng)撞上end時觸發(fā)TLAB refill。因此內(nèi)存中Eden的結(jié)構(gòu)大體為:

JVM與問題定位工具的介紹

第二、新生代的Survivor區(qū)域。當(dāng)Eden區(qū)域內(nèi)存不足時會觸發(fā)Minor GC,也稱為新生代GC,在Minor GC存活下來的對象,會被復(fù)制到Survivor區(qū)域中。我認(rèn)為Survivor區(qū)的作用在于避免過早觸發(fā)Full GC。如果沒有Survivor,Eden區(qū)每進(jìn)行一次Minor GC都把對象直接送到老年代,老年代很快便會內(nèi)存不足引發(fā)Full GC。新生代中有兩個Survivor區(qū),我認(rèn)為兩個Survivor的作用在于提高性能,避免內(nèi)存碎片的出現(xiàn)。在任何時候,總有一個Survivor是empty的,在發(fā)生Minor GC時,會將Eden及另一個的Survivor的存活對象拷貝到該empty Survivor中,從而避免內(nèi)存碎片的產(chǎn)生。新生代的內(nèi)存結(jié)構(gòu)大體為:
JVM與問題定位工具的介紹

第三、老年代。老年代放置長生命周期的對象,通常是從Survivor區(qū)域拷貝過來的對象,不過當(dāng)對象過大的時候,無法在新生代中用連續(xù)內(nèi)存的存放,那么這個大對象就會被直接分配在老年代上。一般來說,普通的對象都是分配在TLAB上,較大的對象,直接分配在Eden區(qū)上的其他內(nèi)存區(qū)域,而過大的對象,直接分配在老年代上。

第四、永久代。如前面所說,在早起的Hotspot JVM中有老年代的概念,老年代用于存儲Java類的元數(shù)據(jù)、常量池、Intern字符串等。在JDK8之后,就將老年代移除,而引入元數(shù)據(jù)區(qū)的概念。

第五、Vritual空間。前面說過,可以使用Xms與Xmx來指定堆的最小與最大空間。如果Xms小于Xmx,堆的大小不會直接擴(kuò)展到上限,而是留著一部分等待內(nèi)存需求不斷增長時,再分配給新生代。Vritual空間便是這部分保留的內(nèi)存區(qū)域。

那么綜上所述,可以畫出Java堆內(nèi)的內(nèi)存結(jié)構(gòu)大體為:
JVM與問題定位工具的介紹

通過一些參數(shù),可以來指定上述的堆內(nèi)存區(qū)域的大?。?/p>

-Xmx value 指定最大的堆大小

-Xms value 指定初始的最小堆大小

-XX:NewSize = value 指定新生代的大小

-XX:NewRatio = value 老年代與新生代的大小比例。默認(rèn)情況下,這個比例是2,也就是說老年代是新生代的2倍大。老年代過大的時候,F(xiàn)ull GC的時間會很長;老年代過小,則很容易觸發(fā)Full GC,F(xiàn)ull GC頻率過高,這就是這個參數(shù)會造成的影響。

-XX:SurvivorRation = value . 設(shè)置Eden與Srivivor的大小比例,如果該值為8,代表一個Survivor是Eden的1/8,是整個新生代的1/10。

常用的性能監(jiān)控與問題定位工具有哪些?

在系統(tǒng)的性能分析中,CPU、內(nèi)存與IO是主要的關(guān)注項(xiàng)。很多時候服務(wù)出現(xiàn)問題,在這三者上會體現(xiàn)出現(xiàn),比如CPU飆升,內(nèi)存不足發(fā)生OOM等,這時候需要使用對應(yīng)的工具,來對性能進(jìn)行監(jiān)控,對問題進(jìn)行定位。

對于CPU的監(jiān)控,首先可以使用top命令來進(jìn)行查看,下面是使用top查看負(fù)載的一個截圖:
JVM與問題定位工具的介紹

load average 代表1分鐘、5分鐘、15分鐘的系統(tǒng)平均負(fù)載,從這三個數(shù)字,可以判斷系統(tǒng)負(fù)荷是大還是小。當(dāng)CPU完全空閑的時候,平均負(fù)荷為0;當(dāng)CPU工作量飽和的時候,平均負(fù)荷為1。因此 load average 這三個數(shù)值越低,代表系統(tǒng)負(fù)荷越小,如果電腦里只有一個CPU,把CPU看成一條單行橋,橋上只有一個車道,所有的車都必須從這個橋上通過。那么

  • 系統(tǒng)負(fù)荷為0,代表橋上一輛車也沒有

JVM與問題定位工具的介紹

  • 系統(tǒng)負(fù)荷0.5,意味著橋上一半路段上有車

JVM與問題定位工具的介紹

  • 系統(tǒng)負(fù)荷1,意味著橋上道路已經(jīng)被車占滿

JVM與問題定位工具的介紹

  • 系統(tǒng)負(fù)荷1.7,代表著在橋上車子已經(jīng)滿了(100%),同時還有70%的車子在等待從橋上通過:

JVM與問題定位工具的介紹

從top命令的截圖中可以看到這三個值機(jī)器的load average非常低。如果這三個值非常高,比如超過了50%或60%,就應(yīng)當(dāng)引起注意。從時間維度上來說,如果發(fā)現(xiàn)CPU負(fù)荷慢慢升高,也需要警惕。

其他的內(nèi)存、CPU等性能監(jiān)控工具的使用,以一張腦圖來展示:

JVM與問題定位工具的介紹


網(wǎng)站欄目:JVM與問題定位工具的介紹
文章地址:http://weahome.cn/article/ipsjii.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部