文章目录

  • JVM 内存模型概述
    • 基于分代收集理论设计的垃圾收集器所管理的堆结构
    • 方法区的演变
  • 内存分配
    • 划分内存的方法
    • 划分内存时如何解决并发问题
    • 对象栈上分配
    • 基于分代收集理论的垃圾收集器管理下的内存分配规则
      • 对象优先分配在 Eden 区
      • 大对象直接进入老年代
      • 长期存活的对象将逐步进入老年代
      • 对象动态年龄判断机制
      • 老年代空间分配担保机制

JVM 内存模型概述

JVM 内存模型,也叫 JVM 运行时数据区。下面是 JVM 内存模型的图解:

可以看出,JVM 内存模型主要分为以下的几块:

  • 堆:线程共享,用于存放对象实例,是由垃圾收集器管理的区域,不同的垃圾收集器对于堆会有不同的布局实现
  • 方法区:线程共享,用于存储已被加载的所有类的类型信息静态变量字段信息方法信息字面量符号引用等数据
  • 程序计数器:线程私有,是当前线程所执行的字节码的行号指示器,是唯一一块不会发生 OOM 的区域
  • Native 栈:线程私有,也叫本地方法栈,当 JVM 执行 Native 方法时,存储一些必要的数据
  • JVM 栈:线程私有,也叫虚拟机栈,每个方法被执行时,JVM 都会创建一个栈帧用于存放方法的局部变量表操作数栈方法返回地址动态链接等数据

基于分代收集理论设计的垃圾收集器所管理的堆结构

堆是由垃圾收集器管理的内存区域,不同的垃圾收集器对于堆会有不同的布局实现,这里主要介绍基于分代收集理论设计的垃圾收集器所管理的堆结构。
基于分代收集理论,堆内存结构如下所示:

可以看出,堆内存结构主要分为:

  • Yound Gen:新生代,约占整个堆大小的 1/3

    • Eden 区:Eden 区,大约占新生代的 8/10
    • Survivor 0 区:S0 区,大约占新生代的 1/10
    • Survivor 1 区:S1 区,大约占新生代的 1/10
  • Old Gen:老年代,约占整个堆大小的 2/3

方法区的演变

方法区,在 JDK8 之前,是用永久代来实现的,在 JDK8 之后,是用元空间来实现的

内存分配

划分内存的方法

JVM 划分内存的方法有两种:

  • 指针碰撞:Bump the Pointer,当堆中的内存比较整齐,即用过的内存和空闲内存有一条清晰的分界线(分界线处有个指针作为分界点指示器)时,可以使用这种方法

    • 指针碰撞方法在分配内存时,仅仅需要将分界点指示器向空闲内存方向挪动一段距离,距离取决于所需内存大小
  • 空闲列表:Free List,当队中的内存不整齐,即用过的内存和空闲内存相互交错、没有清晰的分界线时,就不能使用指针碰撞的方式来分配内存了。JVM 会维护一个列表,记录队中每块内存的使用情况,在分配的时候从可用的内存中分配一块足够大的内存出去,并把这块内存标记为已使用

划分内存时如何解决并发问题

在实际的 JVM 运行过程中,很可能会出现多个线程同时申请内存的情况,此时如果不对划分内存操作进行并发控制操作,大概率会出现并发安全问题(多个内存分配请求被分配到了同一块内存空闲)。
针对上述情况,JVM 采取的并发控制手段有:

  • CAS:在划分内存时,采用 CAS + 自旋操作来保证同一块内存不会同时被分配给多个分配请求
  • TLABThread Local Allocation Buffer,即本地线程分配缓冲。核心思想就是把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在堆中预先分配一小块内存,这个线程后续的内存分配请求都会先在这块区域上进行,这一小块内存就称为本地线程分配缓冲。可以通过设置参数来开启或关闭此功能。

对象栈上分配

为了减少生命周期较短的对象在堆内分配的次数,JVM 会通过逃逸分析结合标量替换两个功能,把一些引用不会逃逸出方法之外的,可以使用若干个标量来替代本身的对象,将其内存分配在栈上进行。即:

  • 应用不会逃逸出方法之外,即这个对象是当前方法栈帧中创建出来的一个局部变量,其生命周期随着方法的结束而结束
  • 可以使用若干个标量来替代本身,即这个对象可以被分解成若干个不可再分的标量(如基本数据类型引用类型等)来替代

满足上述两个条件的对象,将可能会不被创建,而是直接由分解成若干个分配在栈上的标量替代,如:

class User {private int age;private String name;
}public void methodA() {User user = new User();user.age = 10;user.name = "Test";//将 user 数据插入到数据库中//由于 user 对象的引用没有传递到外部,且 user 对象本身可以被一个 int 类型和一个 reference 类型的标量替换掉//那么在开启了逃逸分析+标量替换时,将会用栈上分配的两个标量来替换掉,而不会在堆中创建这个对象......
}

注意,栈上分配必须依赖逃逸分析标量替换两个功能才能生效。

基于分代收集理论的垃圾收集器管理下的内存分配规则

基于分代收集理论的垃圾收集器,将堆根据存放对象的年龄不同划分成了不同的区域。在不同的区域内,会有不同的分配规则。

对象优先分配在 Eden 区

在大多数情况下,对象将会优先分配在 Eden 区。当 Eden 区没有足够的空间进行分配时,将会尝试进行一次 Young GC

大对象直接进入老年代

当对象的大小超过一定的阈值时,对象将会直接被分配到老年代

长期存活的对象将逐步进入老年代

Eden + S0 + S1 共同组成 Young Generation 的设计,称为 Appel 式垃圾收集机制。其主要的特点就是长期存活的对象将逐步进入老年代

  • 每个对象的对象头 Mark Word 中都会记录一个当前对象年龄的计数器
  • 在每一次经历 Young GC 后,如果对象依然存活,那么计数器将 +1
  • 在经历若干次(默认为 15 次)Young GC 还依然存活的对象,也即对象年龄大于晋升老年代年龄阈值(默认为 15)的对象,将会被移动到老年代中

对象动态年龄判断机制

在某一次 Young GC 时,如果此时 Survivor 空间中小于等于某年龄的所有对象大小的总和大于等于 Survivor 空间大小的一半,那么大于或等于该年龄的所有存活对象将直接进入到老年代中(尽管此时对象年龄可能尚未达到晋升老年代年龄阈值

老年代空间分配担保机制

Appel 式垃圾收集机制,核心思想使用的是复制算法。但是这个复制算法的备用空间(空闲的 Survivor 区)远小于主用空间(Eden 区 + 使用中的 Survivor 区),那么如果在某次 Young GC 时,存活下来的对象比备用空间大怎么办?
Appel 式垃圾收集机制给出的解决方案就是将这些所有存活下来的对象都移动到老年代中。这就是老年代空间分配担保机制的来源以及核心思想。
更细节的:

  • 在发生 Young GC 之前,JVM 必须检查当前老年代最大可用的连续空间是否大于新生代当前所有对象的总空间

    • 如果成立,那么可以安全地执行本次 Young GC
    • 如果不成立,那么 JVM 会查看当前是否开启了老年代空间分配担保机制(JDK 8 之后默认开启)
      • 如果未开启,那么将会执行一次 Full GC
      • 如果开启了,那么将会继续检查老年代最大可用的连续空间是否大于历次 Young GC 晋升到老年代的对象的平均总大小
        • 如果小于,那么将会执行一次 Full GC
        • 如果大于,那么将会有风险地执行本次 Young GC

注意,当有风险地执行 Young GC 时,如果本次 Young GC 存活下来的对象总大小大于老年代最大可用的连续空间(即出现了担保失败的情况),那么将会执行一次 Full GC

JVM 内存模型与内存分配方式相关推荐

  1. JVM内功心法-JVM内存模型之内存区域

    JVM内功心法-JVM内存模型之内存区域 程序计数器(线程隔离) 程序计数器(Program Counter Register):也叫PC寄存器,是一块较小的内存空间,它可以看做是当前线程所执行的字节 ...

  2. 内存管理-动态分区分配方式模拟

    内存管理 - 动态分区分配方式模拟 操作系统第二次课程作业 - 动态分区分配方式模拟 项目需求 假设初始态下,可用内存空间为640K,并有下列请求序列,请分别用首次适应算法和最佳适应算法进行内存块的分 ...

  3. 聊聊内存模型和内存序

    本文始发于公众号[高性能架构探索],本公众号致力于分享干货.硬货以及工作上的bug分析,欢迎关注.回复[pdf]免费获取计算机经典书籍 你好,我是雨乐! 最近群里聊到了Memory Order相关知识 ...

  4. java内存模型和内存结构_Java内存模型和优化

    java内存模型和内存结构 总览 许多多线程代码开发人员都熟悉这样的想法,即不同的线程可以对持有的值有不同的看法,这不是唯一的原因,即如果线程不安全,它可能不会看到更改. JIT本身可以发挥作用. 为 ...

  5. java 线程内存模型_JAVA内存模型与线程

    概述 由于计算机的运算速度和它的存储和通讯子系统的速度差距巨大,大部分时间都花在IO,网络和数据库上.为了压榨CPU的运算能力,需要并发.另外,优秀的并发程序对于提高服务器的TPS有重要的意义. 硬件 ...

  6. 内存模型 linux,内存模型 - STM32F4 编程手册学习_Linux编程_Linux公社-Linux系统门户网站...

    STM32F4编程手册学习2_内存模型 1. 内存映射 MCU将资源映射到一段固定的4GB可寻址内存上,如下图所示. 内存映射将内存分为几块区域,每一块区域都有一个定义的内存类型,一些区域还有一些附加 ...

  7. java内存分配空间大小,JVM内存模型及内存分配过程

    一.JVM内存模型 JVM主要管理两种类型内存:堆(Heap)和非堆(Permanent区域). 1.Heap是运行时数据区域,所有类实例和数组的内存均从此处分配.Heap区分两大块,一块是 Youn ...

  8. JVM内存模型及内存分配

    整个JVM内存可分为2个部分,总共5个区域: 线程独占区 1. 程序计数器 程序计数器是一块内存较小的区域,作用是指示当前线程执行的字节码行号.字节码解释器工作时是通过改变这个计数器的值选取下一条要执 ...

  9. jvm内存模型_JVM内存模型的相关概念

    1.前言 Android的虚拟机是根据移动设备的特点基于Java虚拟机(JVM)改进而来,虽然没有保留规范,但作为Java语言的使用者,了解一下JVM的规范还是有必要的. 2.JVM内存模型 JVM在 ...

最新文章

  1. 吴恩达机器学习笔记:(五)区别于微积分的正规方程求解最优解
  2. 台式计算机键盘配置及价格,最新台式电脑组装配置单及价格【图文】
  3. 无法复制winevt中的文件_u盘文件无法复制怎么解决 u盘文件无法复制解决方法【详细步骤】...
  4. Lucene 和 ElasticSearch 的前世今生
  5. Windows一键设置JAVA环境变量
  6. hdu 5154 Harry and Magical Computer
  7. 当java 8 lambda遇上uncheck exception
  8. java 设置excel宽度_javaexcel如何设置指定列宽
  9. 空洞卷积(膨胀卷积)的相关知识以及使用建议(HDC原则)
  10. 提搞网站访问速度可做的优化-------转载自熊哥的博客
  11. 出租屋宽带网络解决方案
  12. 使用rotate()来做一个3d贺卡
  13. 嵌入式软件开发笔试面试知识点总结-Linux部分
  14. 免挂码支付零度码支付intl码支付app监控码支付
  15. 转行学什么就业前景好?
  16. python选股票进阶_Python进阶量化交易专栏场外篇27-股票数据的除权和复权
  17. c++学习总结(4):继承与多态
  18. c语言海伦公式编程注释,c语言 关于海伦公式 求助
  19. ZigBee 3.0教程-步骤3:烧录和测试
  20. 服务器ip会被微信屏蔽吗,别再乱填自己的微信地址,否则分分钟会很麻烦!

热门文章

  1. 人类一败涂地做图教程_绘画步骤_人类一败涂地鼠绘人物步骤与技巧_3DM单机
  2. ruby三元操作符_在Ruby中使用操作符将元素添加到数组实例中
  3. HashMap 为什么在链表长度为 8 的时候转红黑树,为啥不能是 9 是 10?
  4. Spring Boot 最佳实践(三)模板引擎FreeMarker集成
  5. Oracle存储过程及函数的练习题
  6. vb的一些搞怪的操作
  7. 利用NCO 3.0 调用SAP中的函数
  8. 统计学(可汗学院视频62-81集笔记)
  9. docker privileged作用_docker总结
  10. python能制作游戏吗_没有Python不能做的游戏,这些游戏都可以做