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区域中,每个线程都会有的一个独占区域,目的是为了在多线程情况下,避免发生线程安全的问题,当该空间满了之后通过同步的方式增加缓冲区大小

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

  • 相关阅读:
    游标cursor
    SQL: EXISTS
    LeetCode Reverse Integer
    LeetCode Same Tree
    LeetCode Maximum Depth of Binary Tree
    LeetCode 3Sum Closest
    LeetCode Linked List Cycle
    LeetCode Best Time to Buy and Sell Stock II
    LeetCode Balanced Binary Tree
    LeetCode Validate Binary Search Tree
  • 原文地址:https://www.cnblogs.com/Mango-Tree/p/12804088.html
Copyright © 2011-2022 走看看