日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java虚拟机内存区域---学习笔记

發(fā)布時(shí)間:2023/12/20 java 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java虚拟机内存区域---学习笔记 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Java虛擬機(jī)

虛擬機(jī):
定義:模擬某種計(jì)算機(jī)體系結(jié)構(gòu),執(zhí)行特定指令集的軟件。
種類:

  • 系統(tǒng)虛擬機(jī)(Virtual Box 、VMware)
  • 進(jìn)程虛擬機(jī)(JVM、Adobe Flash Player、FC模擬器)
  • Java語言虛擬機(jī):可以執(zhí)行Java語言的高級(jí)語言虛擬機(jī)。Java語言虛擬機(jī)并不以一定就可以稱為JVM,譬如:Apache Harmony

    Java虛擬機(jī):
    1.必須通過Java TCK(Technology Compatibility Kit)的兼容性測(cè)試的Java語言虛擬機(jī)才能成為Java虛擬機(jī)。
    2.Java虛擬機(jī)并非一定要執(zhí)行Java程序
    3.業(yè)界三大商用JVM:Oracle HotSpot、Oracle JRockit VM、IBM J9 VM

    Oracle HotSpot虛擬機(jī)是目前OracleJDK和OpenJDK中自帶的虛擬機(jī)。

    Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)

    Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū),有一些區(qū)域是全局共享的,隨著虛擬機(jī)啟動(dòng)而創(chuàng)建,隨著虛擬機(jī)退出而銷毀。有一些區(qū)域是線程私有的,隨著線程開始和結(jié)束而創(chuàng)建和銷毀。

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

  • 程序計(jì)數(shù)器
  • Java堆
  • Java虛擬機(jī)棧
  • 本地方法棧
  • 方法區(qū)


    圖中的方法區(qū)和Java堆這兩部分是所有線程所共享的數(shù)據(jù)區(qū)域,Java虛擬機(jī)棧、本地方法棧、程序計(jì)數(shù)器這三部分是線程私有的數(shù)據(jù)區(qū)域。

  • 程序計(jì)數(shù)器:
    一塊較小的內(nèi)存空間,它的作用可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。可以看作是指針,指示著當(dāng)前程序(字節(jié)碼)正在運(yùn)行的一行代碼。
    由于Java虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器(對(duì)于多核處理器來說是一個(gè)內(nèi)核)都只會(huì)執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ),我們稱這類內(nèi)存區(qū)域?yàn)榫€程私有的內(nèi)存。
    如果線程正在執(zhí)行的是一個(gè)Java方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址:如果正在執(zhí)行的是Native方法,這個(gè)計(jì)數(shù)器值為空。
    程序計(jì)數(shù)器這塊內(nèi)存區(qū)域是唯一一個(gè)在java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。

    Java虛擬機(jī)棧:
    java虛擬機(jī)棧也是線程私有的。生命周期與線程相同。

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

    一個(gè)完整的棧幀包含:局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接棧信息、方法正常完成和異常完成信息

    1.虛擬機(jī)棧中的局部變量表部分

    局部變量表用于方法間參數(shù)的傳遞,以及方法執(zhí)行過程中存儲(chǔ)基礎(chǔ)數(shù)據(jù)類型的值和對(duì)象的引用。
    局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對(duì)象引用(reference類型)和returnAddress類型(指向了一條字節(jié)碼指令的地址,基本不再使用)。
    其中64位長(zhǎng)度的long、double類型的數(shù)據(jù)會(huì)占用2個(gè)局部變量空間(Slot),但是不會(huì)像Java內(nèi)存模型中的非原子性一樣,其余的數(shù)據(jù)類型只占用1個(gè)。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運(yùn)行期間不會(huì)改變局部變量表的大小。

    2.操作數(shù)棧部分:
    是一個(gè)后進(jìn)先出的棧,由若干個(gè)Entry組成,長(zhǎng)度由編譯器決定。

    單個(gè)Entry即可以存儲(chǔ)一個(gè)Java虛擬機(jī)中定義的任意數(shù)據(jù)類型的值,包括long和double類型,但是存儲(chǔ)long和double類型的Entry深度為2,其他類型深度為1。

    在方法執(zhí)行的過程中,棧幀用于存儲(chǔ)計(jì)算參數(shù)和計(jì)算結(jié)果;在方法調(diào)用時(shí),操作數(shù)棧也用來準(zhǔn)備調(diào)用方法的參數(shù)以及接收方法返回結(jié)果。

    例子:演示棧幀的局部變量表和操作數(shù)棧的工作方式


    字節(jié)碼如圖所示:

    方法的執(zhí)行過程:
    《1》當(dāng)執(zhí)行第一條字節(jié)碼指令時(shí):
    程序計(jì)數(shù)器首先記載了這條指令的偏移量(為0),bipush字節(jié)碼指令的作用是將后邊跟的整型數(shù)據(jù)入棧到操作數(shù)棧的棧頂,當(dāng)著條指令執(zhí)行完成后,操作數(shù)棧的棧頂將存在一個(gè)數(shù)100。
    《2》當(dāng)執(zhí)行第二條字節(jié)碼指令時(shí):
    程序計(jì)數(shù)器將記載這條指令的偏移量(為2,原因是:前邊bipush指令中,指令占用了一個(gè)偏移量,參數(shù)100也占用了一個(gè)偏移量),istore_1字節(jié)碼指令(只有指令,沒有參數(shù),所以只會(huì)占用一個(gè)偏移量)作用是把操作數(shù)棧的棧頂元素出棧,并且將數(shù)據(jù)存儲(chǔ)到局部變量表索引號(hào)為1的solt之中。
    《3》當(dāng)執(zhí)行偏移為11的那條指令時(shí):執(zhí)行該指令之前,局部變量表中的1號(hào)solt中為100,2號(hào)solt中為200,3號(hào)solt中為300;iload_1指令的作用是將局部變量表索引號(hào)為1的數(shù)存儲(chǔ)到操作數(shù)棧的棧頂,這條指令完成之后操作數(shù)棧的棧頂數(shù)位100
    《4》當(dāng)執(zhí)行偏移為12的那條指令時(shí):iload_2指令將會(huì)將局部變量表中索引號(hào)為2的數(shù)存儲(chǔ)到操作數(shù)棧的棧頂,當(dāng)這條指令完成之后操作數(shù)棧的棧頂將會(huì)有兩個(gè)元素:分別是棧頂元素200和第二個(gè)元素100
    《5》執(zhí)行指令iadd,iadd指令是一條整數(shù)加法指令,作用是將操作數(shù)棧距離棧頂最近的兩個(gè)數(shù)出棧,然后把這兩個(gè)數(shù)相加的結(jié)果重新存入操作數(shù)棧的棧頂,當(dāng)這條指令執(zhí)行完之后,操作數(shù)棧的深度變?yōu)?,并且存入了數(shù)據(jù)300
    《6》執(zhí)行指令iload_3,將局部變量表索引號(hào)為3的數(shù)300存入操作數(shù)棧的棧頂
    《7》執(zhí)行imul指令,該指令是整數(shù)乘法指令,與前邊執(zhí)行整數(shù)加法指令類似,將會(huì)把距離操作數(shù)棧棧頂最近的兩個(gè)數(shù)出棧,相乘后的結(jié)果存入操作數(shù)棧的棧頂
    《8》指令ireturn指令的作用是將操作數(shù)棧頂?shù)臄?shù)出棧,將這個(gè)值作為方法返回值進(jìn)行返回。

    java虛擬機(jī)棧的兩種異常:
    1.StackOverflowError異常:如果線程請(qǐng)求的棧容量大于虛擬機(jī)棧所允許的最大容量時(shí),Java虛擬機(jī)將會(huì)拋出該異常。
    2.OutOfMemeryError異常:如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展,并且擴(kuò)展的動(dòng)作已經(jīng)嘗試過,但是目前無法申請(qǐng)到足夠的內(nèi)存去完成擴(kuò)展,或者在建立新的線程時(shí)沒有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的虛擬機(jī)棧,那Java虛擬機(jī)將會(huì)拋出該異常。

    例子1:模擬StackOverflowError異常

    package com.test.JavaVM;/*** 模擬StackOverflowError異常* 2015年9月9日 下午8:09:59* @author 張耀暉**/ public class JavaVMStackSOF {private int stackLength = 1;public static void main(String[] args) {JavaVMStackSOF oom = new JavaVMStackSOF();try {oom.stackLeak();} catch (Throwable e) {System.out.println("棧的深度:"+oom.stackLength);throw e;}}//該方法遞歸調(diào)用自己public void stackLeak(){stackLength++;stackLeak();}}

    運(yùn)行結(jié)果:

    例子2:模擬OutOfMemeryError異常

    package com.test.JavaVM;/*** 模擬OutOfMemeryError異常* 2015年9月9日 下午8:47:24* @author 張耀暉**/ public class JavaVMStackOOM {public static void main(String[] args) {JavaVMStackOOM oom = new JavaVMStackOOM();oom.stackLeakByThread();}private void dontStop(){while(true){}}public void stackLeakByThread(){while(true){Thread thread = new Thread(new Runnable() {@Overridepublic void run() {dontStop();}});thread.start();}} }

    運(yùn)行結(jié)果:

    這段代碼會(huì)導(dǎo)致系統(tǒng)變得很卡,因?yàn)檫@段代碼在不斷的創(chuàng)建線程,直到系統(tǒng)分配給JVM虛擬機(jī)的內(nèi)存不夠時(shí),就會(huì)拋出OutOfMemeryError異常。

    本地方法棧
    本地方法棧與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,它們之間的區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。

    Sun HotSpot虛擬機(jī)直接就把本地方法棧和虛擬機(jī)棧合二為一了。

    與虛擬機(jī)棧一樣,本地方法棧區(qū)域會(huì)拋出StackOverflowError和OutOfMemoryError異常。

    Java堆
    java堆是被java所有線程內(nèi)存共享的內(nèi)存區(qū)域。
    Java堆是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。
    在虛擬機(jī)創(chuàng)建的時(shí)候創(chuàng)建,在虛擬機(jī)銷毀的時(shí)候銷毀。
    此區(qū)域的唯一作用:存放Java對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例以及數(shù)組都要在堆上分配內(nèi)存。
    Java堆是垃圾收集器管理的主要區(qū)域,即常說的GC堆,該區(qū)域是實(shí)現(xiàn)自動(dòng)內(nèi)存管理的。
    因?yàn)镴ava堆是全局共享的內(nèi)存區(qū)域,所有Java線程所分配的Java對(duì)象都存儲(chǔ)在Java堆之中,以為這個(gè)數(shù)據(jù)區(qū)域會(huì)被Java線程共同使用,為了避免各個(gè)Java線程所可能產(chǎn)生的競(jìng)爭(zhēng)關(guān)系,例如,兩個(gè)Java線程同時(shí)使用Java堆中的一塊內(nèi)存來分配不同的對(duì)象,這時(shí)候它們就對(duì)Java堆中的空間產(chǎn)生了競(jìng)爭(zhēng),為了避免這種競(jìng)爭(zhēng)關(guān)系,Java虛擬機(jī)很可能會(huì)把Java堆根據(jù)不同的各個(gè)線程劃分出若干個(gè)線程私有的分配緩沖區(qū),這時(shí)候各個(gè)線程會(huì)在自己獨(dú)立的分配緩沖區(qū)中分配對(duì)象,當(dāng)分配緩沖區(qū)的空間用完的時(shí)候,才會(huì)加鎖,并且向Java堆分配新的分配緩沖區(qū)的內(nèi)存。
    Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上連續(xù)就行,就像我們的磁盤空間一樣。在實(shí)現(xiàn)時(shí),既可以實(shí)現(xiàn)成固定大小的,也可以實(shí)現(xiàn)成可擴(kuò)展的,不過當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展的來實(shí)現(xiàn)的。

    如果在堆中沒有內(nèi)存完成實(shí)例分配,并且堆也無法在擴(kuò)展時(shí),將拋出OutOfMemoryError異常。

    從棧到堆的關(guān)聯(lián)過程:
    第一種方式:HotSpot虛擬機(jī)采用的方式

    第二種方式:

    模擬Java堆中的OutOfMemoryError異常例子:

    package com.test.JavaVM;import java.util.ArrayList; import java.util.List;/*** 模擬Java堆的OutOfMemoryError異常* 2015年9月10日 下午3:06:03* @author 張耀暉**/ public class HeapOOM {static class OOMObject{}public static void main(String[] args) {List<OOMObject> list = new ArrayList<OOMObject>();//會(huì)不斷的創(chuàng)建OOMObject的對(duì)象,并且創(chuàng)建出來的這些對(duì)象都被List所引用,保證了創(chuàng)建的這些對(duì)象不被Java虛擬機(jī)的自動(dòng)回收機(jī)制所回收while(true){list.add(new OOMObject());}}}

    運(yùn)行結(jié)果:

    方法區(qū):
    方法區(qū)與Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已經(jīng)被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
    Java虛擬機(jī)規(guī)范對(duì)方法區(qū)的限制相當(dāng)寬松,除了和Java堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴(kuò)展外,還可以選擇不實(shí)現(xiàn)垃圾收集。相對(duì)而言,垃圾收集行為在這個(gè)區(qū)域是比較少出現(xiàn)的,但并非數(shù)據(jù)進(jìn)入方法區(qū)就如永久代的名字一樣永久存在了。這個(gè)區(qū)域的內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類型的卸載,一般來說,這個(gè)區(qū)域的回收成績(jī)比較難以令人滿意,尤其是類型的卸載,條件相當(dāng)?shù)目量?#xff0c;但是這部分區(qū)域的回收確實(shí)是必要的。在Sun公司的BUG列表中,曾出現(xiàn)過的若干個(gè)嚴(yán)重的BUG就是由于低版本的HotSpot虛擬機(jī)對(duì)此區(qū)域未完成回收而導(dǎo)致內(nèi)存泄漏。
    當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí),將拋出OutOfMemoryError異常。

    運(yùn)行時(shí)常量池
    運(yùn)行時(shí)常量池是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等信息外,還有一項(xiàng)信息是常量池,用于存放編譯期生成的各種字面量和符號(hào)的引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。
    運(yùn)行時(shí)常量池相對(duì)于Class文件常量池的另外一個(gè)重要的特征是具備動(dòng)態(tài)性,Java語言并不要求常量一定只有編譯期才能產(chǎn)生,也就是并非預(yù)置入Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用的比較多的便是String類的intern()方法。
    既然運(yùn)行時(shí)常量池是方法區(qū)的一部分,自然受到方法區(qū)內(nèi)存的限制,當(dāng)常量池?zé)o法在申請(qǐng)到內(nèi)存時(shí)會(huì)拋出OutOfMemoryError異常。

    HotSpot虛擬機(jī)方法區(qū)實(shí)現(xiàn)的變遷:
    在JDK1.2~JDK6,HotSpot使用永久代實(shí)現(xiàn)方法區(qū)。
    在JDK7開始,HotSpot開始了移除永久代的計(jì)劃,符號(hào)表被移到了Native Heap中,字符串常量和類的靜態(tài)引用被移到Java Heap中。
    在JDK8開始,永久代已被元空間所代替。

    直接內(nèi)存
    直接內(nèi)存并不是java虛擬機(jī)運(yùn)行時(shí)內(nèi)存區(qū)域的一部分,也不是Java虛擬機(jī)規(guī)范中定義的一塊內(nèi)存區(qū)域,但是該區(qū)域也頻繁的使用,也會(huì)導(dǎo)致OutOfMemoryError異常。
    直接內(nèi)存是被java所有線程內(nèi)存共享的內(nèi)存區(qū)域。
    直接內(nèi)存區(qū)域能被Java虛擬機(jī)進(jìn)行自動(dòng)內(nèi)存管理,但是檢測(cè)手段是有一些簡(jiǎn)陋的。

    總結(jié)

    以上是生活随笔為你收集整理的Java虚拟机内存区域---学习笔记的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。