JDK8内存模型

JDK8内存模型

作者 时间
雨中星辰 2023-01-15
image.png

方法区

方法区:是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、方法区中的静态变量是内存引用的地址,实际对象仍在对中创建。

例如:

源码如下:

public class MathUtil {
    public static String aaa = "sdf";
    public static final double PI=3.14;

    public int add() {
        int a = 100;
        int b = 200;
        int c = a + b;
        return c;
    }

    public static void main(String[] args) {
        MathUtil mathUtil = new MathUtil();
        mathUtil.add();
    }
}

class字节码反编译成可读的指令:javap -v MathUtil

Classfile /Users/star/opensource/la/target/classes/MathUtil.class
  Last modified 2023-1-5; size 722 bytes
  MD5 checksum 6ede24c108f9167fb00261f045de898e
  Compiled from "MathUtil.java"
public class MathUtil
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#36         // java/lang/Object."<init>":()V
   #2 = Class              #37            // MathUtil
   #3 = Methodref          #2.#36         // MathUtil."<init>":()V
   #4 = Methodref          #2.#38         // MathUtil.add:()I
   #5 = String             #39            // sdf
   #6 = Fieldref           #2.#40         // MathUtil.aaa:Ljava/lang/String;
   #7 = Class              #41            // java/lang/Object
   #8 = Utf8               aaa
   #9 = Utf8               Ljava/lang/String;
  #10 = Utf8               PI
  #11 = Utf8               D
  #12 = Utf8               ConstantValue
  #13 = Double             3.14d
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               this
  #21 = Utf8               LMathUtil;
  #22 = Utf8               add
  #23 = Utf8               ()I
  #24 = Utf8               a
  #25 = Utf8               I
  #26 = Utf8               b
  #27 = Utf8               c
  #28 = Utf8               main
  #29 = Utf8               ([Ljava/lang/String;)V
  #30 = Utf8               args
  #31 = Utf8               [Ljava/lang/String;
  #32 = Utf8               mathUtil
  #33 = Utf8               <clinit>
  #34 = Utf8               SourceFile
  #35 = Utf8               MathUtil.java
  #36 = NameAndType        #15:#16        // "<init>":()V
  #37 = Utf8               MathUtil
  #38 = NameAndType        #22:#23        // add:()I
  #39 = Utf8               sdf
  #40 = NameAndType        #8:#9          // aaa:Ljava/lang/String;
  #41 = Utf8               java/lang/Object
{
  public static java.lang.String aaa;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC

  public static final double PI;
    descriptor: D
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: double 3.14d

  public MathUtil();
    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 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LMathUtil;

  public int add();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        100
         2: istore_1
         3: sipush        200
         6: istore_2
         7: iload_1
         8: iload_2
         9: iadd
        10: istore_3
        11: iload_3
        12: ireturn
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 7
        line 13: 11
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      13     0  this   LMathUtil;
            3      10     1     a   I
            7       6     2     b   I
           11       2     3     c   I

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class MathUtil
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #4                  // Method add:()I
        12: pop
        13: return
      LineNumberTable:
        line 17: 0
        line 18: 8
        line 19: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0  args   [Ljava/lang/String;
            8       6     1 mathUtil   LMathUtil;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #5                  // String sdf
         2: putstatic     #6                  // Field aaa:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 6: 0
}
SourceFile: "MathUtil.java"

虚拟机栈

image.png

局部变量表:用于存放栈帧中的局部变量

操作数栈:将class转换为底层的操作数

例如:

public int add() {
    int a = 100;
    int b = 200;
    int c = a + b;
    return c;
}

转换后为:

0: bipush        100     # bipush 将一个8位带符号整数压入栈,这里为将100压入栈
2: istore_1                          # istore_1 将int类型值存入局部变量1,通过局部变量表,可以找到局部变量1的变量名为a,则就是将100存入a
3: sipush        200     # 将16位带符号整数压入栈,则立为将200压入栈
6: istore_2              # 将int类型值存入局部变量2,这里为将200存入到变量b中
7: iload_1               # 从局部变量1中装载int类型值,这里为取出100
8: iload_2                           # 从局部变量2中装载int类型值,这里为取出200
9: iadd                  # 执行int类型的加法,这里为100+200
10: istore_3             # 将int类型值存入局部变量3,这里为将300存入变量c中
11: iload_3              # 从局部变量3中装载int类型值,这里为从c中取出值300
12: ireturn              # 从方法中返回int类型的数据,这里为将300返回

动态链接:在jvm文件被编译到字节码文件中,所有的变量和方法引用都作为符号链接保存在class文件的常量池中,比如描述一个方法调用了另外的其他方法,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是将这些符号引用转换为调用方法的直接引用。

例:

Constant pool:
   #1 = Methodref          #7.#36         // java/lang/Object."<init>":()V
   #2 = Class              #37            // MathUtil
   #3 = Methodref          #2.#36         // MathUtil."<init>":()V
   #4 = Methodref          #2.#38         // MathUtil.add:()I
   
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V // 参数为字符串数组,返回值为void
    flags: ACC_PUBLIC, ACC_STATIC      // 方法是public static 
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2         // new 创建类实例,从常量池中找到#2为MathUtil
         3: dup                      // dup 复制栈顶部一个字长内容
         4: invokespecial #3         // 根据编译时类型来调用实例方法,这里为调用MathUtil."<init>":()V方法
         7: astore_1                 // astore_1 将引用类型或returnAddress类型值存入局部变量1
         8: aload_1                  // aload_1 从局部变量1中装载引用类型值
         9: invokevirtual #4         // invokevirtual 调用对象的实例方法,这里为调用MathUtil.add:()I方法
        12: pop                      // pop 弹出栈顶端一个字长的内容
        13: return                   // return 从方法中返回,返回值为void

方法出口:记录该方法被其他方法调用时的位置,以便在该方法执行完毕后,跳回被调用的方法中,继续执行后面。

例:

image.png

本地方法栈

每个线程栈在执行本地方法时,都会字啊栈中分配一块内存,用于执行本地方法。

程序计数器

记录程序运行到那个位置了,也就是行号。

因为程序在系统中,是以多线程方式运行到,程序计数器可以方便告诉CPU,线程在那里挂起到,再重新得到CPU后,继续执行。

image.png

堆分为年轻代和老年代,其中年轻代占1/3空间,老年代占2/3空间。

年轻代中有分为Eden区和两个survivor区,其中Eden区占年轻代8/10的空间,两个survivor分别占1/10的空间。

大多数情况下,对象被分配在年轻代的Eden区,大对象会被直接放到老年代,栈上分配的对象被放到栈上,当栈桢弹出后,对象及所在空间被清理。

大量的对象被分配在eden区,eden区满了后会触发minor gc,可能会有99%以上的对象成为垃圾被回收掉,剩余存活的对象会被挪到为空的那块survivor区,下一次eden区满了后又会触发minor gc,把eden区和survivor区垃圾对象回收,把剩余存活的对象一次性挪动到另外一块为空的survivor区,因为新生代的对象都是朝生夕死的,存活时间很短,所以JVM默认的8:1:1的比例是很合适的,让eden区尽量的大,survivor区够用即可。

JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC后调整堆的大小。

哪些情况对象会被放入老年代?

  1. 大对象直接进入老年代

    大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。JVM参数 -XX:PretenureSizeThreshold 可以设置大对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下有效。

    为了避免为大对象分配内存时的复制操作而降低效率。

  2. 长期存活的对象将进入老年代

    既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。
    如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor空间中,并将对象年龄设为1。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁,CMS收集器默认6岁,不同的垃圾收集器会略微有点不同),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

  3. 对象动态年龄判断

    当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总大小大于这块Survivor区域内存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了,例如Survivor区域里现在有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。对象动态年龄判断机制一般是在minor gc之后触发的

  4. 老年代空间分配担?;?/p>

    年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间

    如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象)就会看一个“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是否设置了

    如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。
    如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full gc,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够空间存放新的对象就会发生"OOM"
    当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发full gc,fullgc完之后如果还是没有空间放minor gc之后的存活对象,则也会发生“OOM

什么情况下会触发full gc?

  1. 在程序中调用了System.gc(),可能会触发full gc。
  2. 老年代空间不足
  3. 方法区满了,这种情况比较简单,在启动脚本中配置合适方法区参数即可:-XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M,方法区通常在启动后变动比较小了,如果应该方法区导致full gc,通常现场为启动比较慢,因为在启动过程中会频繁触发full gc,导致程序卡顿。
  4. 当大对象进入老年代,大对象指需要大量连续内存空间的java对象,例如很长的数组,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况就会触发JVM进行Full GC。
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容