<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JVM的基礎知識點Java的內(nèi)存模型

          共 4952字,需瀏覽 10分鐘

           ·

          2020-08-07 04:39

          閱讀文本大概需要3分鐘。

          ? ? ? Java虛擬機是Java工程師必學的進階功課,這段時間開始死磕JVM。今天梳理一下JVM的基礎知識點Java的內(nèi)存模型!

          程序計數(shù)器

          是什么:程序計數(shù)器是很小的一塊內(nèi)存空間,它是當前線程所執(zhí)行的字節(jié)碼的行號指示器。

          有什么用:解釋器通過這個計數(shù)器來選取下一條需要執(zhí)行的字節(jié)碼指令。
          存儲什么內(nèi)容:如果線程執(zhí)行的是Java方法,存儲的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址;如果是native方法,計數(shù)器值為空(undefined)。

          為什么是線程私有的:多線程是線程輪流切換并分配處理器執(zhí)行時間片的方式來實現(xiàn)的,在任何確定的時刻,一個處理器(對于多核處理器來說就是一個內(nèi)核)都只會執(zhí)行一條線程,所以,為了線程在切換后能恢復到正確的執(zhí)行位置,每個線程應該獨立擁有一個程序計數(shù)器。

          會出現(xiàn)什么異常情況:唯一一個無內(nèi)存溢出異常的區(qū)域。

          Java虛擬機棧

          是什么:虛擬機棧是Java方法的內(nèi)存模型,每一個Java方法從調(diào)用到執(zhí)行完成就對應著一個棧幀在虛擬機棧中的入棧和出棧。

          存儲什么內(nèi)容:每個方法的執(zhí)行就會創(chuàng)建一個棧幀,這個棧幀會存儲這個Java方法的局部變量表,操作數(shù)棧,動態(tài)鏈接,方法出口等信息。

          為什么是線程私有的:每個線程所執(zhí)行的方法可能是不一樣的。

          會出現(xiàn)什么異常情況:如果線程請求的棧深度>虛擬機允許的深度,拋出棧溢出異常;如果擴展時無法申請到足夠的內(nèi)存,拋出內(nèi)存溢出異常。

          本地方法棧

          是什么:本地方法棧的作用和虛擬機棧非常像是,只不過本地方法棧是native方法的內(nèi)存模型,每一個native方法從調(diào)用到執(zhí)行完成就對應著一個棧幀在本地方法棧中的入棧和出棧。

          存儲什么內(nèi)容:同虛擬機棧。

          為什么是線程私有的:同虛擬機棧。

          會出現(xiàn)什么異常情況:同虛擬機棧。

          Java堆

          是什么:Java堆是Java虛擬機管理的內(nèi)存中最大的一塊,Java堆是在虛擬機啟動的時候創(chuàng)建的。

          存儲什么內(nèi)容:存放對象實例,幾乎所有的對象實例都在這個內(nèi)存區(qū)域分配內(nèi)存。

          為什么是線程共享的:所有的線程都可以訪問不同的對象。其實從內(nèi)存分配的角度來看,線程共享的Java堆可能其實是多個線程私有的分配緩沖區(qū),不同的線程將各自的對象實例放在看似共享的Java堆的各自的緩沖區(qū)上,這樣劃分可以更好的回收內(nèi)存,也可以更好點分配內(nèi)存。

          會出現(xiàn)什么異常情況:Java堆可以處于物理上不連續(xù)的內(nèi)存空間上,但邏輯上一定是連續(xù)的,在堆中沒有內(nèi)存可以完成對象實例的分配,且無法再擴展時,會拋出內(nèi)存溢出異常。

          方法區(qū)

          是什么:和堆一樣,是各個線程共享的內(nèi)存區(qū)域。很多人把方法區(qū)稱為永久代,但是本質(zhì)上這兩個不等價,Java虛擬機將GC分代收集擴展至方法區(qū),使用永久代來實現(xiàn)方法區(qū),這樣GC收集器就能像管理Java堆一樣管理方法區(qū)而不需要再寫一套GC收集來管理方法區(qū)。當然在方法區(qū)里也可以設置不進行GC收集。

          存儲什么內(nèi)容:已被虛擬機加載的類信息,類常量,類的靜態(tài)變量,即時編譯器編譯后的代碼等。運行時常量池也是方法區(qū)的一部分。

          為什么是線程共享的:各個線程都可以訪問虛擬機加載的類。

          會出現(xiàn)什么異常情況:內(nèi)存溢出異常。

          直接內(nèi)存

          是什么:直接內(nèi)存并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機定義的內(nèi)存區(qū)域,但也經(jīng)常被使用。JDK1.4加入了NIO類,一種基于通道與緩沖區(qū)的新I/O方式,NIO可以使用native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為直接內(nèi)存的引用來操作直接內(nèi)存,這樣可以避免在Java堆和native堆來回復制數(shù)據(jù),從而提高了性能。

          會出現(xiàn)什么異常情況:受機器總內(nèi)存的影響,會出現(xiàn)內(nèi)存溢出異常。


          Java虛擬機中描述了兩種異常:

          • 如果線程請求的棧深度大于虛擬機所允許的最大深度,將拋出StackOverflowError異常;

          • 如果在虛擬機中無法申請到足夠多的內(nèi)存空間,將拋出OutOfMemoryError異常。

          我們都知道Java虛擬機各個內(nèi)存區(qū)域(除了程序計數(shù)器)都有發(fā)生內(nèi)存溢出的可能,但到底什么樣的操作或程序才會導致內(nèi)存溢出或棧溢出的異常呢?我們分不同的內(nèi)存區(qū)域來解釋這個問題。


          0x01、對于Java堆內(nèi)存區(qū)域

          Java堆中只會產(chǎn)生OutOfMemoryError異常。

          先搞清楚Java堆內(nèi)存放的是什么,還不清楚的可以回顧下這篇文章《死磕JVM-Java內(nèi)存模型》,從這篇文章里我們知道Java堆內(nèi)存存放的是對象實例,所以原理上只要我們不斷創(chuàng)建對象,并且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清楚這些對象,也就是說當Eden區(qū)滿的時候,GC被觸發(fā)時,讓GC誤以為內(nèi)存中的對象還存活著,那么在對象數(shù)量達到最大堆容量限制的時候就會產(chǎn)生內(nèi)存溢出的異常。如下代碼就會產(chǎn)生內(nèi)存溢出的異常:

          public?class?堆溢出{
          ?????static?class?OOMError{}
          ?????public?static?void?main(String[]?args){
          ??????????List?list?=new?ArrayList();
          ??????????while(true){
          ???????????????list.add(newOOMError());
          ??????????}
          ?????}
          }

          運行結(jié)果:

          Exceptionin?thread?"main"?java.lang.OutOfMemoryError:Java?heap?space
          ?????at?java.util.Arrays.copyOf(Arrays.java:3210)
          ?????at?java.util.Arrays.copyOf(Arrays.java:3181)
          ?????at?java.util.ArrayList.grow(ArrayList.java:261)
          ?????at?java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
          ?????at?java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
          ?????at?java.util.ArrayList.add(ArrayList.java:458)
          ?????at?com.intelligentler.jvm.堆溢出.main(堆溢出.java:13)

          “Java heap space”提示著產(chǎn)生OutOfMemoryError異常的Java虛擬機的內(nèi)存區(qū)域,也就是Java堆內(nèi)存。

          如何解決發(fā)生在Java堆內(nèi)存的OutOfMemoryError異常呢?

          首先我們要分清楚產(chǎn)生OutOfMemoryError異常的原因是內(nèi)存泄露還是內(nèi)存溢出,如果內(nèi)存中的對象確實都必須存活著而不像上面那樣不斷地創(chuàng)建對象實例卻不使用該對象,則是內(nèi)存溢出,而像上面代碼中的情況則是內(nèi)存泄露。

          如果是內(nèi)存泄露,我們可以通過一些內(nèi)存查看工具來查看泄露對象到GC Roots的引用鏈,找到泄露對象是通過怎樣的路徑與GC Roots相關聯(lián)并導致GC無法自動回收這些泄露對象,掌握了這些信息,我們就能比較準確地定位出泄露代碼的位置。

          如果不是內(nèi)存泄露,也就是說內(nèi)存中的對象確實都還必須存活,那么應該檢查虛擬機的堆參數(shù),看看是否還可以將機器物理內(nèi)存調(diào)大,同時在代碼上檢查是否存在某些對象生命周期過長、持有狀態(tài)時間過長的情況。


          0x02、對于虛擬機棧和本地方法棧

          在這一部分內(nèi)存區(qū)域,可能產(chǎn)生OutOfMemoryError異常和StackOverflowError異常。

          如果定義大量的本地變量,增大此方法幀中本地變量表的長度或者設置-Xss參數(shù)減少棧內(nèi)存容量,這兩種操作都會拋出StackOverflowError異常,如下面的代碼:

          public?class?棧溢出{
          ?????privateint?stackLength?=1;
          ?????publicvoid?addStackLength(){
          ??????????stackLength++;
          ??????????addStackLength();
          ?????}

          ?????public?static?void?main(String[]?args)throws?Throwable{
          ??????????棧溢出?oom?=new?棧溢出();
          ??????????try{
          ???????????????oom.addStackLength();
          ??????????}catch(Throwable?e){
          ???????????????System.out.println("stack?length:"+?oom.stackLength);
          ???????????????throw?e;
          ??????????}
          ?????}
          }

          運行結(jié)果:

          stack?length:18388Exceptionin?thread?"main"?java.lang.StackOverflowError
          ?????at?com.intelligentler.jvm.棧溢出.addStackLength(棧溢出.java:9)
          ?????at?com.intelligentler.jvm.棧溢出.addStackLength(棧溢出.java:9)
          ?????at?com.intelligentler.jvm.棧溢出.addStackLength(棧溢出.java:9)

          所以,如果在單線程的情況下,無論是棧幀太大還是虛擬機棧容量太小,當內(nèi)存無法再分配的時候,虛擬機拋出的是StackOverflowError異常。

          如果在多線程下,不斷地建立線程可能會產(chǎn)生OutOfMemoryError異常。


          0x03、對于方法區(qū)

          方法區(qū)中只會產(chǎn)生OutOfMemoryError異常。

          由于運行時常量池是方法區(qū)的一部分,我們可以通過String.intern()方法來構(gòu)建一個運行時常量池的OutOfMemoryError異常。

          String.intern()是一個Native方法,它的作用是:如果字符串常量池中已經(jīng)包含了一個等于該String對象的字符串,則返回這個String對象,否則,將此String對象包含的字符串添加到常量池中,并返回這個字符串的String對象的引用。如下面代碼:

          public?class?方法區(qū)溢出{

          ?????public?static?void?main(String[]?args){
          ??????????List?list?=newArrayList();
          ??????????int?i?=0;
          ??????????while(true){
          ???????????????list.add(String.valueOf(i++).intern());
          ??????????}
          ?????}
          }

          運行結(jié)果:

          Exceptionin?thread?"main"?java.lang.OutOfMemoryError:PermGen?space
          ????at?java.lang.String.intern(NativeMethod)

          PermGen space的全稱是Permanent Generation space,是指內(nèi)存的永久保存區(qū)域,也就是說運行時常量池屬于方法區(qū)(也就是虛擬機永久代)中的一部分。

          另外,方法區(qū)是存放Class的相關信息的,運行時如果有大量的類來填滿方法區(qū),就會產(chǎn)生OutOfMemoryError異常。




          往期精彩



          01?Sentinel如何進行流量監(jiān)控

          02?Nacos源碼編譯

          03?基于Apache Curator框架的ZooKeeper使用詳解

          04?spring boot項目整合xxl-job

          05?互聯(lián)網(wǎng)支付系統(tǒng)整體架構(gòu)詳解

          關注我

          每天進步一點點

          喜歡!在看?
          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产无遮挡啪视频 | 久久婷婷秘 精品日产538 | 欧美三级片中文字幕在线观看 | 二级毛片操逼 | 男女直接拍拍 |