偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

JVM性能調(diào)優(yōu)Java內(nèi)存區(qū)域與內(nèi)存溢出異常

存儲(chǔ) 存儲(chǔ)軟件
JVM 的多線程是通過線程輪流切換并分配CPU時(shí)間的方式來實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器(對于多核處理器來說是一個(gè)內(nèi)核)都只會(huì)執(zhí)行一條線程中的指令。

[[337100]]

 JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)的劃分

 

JVM性能調(diào)優(yōu)Java內(nèi)存區(qū)域與內(nèi)存溢出異常

 

線程共享的數(shù)據(jù)區(qū)特征

  • 虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,生命周期與進(jìn)程相同
  • 內(nèi)存分配和回收是動(dòng)態(tài)的,GC負(fù)責(zé)的區(qū)域

線程私有的數(shù)據(jù)區(qū)特征

  • 線程啟動(dòng)時(shí)創(chuàng)建,生命周期與線程相同
  • 內(nèi)存的分配和回收都具備確定性,方法結(jié)束或線程結(jié)束就回收,不需過多考慮回收問題

程序計(jì)數(shù)器(Program Counter Register)

一塊較小的內(nèi)存空間,當(dāng)前線程所執(zhí)行字節(jié)碼的行號指示器。

  • 線程私有
  • JVM 5大數(shù)據(jù)區(qū)中唯一一個(gè)沒有規(guī)定OOM的區(qū)域
  • 執(zhí)行Java方法時(shí),計(jì)數(shù)器記錄的是字節(jié)碼指令的地址;執(zhí)行Native方法時(shí),計(jì)數(shù)器值為空(undefined)

為什么需要程序計(jì)數(shù)器呢?

JVM 的多線程是通過線程輪流切換并分配CPU時(shí)間的方式來實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器(對于多核處理器來說是一個(gè)內(nèi)核)都只會(huì)執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器。

Java虛擬機(jī)棧(Java Virtual Machine Stacks)

虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程。

 

JVM性能調(diào)優(yōu)Java內(nèi)存區(qū)域與內(nèi)存溢出異常

 

  • 線程私有,生命周期與線程相同
  • StackOverflowError:棧深度大于虛擬機(jī)所允許的深度
  • OutOfMemorryError:如果虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展(大部分虛擬機(jī)可動(dòng)態(tài)擴(kuò)展,只不過Java虛擬機(jī)規(guī)范中也允許固定長度的虛擬機(jī)棧),擴(kuò)展時(shí)無法申請足夠內(nèi)存

經(jīng)常有人把Java內(nèi)存區(qū)分為堆內(nèi)存(Heap)和棧內(nèi)存(Stack),這種分法比較粗糙,其流行只能說明大多數(shù)程序員最關(guān)注的、與對象內(nèi)存分配關(guān)系最密切的內(nèi)存區(qū)域是這兩塊。其中所指的『堆』就是后面即將提到的Java堆,而所指的『棧』就是這里的虛擬機(jī)棧,或者說是虛擬機(jī)棧中局部變量表部分。

局部變量表

局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference)和returnAddress類型(指向了一條字節(jié)碼指令的地址)。

局部變量表的容量以變量槽(Variable Slot)為最小單位

64位長度的 long 和 double 類型的數(shù)據(jù)占用2個(gè)slot,其余數(shù)據(jù)類型只占用1個(gè)slot

局部變量表所需內(nèi)存空間在編譯期已經(jīng)確定,在方法運(yùn)行期間不會(huì)改變大小

局部變量表的影響

讓我們通過以下示例代碼直觀地感受一下局部變量表的影響。第一個(gè)recursion()沒有參數(shù)和局部變量,第二個(gè)包含3個(gè)參數(shù)和4個(gè)局部變量,因此后者占用更多內(nèi)存空間,在jvm參數(shù)-Xss 128K下分別執(zhí)行兩個(gè)方法:

  1. private static int count=0; 
  2. public static void recursion(){ 
  3.  System.out.println("count="+count); 
  4.  count++; 
  5.  recursion(); 
  6. public static void recursion(int a,int b,int c){ 
  7.  long l1=12; 
  8.  short sl=1; 
  9.  byte b1=1; 
  10.  String s="1"
  11.  System.out.println("count="+count); 
  12.  count++; 
  13.  recursion(1,2,3); 

執(zhí)行第一個(gè)無參的recursion()的輸出:

  1. count=4495 
  2. Exception in thread "main" java.lang.StackOverflowError 
  3. at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77) 

執(zhí)行第二個(gè)有參的recursion()的輸出:

  1. count=3865 
  2. Exception in thread "main" java.lang.StackOverflowError 
  3. at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77) 
  4. at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:564) 
  5. at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:619) 

可見,在同等的棧容量下,局部變量少的函數(shù)可以支持更深的調(diào)用層次,換句話說,一個(gè)線程中可調(diào)用的方法數(shù)就越多。

本地方法棧(Native Method Stack)

本地方法棧與虛擬機(jī)棧的作用類似,區(qū)別只是前者為執(zhí)行Native方法服務(wù),后者為執(zhí)行Java方法服務(wù)。有的虛擬機(jī)(如Sun HotSpot虛擬機(jī))直接把本地方法棧和虛擬機(jī)棧合二為一。

  • 線程私有
  • 和Java虛擬機(jī)棧一樣,也會(huì)拋出StackOverflowError 和 OutOfMemorryError

Java堆(Java Heap)

所有的對象實(shí)例以及數(shù)組都要在堆上分配,但是隨著JIT編譯器的發(fā)展與逃逸分析技術(shù)逐漸成熟,棧上分配、標(biāo)量替換優(yōu)化技術(shù)將會(huì)導(dǎo)致一些微妙的變化發(fā)生,所有的對象都分配在堆上也漸漸變得不是那么"絕對"了。

 

  • 線程共享
  • OutOfMemorryError:Java heap space
  • GC的主要區(qū)域,因此也被稱作"GC堆"
  • JVM所管理的內(nèi)存中最大的一塊
  • 虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建

虛擬機(jī)規(guī)范對該區(qū)的限制

  • 可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上連續(xù)即可
  • 即可以實(shí)現(xiàn)成固定大小的,也可以是可擴(kuò)展的,當(dāng)前主流虛擬機(jī)都是按照可擴(kuò)展來實(shí)現(xiàn)的

方法區(qū)(Method Area)

用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。

  • 線程共享
  • OutOfMemorryError:PermGen spage
  • GC比較少出現(xiàn)(虛擬機(jī)實(shí)現(xiàn)時(shí)也可以選擇不實(shí)現(xiàn)GC,但事實(shí)證明該區(qū)域的GC是必要的)
  • 有一個(gè)別名叫"Non-Heap"(非堆):虛擬機(jī)規(guī)范中把方法區(qū)描述為堆的一個(gè)邏輯部分,為了與Java堆區(qū)分開來

淺談“永久代”(Permanent Generation)

在HotSpot虛擬機(jī)上,很多人都更愿意把方法區(qū)稱為“永久代”,但本質(zhì)上兩者并不等價(jià),僅僅是因?yàn)镠otSpot虛擬機(jī)的設(shè)計(jì)團(tuán)隊(duì)使用永久代來實(shí)現(xiàn)方法區(qū)而已。而對于其他虛擬機(jī)(如BEA JRockit、IBM J9等)來說是不存在永久代的概念的。目前,在HotSpot虛擬機(jī)上也有放棄永久代并逐步改為采用Native Memory來實(shí)現(xiàn)方法區(qū)的規(guī)劃了,在JDK1.7的HotSpot中,已經(jīng)把原本放在永久代的字符串常量池移出。

虛擬機(jī)規(guī)范對該區(qū)的限制

  • 可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上連續(xù)即可
  • 即可以實(shí)現(xiàn)成固定大小的,也可以是可擴(kuò)展的,當(dāng)前主流虛擬機(jī)都是按照可擴(kuò)展來實(shí)現(xiàn)的
  • 可以選擇不實(shí)現(xiàn)垃圾收集

垃圾收集行為在方法區(qū)是比較少出現(xiàn)的,但并非數(shù)據(jù)進(jìn)入了方法區(qū)就如永久代的名字一樣“永久”存在了。這里的內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載,一般來說,這里的回收“成績”難以令人滿意,尤其是類型的卸載,條件相當(dāng)苛刻,但是這部分區(qū)域的回收確實(shí)是必要的。

運(yùn)行時(shí)常量池(Runtime Constant Pool)

運(yùn)行時(shí)常量池是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。

直接內(nèi)存

并非虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范定義的內(nèi)存區(qū)域。但這部分也被頻繁使用,而且也可能導(dǎo)致OOM。

JDK 1.4中加入的NIO類,引入了一種基于通道與緩沖區(qū)的I/O方式,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個(gè)存儲(chǔ)在Java堆中的 DirectByteBuffer 對象作為這塊內(nèi)存的引用進(jìn)行操作。

本機(jī)直接內(nèi)存的分配不會(huì)受到Java堆大小的限制,但還是會(huì)受到本機(jī)總內(nèi)存大小以及處理器尋址空間的限制。

5大數(shù)據(jù)區(qū)對比

JVM數(shù)據(jù)區(qū) 私有/共享 創(chuàng)建時(shí)機(jī) 生命周期 垃圾收集 內(nèi)存溢出 程序計(jì)數(shù)器 線程私有 線程啟動(dòng)時(shí) 與線程相同 無 無 虛擬機(jī)棧 線程私有 線程啟動(dòng)時(shí) 與線程相同 無 StackOverflowError OutOfMemoryError 本地方法棧 線程私有 線程啟動(dòng)時(shí) 與線程相同 無 StackOverflowError OutOfMemoryError Java堆 線程共享 JVM啟動(dòng)時(shí) 與進(jìn)程相同 主要區(qū)域 OutOfMemoryError: Java heap space 方法區(qū) 線程共享 JVM啟動(dòng)時(shí) 與進(jìn)程相同 較少出現(xiàn) OutOfMemoryError: PermGen space 對象初探秘

對象的創(chuàng)建

在Java中,從語言層面上來看,創(chuàng)建對象通常只是一個(gè) new 關(guān)鍵字而已,而在虛擬機(jī)中,對象(這里討論的對象僅限于普通對象,不包括數(shù)組和Class對象)的創(chuàng)建又是怎樣一個(gè)過程呢?

虛擬機(jī)遇到一條 new 指令時(shí):

執(zhí)行類加載檢查

  • 檢查指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號引用,并檢查這個(gè)符號引用代表的類是否已被加載、解析和初始化過。
  • 若沒有,則執(zhí)行相應(yīng)的類加載過程。

為新生對象分配內(nèi)存

指針碰撞

假設(shè)Java堆中內(nèi)存是絕對規(guī)整的,所有用過的內(nèi)存放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那所分配內(nèi)存就僅僅是把指針向著空閑內(nèi)存那邊移動(dòng)一段與對象大小相等的距離,這種分配方式稱為“指針碰撞”。

空閑列表

如果Java堆中的內(nèi)存并不是規(guī)整的,已使用內(nèi)存和空閑內(nèi)存相互交錯(cuò),那就沒辦法簡單地進(jìn)行指針碰撞了,虛擬機(jī)就必須維護(hù)一個(gè)列表,記錄哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找出一塊足夠大的空間劃分給對象實(shí)例,并更新列表上的記錄,這種分配方式稱為“空閑列表”。

如何選擇分配方式

選擇哪種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有 壓縮整理 功能決定。因此,在使用Serial、ParNew等待Compact過程的收集器時(shí),系統(tǒng)采用的分配算法是指針碰撞,而使用CMS這種基于Mark-Sweep算法的收集器時(shí),通常采用空閑列表。

對象的內(nèi)存布局

在HotSpot虛擬機(jī)中,對象在內(nèi)存中存儲(chǔ)的布局可以分為 3 塊區(qū)域:對象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)、和對齊填充(Padding)。

對象頭

對象頭包括兩部分信息,第一部分用于存儲(chǔ)對象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志等,官方稱這些數(shù)據(jù)為 “Mark Word” 。

對象頭的另一部分是類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過該指針來確定這個(gè)對象是哪個(gè)類的實(shí)例。但并非所有的虛擬機(jī)實(shí)現(xiàn)都必須在對象數(shù)據(jù)上保留類型指針,換句話說,查找對象的元數(shù)據(jù)信息并不一定要經(jīng)過對象本身。另外,如果對象是一個(gè)Java數(shù)組,那在對象頭中還必須有一塊用于記錄數(shù)組長度的數(shù)據(jù),因?yàn)樘摂M機(jī)可以通過普通對象的元數(shù)據(jù)確定該對象的大小,但是從數(shù)組的元數(shù)據(jù)中卻無法確定數(shù)組的大小。

實(shí)例數(shù)據(jù)

實(shí)例數(shù)據(jù)是對象真正存儲(chǔ)的有效信息,也是在程序代碼中定義的各種類型的字段內(nèi)容。無論是從父類繼承下來的,還是子類中定義的,都需要記錄。

對齊填充

對齊填充并不是必然存在的,也沒有特別的含義,它僅僅起著占位符的作用。由于HotSpot VM要求對象起始地址必須是8字節(jié)的整數(shù)倍,話句話說,就是對象大小必須是8字節(jié)的整數(shù)倍。而對象頭部分正好是8字節(jié)的倍數(shù),因此,當(dāng)對象的實(shí)例數(shù)據(jù)部分沒有對齊時(shí),就需要通過對齊填充來補(bǔ)全。

對象的訪問定位

Java程序需要通過棧(具體是虛擬機(jī)棧中的局部變量表)上的reference數(shù)據(jù)來操作堆上的具體對象。而reference如何定位、訪問堆中對象的具體位置,則取決于不同的虛擬機(jī)實(shí)現(xiàn)。目前主流的訪問方式有使用 句柄 和 直接指針 兩種。

問題:毫無疑問,局部變量中的reference存放在棧中,那么成員變量中的reference又是存放在哪里?

筆者也是看到這里時(shí)感到疑惑,上網(wǎng)查證了很多,但是說法不一,有的認(rèn)為在棧中(一概而論:對象在堆,引用在棧),有的認(rèn)為在堆中(比如https://blog.csdn.net/qq_36596145/article/details/76300922),筆者認(rèn)為在方法區(qū)中(具體是方法區(qū)中的運(yùn)行時(shí)常量池,因?yàn)閏lass文件中有一個(gè)常量池,用于存放編譯期生成的各種字面量和符號引用,這部分信息在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放)。

如果有讀者可以給出明確的結(jié)論,還請不吝賜教!

句柄式

在Java堆中劃分出一塊內(nèi)存用作句柄池,reference中存儲(chǔ)對象的句柄地址,而句柄中包含了對象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息。

 

JVM性能調(diào)優(yōu)Java內(nèi)存區(qū)域與內(nèi)存溢出異常

 

使用句柄訪問方式的最大好處就是reference中存儲(chǔ)的是穩(wěn)定的句柄地址,在對象被移動(dòng)(垃圾收集時(shí)移動(dòng)對象是很普遍的行為)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而reference本身不需要修改。

直接指針

reference中存儲(chǔ)的直接就是對象地址,此時(shí)對象的布局中就必須考慮如何放置對象類型數(shù)據(jù)的指針。

HotSpot虛擬機(jī)采用的就是這種方式。

 

JVM性能調(diào)優(yōu)Java內(nèi)存區(qū)域與內(nèi)存溢出異常

 

使用直接指針訪問方式的最大好處就是速度更快,它節(jié)省了一次指針定位的時(shí)間開銷。由于對象訪問在Java中非常頻繁,因此這類開銷積少成多后也是一項(xiàng)非??捎^的執(zhí)行成本。

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2012-05-15 02:04:22

JVMJava

2015-12-28 11:41:57

JVM內(nèi)存區(qū)域內(nèi)存溢出

2010-09-26 10:53:00

JVM內(nèi)存調(diào)優(yōu)設(shè)置

2010-09-25 15:52:27

JVM內(nèi)存JVM

2014-12-19 11:07:40

Java

2023-05-29 07:43:32

JVM內(nèi)存調(diào)優(yōu)

2023-02-10 09:28:23

優(yōu)化工具

2024-03-11 08:22:40

Java內(nèi)存泄漏

2017-07-21 08:55:13

TomcatJVM容器

2020-12-30 15:06:39

開發(fā)技能代碼

2025-06-16 07:40:00

2024-12-04 15:49:29

2009-07-09 09:47:26

Sun JVM

2010-09-17 14:17:05

JVM內(nèi)存設(shè)置

2010-09-25 12:54:24

JVM內(nèi)存

2015-03-30 11:18:50

內(nèi)存管理Android

2023-04-24 14:54:09

JVM性能調(diào)優(yōu)

2021-11-21 23:03:38

jvm調(diào)優(yōu)虛擬機(jī)

2012-01-10 14:35:08

JavaJVM

2019-11-01 08:49:07

JVM監(jiān)控性能
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號