国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
JVM內(nèi)存模型有這篇文章就夠了

文章目錄

一、你了解JVM內(nèi)存模型嗎

在這之前需要知道

內(nèi)存尋址過(guò)程


地址空間劃分

  • 內(nèi)核空間是用于連接硬件,調(diào)度程序聯(lián)網(wǎng)等服務(wù)
  • 用戶空間,才是java運(yùn)行的系統(tǒng)空間

我們知道JVM是內(nèi)存中的虛擬機(jī),主要使用內(nèi)存進(jìn)行存儲(chǔ),所有類、類型、方法,都是在內(nèi)存中,這決定著我們的程序運(yùn)行是否健壯、高效。

JVM內(nèi)存模型圖——JDK1.8

  • 線程私有:程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧
  • 線程共享:MetaSpace、Java堆
    下面我們會(huì)對(duì)圖中五個(gè)部分進(jìn)行詳細(xì)說(shuō)明

1.1、程序計(jì)數(shù)器

  • 當(dāng)前線程所執(zhí)行的字節(jié)碼行號(hào)指示器(邏輯)
  • 通過(guò)改變計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令
  • JVM的多線程是通過(guò)線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來(lái)實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器只會(huì)執(zhí)行一條線程中的指令,為了線程切換后能夠恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程計(jì)數(shù)器不會(huì)互相影響。所以,程序計(jì)數(shù)器和線程是一對(duì)一的關(guān)系即(線程私有
  • 對(duì)Java方法計(jì)數(shù),如果是Native方法則計(jì)數(shù)器值為Undefined,Native方法是由非Java代碼實(shí)現(xiàn)的外部接口
  • 程序計(jì)數(shù)器是為了防止內(nèi)存泄漏
    在后邊的舉例中我們可以看到程序計(jì)數(shù)器的作用。

1.2、Java虛擬機(jī)棧(Stack)

  • Java方法執(zhí)行的內(nèi)存模型
  • 生命周期和線程是相同的,每個(gè)線程都會(huì)有一個(gè)虛擬機(jī)棧,棧的大小在編譯期就已經(jīng)確定了
  • 棧的變量隨著變量作用域的結(jié)束而釋放,不需要jvm垃圾回收機(jī)制回收。
  • 包含多個(gè)棧幀
    • 棧幀包含
      • 局部變量表
        • 包含方法執(zhí)行過(guò)程中的所有變量(所有類型)
      • 操作數(shù)棧
        • 入棧、出棧、復(fù)制、交換、產(chǎn)生消費(fèi)變量
      • 動(dòng)態(tài)連接
      • 返回地址

在Java虛擬機(jī)棧中,一個(gè)棧幀對(duì)應(yīng)一個(gè)方法,,方法執(zhí)行時(shí)會(huì)在虛擬機(jī)棧中創(chuàng)建一個(gè)棧幀,而且當(dāng)前虛擬機(jī)棧只能有一個(gè)活躍的棧幀,并且處于棧頂,當(dāng)前方法結(jié)束后,可能會(huì)將返回值返回給調(diào)用它的方法,而自己將會(huì)被彈出棧(即銷毀),下一個(gè)棧頂將會(huì)被執(zhí)行。

舉例說(shuō)明:

ByteCodeSample.java

package com.mtli.jvm.model;/** * @Description:測(cè)試JVM內(nèi)存模型 * @Author: Mt.Li * @Create: 2020-04-26 17:47 */public class ByteCodeSample { public static int add(int a , int b) { int c= 0; c = a b; return c; }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

對(duì)其進(jìn)行編譯生成.class文件

javac com/mtli/jvm/model/ByteCodeSample.java
  • 1

然后用javap -verbose 進(jìn)行反編譯

javap -verbose com/mtli/jvm/model/ByteCodeSample.class
  • 1

生成如下:

Classfile /E:/JavaTest/javabasic/java_basic/src/com/mtli/jvm/model/ByteCodeSample.class  Last modified 2020-4-26; size 289 bytes  MD5 checksum 2421660bb241239f1a67171bb771521f  Compiled from 'ByteCodeSample.java'public class com.mtli.jvm.model.ByteCodeSample  minor version: 0  major version: 52  flags: ACC_PUBLIC, ACC_SUPER// 描述類信息Constant pool:   #1 = Methodref          #3.#12         // java/lang/Object.'<init>':()V   #2 = Class              #13            // com/mtli/jvm/model/ByteCodeSample   #3 = Class              #14            // java/lang/Object   #4 = Utf8               <init>   #5 = Utf8               ()V   #6 = Utf8               Code   #7 = Utf8               LineNumberTable   #8 = Utf8               add   #9 = Utf8               (II)I  #10 = Utf8               SourceFile  #11 = Utf8               ByteCodeSample.java  #12 = NameAndType        #4:#5          // '<init>':()V  #13 = Utf8               com/mtli/jvm/model/ByteCodeSample  #14 = Utf8               java/lang/Object // 以上是常量池(線程共享){  public com.mtli.jvm.model.ByteCodeSample();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object.'<init>':()V         4: return      LineNumberTable:        line 8: 0// 以上是初始化過(guò)程  public static int add(int, int);    descriptor: (II)I  // 接收兩個(gè)int類型變量    flags: ACC_PUBLIC, ACC_STATIC // 描述方法權(quán)限和類型    Code:      stack=2, locals=3, args_size=2 // 操作數(shù)棧深度 、 容量  、參數(shù)數(shù)量         0: iconst_0         1: istore_2         2: iload_0         3: iload_1         4: iadd         5: istore_2         6: iload_2         7: ireturn      LineNumberTable:        line 10: 0 // 這里的第0行對(duì)應(yīng)我們代碼中的第10行        line 12: 2        line 13: 6}SourceFile: 'ByteCodeSample.java'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

執(zhí)行add(1,2)

以下是程序在JVM虛擬機(jī)棧中的執(zhí)行過(guò)程


圖不是很清楚,我來(lái)說(shuō)一下過(guò)程,最下邊的是程序計(jì)數(shù)器(前邊提到的),最上邊是操作指令,中間是局部變量表和操作數(shù)棧(位置從0開始)

  • 最開始,我們int c = 0,所以操作數(shù)棧頂初始值為0,局部變量表存儲(chǔ)變量值。
  • istore_2 就是出棧的意思,將0放入變量表2的位置
  • iload_0 就是入棧,將1復(fù)制并壓入操作數(shù)棧
  • 然后將位置在1的值“2”壓入棧
  • 在棧中執(zhí)行add方法,得到“3”
  • 將棧頂“3”取出到變量表的2位置
  • 再次將“3”壓入棧,準(zhǔn)備return
  • 方法返回值

執(zhí)行完之后,當(dāng)前線程虛擬機(jī)棧的棧幀會(huì)彈出,對(duì)應(yīng)的其他方法與當(dāng)前棧幀的連接釋放、引用釋放,它的下一個(gè)棧幀成為棧頂。

1.1.1、java.lang.StackOverflowError問(wèn)題

我們知道,一個(gè)棧幀對(duì)應(yīng)一個(gè)方法,存放棧幀的線程虛擬棧是有深度限制的,我們調(diào)用遞歸方法,每遞歸一次,就會(huì)創(chuàng)建一個(gè)新的棧幀壓入虛擬棧,當(dāng)超出限度后,就會(huì)報(bào)此錯(cuò)誤。

舉例說(shuō)明:

package com.mtli.jvm.model;/** * @Description:斐波那契 * F(0)=0,F(1)=1,當(dāng)n>=2的時(shí)候,F(n) = F(n-1) F(n-2), * F(2) = F(1) F(0) = 1,F(3) = F(2) F(1) = 1 1 = 2 * 0, 1, 1, 2, 3, 5, 8, 13, 21, 34... * @Author: Mt.Li * @Create: 2020-04-26 18:33 */public class Fibonacci { public static int fibonacci(int n) { if(n>=0){ if(n == 0) {return 0;} if(n == 1) {return 1;} return fibonacci(n-1) fibonacci(n-2); } return n; } public static void main(String[] args) { System.out.println(fibonacci(0)); System.out.println(fibonacci(1)); System.out.println(fibonacci(2)); System.out.println(fibonacci(3)); System.out.println(fibonacci(1000000)); // java.lang.StackOverflowError }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

結(jié)果:

解決方法是限制遞歸次數(shù),或者直接用循環(huán)解決。

還有就是,由JVM管理的虛擬機(jī)棧數(shù)量也是有限的,也就是線程數(shù)量也是有限定。

由于棧幀在方法返回后會(huì)自動(dòng)釋放,所有棧是不需要GC來(lái)回收的。

1.3、本地方法棧

  • 與虛擬機(jī)棧相似,主要作用于標(biāo)注了native的方法

1.4、元空間(MetaSpace)

元空間(MetaSpace)在jdk1.7之前是屬于永久代(PermGen)的,兩者的作用就是記錄class的信息,jdk1.7中,永久代被移入堆中解決了前面版本的永久代分配內(nèi)存不足時(shí)報(bào)出的OutOfMemoryError,jdk1.8之后元空間替代了永久代。

  • 元空間使用本地內(nèi)存,而永久代使用的是jvm的空間

1.4.1、MetaSpace相比PermGen的優(yōu)勢(shì)

  • 字符串常量池存在永久代中,容易出現(xiàn)性能問(wèn)題和內(nèi)存溢出(空間大小不如元空間)
  • 類和方法的信息大小難以確定,給永久代的大小指定帶來(lái)了困難
  • 永久代會(huì)為GC帶來(lái)不必要的復(fù)雜性
  • 方便HotSpot與其他JVM如Jrockit的集成

1.5、Java堆(Heap)

  • 對(duì)象實(shí)例的分配區(qū)域,實(shí)例在此處分配內(nèi)存
  • java堆可以處于不連續(xù)的物理空間中,只要邏輯上是連續(xù)的即可
  • 是GC管理的主要區(qū)域,按照GC分代回收的方法,java堆又分為新生代老生代(以后會(huì)出一篇GC相關(guān)的)

二、JVM三大性能調(diào)優(yōu)參數(shù) -Xms -Xmx -Xss的含義

  • -Xss:規(guī)定了每個(gè)線程虛擬機(jī)棧(堆棧)的大?。ㄒ话闱闆r下256k足夠)
  • -Xms:堆的初始值
  • -Xmx:堆能達(dá)到的最大值

三、Java內(nèi)存模型中堆和棧的區(qū)別——內(nèi)存分配策略

需要先了解

  • 靜態(tài)存儲(chǔ):編譯時(shí)確定每個(gè)數(shù)據(jù)目標(biāo)在運(yùn)行時(shí)的存儲(chǔ)空間需求,不允許有可變的程序存在,比如循環(huán)
  • 棧式存儲(chǔ):數(shù)據(jù)區(qū)需求在編譯時(shí)未知,運(yùn)行時(shí)模塊入口前確定。存儲(chǔ)局部變量,定義在方法中的都是局部變量,所以,方法先進(jìn)棧,創(chuàng)建棧幀等操作,方法一旦返回,即變量離開作用域,則棧幀釋放,變量也會(huì)釋放。(生命周期短)
  • 堆式存儲(chǔ):編譯時(shí)或運(yùn)行時(shí)模塊入口都無(wú)法確定,動(dòng)態(tài)分配。堆存儲(chǔ)的是數(shù)組和對(duì)象,存儲(chǔ)結(jié)構(gòu)復(fù)雜,所需空間更多,哪怕是實(shí)體中的一個(gè)屬性數(shù)據(jù)消失,這個(gè)實(shí)體也不會(huì)消失。(生命周期長(zhǎng))

區(qū)別

  • 管理方式:棧自動(dòng)釋放,堆需要GC
  • 空間大?。簵1榷研?/li>
  • 碎片相關(guān):棧產(chǎn)生的碎片遠(yuǎn)小于堆
  • 分配方式:棧支持靜態(tài)和動(dòng)態(tài)分配,而堆僅支持動(dòng)態(tài)分配
  • 效率:棧的效率比堆高,堆更靈活
  • 聯(lián)系:引用對(duì)象、數(shù)組時(shí),棧里面定義變量保存堆中目標(biāo)的首地址

四、元空間、堆、線程獨(dú)占部分間的聯(lián)系——內(nèi)存角度

我們來(lái)看下面這個(gè)例子:


以下是各個(gè)部分包含的內(nèi)容:

  • 元空間里面存著類的信息,比如方法、變量
  • java堆中存放對(duì)象實(shí)例
  • 線程獨(dú)占:用來(lái)保存變量的值即變量的引用、對(duì)象的地址引用,記錄行號(hào),用來(lái)記錄代碼的執(zhí)行

五、不同JDK版本之間的intern()方法的區(qū)別——JDK6 VS JDK6

說(shuō)到這里我們不得不提一下String.intern()方法在jdk版本變更中的不同

String s = new String('a');s.intern();
  • 1
  • 2

JDK6:當(dāng)調(diào)用intern方法時(shí),如果字符串常量池先前已創(chuàng)建出該字符串對(duì)象,則返回池中的該字符串的引用。否則,將此字符串對(duì)象添加到字符串常量池中,并且返回該字符串對(duì)象的引用。

JDK6 :當(dāng)調(diào)用intern方法時(shí),如果字符串常量池先前已創(chuàng)建出該字符串對(duì)象,則返回池中的該字符串的引用。否則,如果該字符串對(duì)象已經(jīng)存在于Java堆中,則將堆中,則將堆中對(duì)此對(duì)象的引用添加到字符串常量池中,并且返回該引用;如果堆中不存在,則在池中創(chuàng)建該字符串并返回其引用

我們看一個(gè)例子:

public class InternDifference { public static void main(String[] args) { String s = new String('a'); s.intern(); String s2 = 'a'; System.out.println(s == s2); String s3 = new String('a') new String('a'); s3.intern(); String s4 = 'aa'; System.out.println(s3 == s4); }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

jdk1.8下運(yùn)行結(jié)果為

falsetrue
  • 1
  • 2

分析:

  • s在創(chuàng)建的時(shí)候使用new方式創(chuàng)建,這里會(huì)在堆中就會(huì)有一個(gè)值為'a'的對(duì)象,intern()之后,intern()會(huì)將首次遇到的字符串放到常量池中,此時(shí)常量池中就有'a',發(fā)現(xiàn)常量池中有'a'。創(chuàng)建s2的時(shí)候,看到常量池中已經(jīng)有'a'了,于是,s2直接指向常量池'a'的地址,而s是指向堆中對(duì)象的地址,故返回false。
  • 我們?cè)賮?lái)看s3,s3則直接在堆中創(chuàng)建'aa',第一個(gè)'a',intern原本是要將第一個(gè)遇見的'a'放入常量池的,但是常量池中已經(jīng)存在'a'了,于是便不會(huì)管,new 的第二個(gè)'a'也不會(huì)管,但是到'aa'的時(shí)候,發(fā)現(xiàn)常量池中并沒(méi)有'aa',于是,直接將s3的引用放入常量池,而不是副本,這樣s4在創(chuàng)建的時(shí)候,發(fā)現(xiàn)常量池中有引用,便直接指向引用,而該引用是指向堆中的s3,故結(jié)果為true。

jdk1.6下結(jié)果

falsefalse
  • 1
  • 2

第一個(gè)false跟上邊的一樣,第二個(gè)false是因?yàn)閖dk1.6的intern()發(fā)現(xiàn)常量池中沒(méi)有'aa',則直接將此字符串對(duì)象添加到常量池中,兩個(gè)'aa'的地址是不一樣的,一個(gè)是堆中的一個(gè)是常量池中的,故結(jié)果也是false。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
JVM內(nèi)存模型總結(jié),有各版本JDK對(duì)比、有元空間OOM監(jiān)控案例、有Java版虛擬機(jī),綜合學(xué)習(xí)更容易!
java 9 10 11 12 13新特性,這里為你總結(jié)全了
JVM運(yùn)行原理詳解
終于搞懂了Java 8 的內(nèi)存結(jié)構(gòu),再也不糾結(jié)方法區(qū)和常量池了!!
編寫高質(zhì)量代碼改善Java程序的151個(gè)建議
關(guān)于Java基礎(chǔ)你不得不會(huì)的34個(gè)問(wèn)題
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服