zoukankan      html  css  js  c++  java
  • JVM内存模型及内存分配

    JVM内存模型

    首先先让我们看看内存结构图

    然后我们来具体介绍下每一部分的内容:

    类加载器

    之前有一篇文章讲解了有关类加载的机制,在这里就不再赘述了,有需要的朋友请移步查看

    虚拟机栈

    虚拟机栈描述的是Java方法执行的动态内存模型,虚拟机栈中最重要的就是栈帧的概念

    栈帧

    每个方法的执行,都会创建一个栈帧,伴随着方法从创建到执行完成,栈帧中有局部变量表、操作数栈、动态链接,方法出口等

    局部变量表

    存放编译期可知的各种基本数据类型,引用类型,returnAddress类型(仅存在于字节码层面,returnAddress类型的值就是指向特定指令内存地址的指针)
    局部变量表的内存空间在编译期分配完成,在进入一个方法时,这个方法需要在栈帧中分配的内存大小是固定的,在方法运行期间不会改变局部变量表的大小,任何会改变大小的对象都会使用引用类型来指定

    操作数栈

    Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中的栈就是操作数栈,虚拟机把操作数栈作为他的工作区,大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈

    动态链接

    每一次运行期间,都会转化为字节引用,指向运行时常量池中该栈帧所属方法的引用

    方法返回地址

    方法执行结束后应该回到哪一行字节码继续执行,该地址的获取有两种方式,一是方法正常退出通过PC计数器获取,另一个则是方法执行发生异常后通过异常处理表获取

    本地方法栈

    和虚拟机栈类似,最大的不同就是本地方法用于本地方法的调用,即native修饰的方法,JVM允许Java直接调用本地方法(由C语言编写)

    程序计数器

    程序计数器是一块比较小的内存空间,他可以看作是当前线程所执行的字节码的行号指示器
    在任意时刻,一个线程总是在执行一个方法,这个方法称为当前方法,如果线程执行的是Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是native方法,则程序计数器的值为undefined

    Java堆

    Java堆解决的是数据存储的问题,几乎所有的对象实例都存放在Java堆中
    堆内存分为新生代和老年代(Tenured Gen),分配比例默认为1:2
    其中新生代又分为Eden和两个Survivor区(From和To),分配的默认比例为8:1:1

    方法区

    方法区是辅助Java堆的一块内存区域,用于存储虚拟机加载的类信息、常量、运行时常量池、即时编译器(JIT)编译后的代码等数据

    JVM中特殊的内存区域

    运行时常量池(隶属于方法区)

    存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区(共享内存区)的运行时常量池存放,其中的字面量(字符串常量)会被存放到一个字符串表中,该表为HashSet类型,具有无序不可重复的特点,所以相同的字面常量自然就是同一块内存地址,通过new的方式创建的同字面量字符串不会考虑常量池的对象,而是直接在堆中重新开辟一块内存空间,所以必然和常量池对象地址不同

    直接内存

    在NIO中会使用到直接内存用于提高IO性能,其他地方几乎是不用的,通常直接内存速度会优于java堆,读写频繁的场合可能会考虑使用
    直接内存不受制JVM堆内存的制约,但是依旧受限于物理内存的制约,如果内存满了也会抛出OutOfMemoryError异常
    不过可以调用String对象的intern()方法实现运行时加入常量池,从而实现System.out.println("str"==new String("str").intern())的输出为true

    JVM内存分配

    内存分配策略

    优先分配到Eden

    测试方法:在方法中直接分配一个不超过Eden总内存的大对象,通过打印GC信息来查看是否是在Eden区分配的内存

    大对象直接分配到老年代

    当新对象的大小超出了Eden的内存上限,将直接在老年代中给该对象分配内存

    空间分配担保,默认是开启的,可以通过-XX:-HandlePromotionFailure来关闭

    当Eden区的空间已不足以存放新建对象,而且Survivor中空间也不够时(必然不够),只能将原有新生代的内容直接存放到老年代中,从而给新对象腾出足够的空间

    动态对象年龄判断

    对于在From和To两个Survivor区流转的对象,当流转次数达到某个值时(默认为15),会被存入到老年代中

    逃逸分析和栈上分配

    栈上分配

    Java栈的方法执行完成后会自动释放空间,所以如果将方法中的局部变量直接在栈中进行分配,在方法执行完毕时,对象会跟随方法释放掉,不需要垃圾收集器的介入,可以提高程序效率

    逃逸分析

    对于一个方法中的变量,分析其作用域,只要能够确定该变量没有方法外的引用,那就说明该变量没有发生逃逸,也就可以使用栈上分配

    对象的创建流程

    先尝试栈上分配,满足则分配,不满足尝试TLAB(Thread Local Allocation Buffer)分配,满足则分配,不满足查看是否满足进入老年代,满足则在老年区进行分配,不满足则在Eden区进行分配

    TLAB,称为本地线程分配缓冲,位于Eden区域中,每个线程都会有的一个独占区域,目的是为了在多线程情况下,避免发生线程安全的问题,当该空间满了之后通过同步的方式增加缓冲区大小

    如果对你有帮助,点个赞,或者打个赏吧,嘿嘿
    整理不易,请尊重博主的劳动成果

  • 相关阅读:
    [JSOI2007][BZOJ1030] 文本生成器|AC自动机|动态规划
    [NOI2014][BZOJ3670] 动物园|KMP
    [HAOI2010][BZOJ2427] 软件安装|tarjan|树型dp
    [JSOI2008][BZOJ1017] 魔兽地图DotR|树型dp
    [JLOI2014][BZOJ3631] 松鼠的新家|树上倍增LCA|差分
    [SDOI2010][BZOJ1975] 魔法猪学院|A*|K短路
    [BZOJ1251] 序列终结者|Splay
    hdu 2141 Can you find it?
    hdu 3152 Obstacle Course
    hdu 2680 Choose the best route
  • 原文地址:https://www.cnblogs.com/Mango-Tree/p/12804088.html
Copyright © 2011-2022 走看看