文章收录地址:Java-Bang

专一于零碎架构、高可用、高性能、高并发类技术分享

在读博士的时候,我已经写过一个统计 Java 对象生命周期的动态分析,并且用它来跑了一些基准测试。

【腾讯云】云产品限时秒杀,爆款1核2G云服务器,首年99元

其中一些程序的后果,恰好验证了许多钻研人员的假如,即大部分的 Java 对象只存活一小段时间,而存活下来的小局部 Java 对象则会存活很长一段时间。

(pmd 中 Java 对象生命周期的直方图,红色的示意被逃逸剖析优化掉的对象)

之所以要提到这个假如,是因为它造就了 Java 虚拟机的分代回收思维。简略来说,就是将堆空间划分为两代,别离叫做新生代和老年代。新生代用来存储新建的对象。当对象存活工夫够长时,则将其挪动到老年代。

Java 虚拟机能够给不同代应用不同的回收算法。对于新生代,咱们猜想大部分的 Java 对象只存活一小段时间,那么便能够频繁地采纳耗时较短的垃圾回收算法,让大部分的垃圾都可能在新生代被回收掉。

对于老年代,咱们猜想大部分的垃圾曾经在新生代中被回收了,而在老年代中的对象有大概率会持续存活。当真正触发针对老年代的回收时,则代表这个假如出错了,或者堆的空间曾经耗尽了。

这时候,Java 虚拟机往往须要做一次全堆扫描,耗时也将不计成本。(当然,古代的垃圾回收器都在并发收集的路线上倒退,来防止这种全堆扫描的状况。)

明天这一篇咱们来关注一下针对新生代的 Minor GC。首先,咱们来看看 Java 虚拟机中的堆具体是怎么划分的。

Java 虚拟机的堆划分

后面提到,Java 虚拟机将堆划分为新生代和老年代。其中,新生代又被划分为 Eden 区,以及两个大小雷同的 Survivor 区。

默认状况下,Java 虚拟机采取的是一种动态分配的策略(对应 Java 虚拟机参数 -XX:+UsePSAdaptiveSurvivorSizePolicy),依据生成对象的速率,以及 Survivor 区的应用状况动静调整 Eden 区和 Survivor 区的比例。

当然,你也能够通过参数 -XX:SurvivorRatio 来固定这个比例。然而须要留神的是,其中一个 Survivor 区会始终为空,因而比例越低节约的堆空间将越高。

通常来说,当咱们调用 new 指令时,它会在 Eden 区中划出一块作为存储对象的内存。因为堆空间是线程共享的,因而间接在这里边划空间是须要进行同步的。

否则,将有可能呈现两个对象共用一段内存的事变。如果你还记得前两篇我用“停车位”打的比如的话,这里就相当于两个司机(线程)同时将车停入同一个停车位,因此产生剐蹭事变。

Java 虚拟机的解决办法是为每个司机事后申请多个停车位,并且只容许该司机停在本人的停车位上。那么当司机的停车位用完了该怎么办呢(假如这个司机代客泊车)?

答案是:再申请多个停车位便能够了。这项技术被称之为 TLAB(Thread Local Allocation Buffer,对应虚拟机参数 -XX:+UseTLAB,默认开启)。

具体来说,每个线程能够向 Java 虚拟机申请一段间断的内存,比方 2048 字节,作为线程公有的 TLAB。

这个操作须要加锁,线程须要保护两个指针(实际上可能更多,但重要也就两个),一个指向 TLAB 中空余内存的起始地位,一个则指向 TLAB 开端。

接下来的 new 指令,便能够间接通过指针加法(bump the pointer)来实现,即把指向空余内存地位的指针加上所申请的字节数。

1. 我猜想会有留言问为什么不把 bump the pointer 翻译成指针碰撞。这里先解释一下,在英语中咱们通常省略了

2. bump up the pointer 中的 up。在这个上下文中 bump 的含意应为“进步”。另外一个例子是当咱们公布软件的新版本

3. 时,也会说 bump the version number。

如果加法后空余内存指针的值仍小于或等于指向开端的指针,则代表调配胜利。否则,TLAB 曾经没有足够的空间来满足本次新建操作。这个时候,便须要以后线程从新申请新的 TLAB。

当 Eden 区的空间耗尽了怎么办?这个时候 Java 虚拟机便会触发一次 Minor GC,来收集新生代的垃圾。存活下来的对象,则会被送到 Survivor 区。

后面提到,新生代共有两个 Survivor 区,咱们别离用 from 和 to 来指代。其中 to 指向的 Survivior 区是空的。

当产生 Minor GC 时,Eden 区和 from 指向的 Survivor 区中的存活对象会被复制到 to 指向的 Survivor 区中,而后替换 from 和 to 指针,以保障下一次 Minor GC 时,to 指向的 Survivor 区还是空的。

Java 虚构机会记录 Survivor 区中的对象一共被来回复制了几次。如果一个对象被复制的次数为 15(对应虚拟机参数 -XX:+MaxTenuringThreshold),那么该对象将被降职(promote)至老年代。另外,如果单个 Survivor 区曾经被占用了 50%(对应虚拟机参数 -XX:TargetSurvivorRatio),那么较高复制次数的对象也会被降职至老年代。

总而言之,当产生 Minor GC 时,咱们利用了标记 – 复制算法,将 Survivor 区中的老存活对象降职到老年代,而后将剩下的存活对象和 Eden 区的存活对象复制到另一个 Survivor 区中。现实状况下,Eden 区中的对象根本都死亡了,那么须要复制的数据将非常少,因而采纳这种标记 – 复制算法的成果极好。

Minor GC 的另外一个益处是不必对整个堆进行垃圾回收。然而,它却有一个问题,那就是老年代的对象可能援用新生代的对象。也就是说,在标记存活对象的时候,咱们须要扫描老年代中的对象。如果该对象领有对新生代对象的援用,那么这个援用也会被作为 GC Roots。

这样一来,岂不是又做了一次全堆扫描呢?

卡表

HotSpot 给出的解决方案是一项叫做卡表(Card Table)的技术。该技术将整个堆划分为一个个大小为 512 字节的卡,并且保护一个卡表,用来存储每张卡的一个标识位。这个标识位代表对应的卡是否可能存有指向新生代对象的援用。如果可能存在,那么咱们就认为这张卡是脏的。

在进行 Minor GC 的时候,咱们便能够不必扫描整个老年代,而是在卡表中寻找脏卡,并将脏卡中的对象退出到 Minor GC 的 GC Roots 里。当实现所有脏卡的扫描之后,Java 虚拟机便会将所有脏卡的标识位清零。

因为 Minor GC 随同着存活对象的复制,而复制须要更新指向该对象的援用。因而,在更新援用的同时,咱们又会设置援用所在的卡的标识位。这个时候,咱们能够确保脏卡中必然蕴含指向新生代对象的援用。

在 Minor GC 之前,咱们并不能确保脏卡中蕴含指向新生代对象的援用。其起因和如何设置卡的标识位无关。

首先,如果想要保障每个可能有指向新生代对象援用的卡都被标记为脏卡,那么 Java 虚拟机须要截获每个援用型实例变量的写操作,并作出对应的写标识位操作。

这个操作在解释执行器中比拟容易实现。然而在即时编译器生成的机器码中,则须要插入额定的逻辑。这也就是所谓的写屏障(write barrier,留神不要和 volatile 字段的写屏障混同)。

写屏障须要尽可能地放弃简洁。这是因为咱们并不心愿在每条援用型实例变量的写指令后跟着一大串注入的指令。

因而,写屏障并不会判断更新后的援用是否指向新生代中的对象,而是宁肯错杀,不可放过,一律当成可能指向新生代对象的援用。

这么一来,写屏障便可精简为上面的伪代码 [1]。这里右移 9 位相当于除以 512,Java 虚拟机便是通过这种形式来从地址映射到卡表中的索引的。最终,这段代码会被编译成一条移位指令和一条存储指令。

CARD_TABLE [this address >> 9] = DIRTY;

尽管写屏障不可避免地带来一些开销,然而它可能加大 Minor GC 的吞吐率( 利用运行工夫 /(利用运行工夫 + 垃圾回收工夫) )。总的来说还是值得的。不过,在高并发环境下,写屏障又带来了虚共享(false sharing)问题 [2]。

在介绍对象内存布局中我曾提到虚共享问题,讲的是几个 volatile 字段呈现在同一缓存行里造成的虚共享。这里的虚共享则是卡表中不同卡的标识位之间的虚共享问题。

在 HotSpot 中,卡表是通过 byte 数组来实现的。对于一个 64 字节的缓存行来说,如果用它来加载局部卡表,那么它将对应 64 张卡,也就是 32KB 的内存。

如果同时有两个 Java 线程,在这 32KB 内存中进行援用更新操作,那么也将造成存储卡表的同一部分的缓存行的写回、有效化或者同步操作,因此间接影响程序性能。

为此,HotSpot 引入了一个新的参数 -XX:+UseCondCardMark,来尽量减少写卡表的操作。其伪代码如下所示:

1. if (CARD_TABLE [this address >> 9] != DIRTY)

2. CARD_TABLE [this address >> 9] = DIRTY;

总结与实际

明天我介绍了 Java 虚拟机中垃圾回收具体实现的一些通用常识。

Java 虚拟机将堆分为新生代和老年代,并且对不同代采纳不同的垃圾回收算法。其中,新生代分为 Eden 区和两个大小统一的 Survivor 区,并且其中一个 Survivor 区是空的。

在只针对新生代的 Minor GC 中,Eden 区和非空 Survivor 区的存活对象会被复制到空的 Survivor 区中,当 Survivor 区中的存活对象复制次数超过肯定数值时,它将被降职至老年代。

因为 Minor GC 只针对新生代进行垃圾回收,所以在枚举 GC Roots 的时候,它须要思考从老年代到新生代的援用。为了防止扫描整个老年代,Java 虚拟机引入了名为卡表的技术,大抵地标出可能存在老年代到新生代援用的内存区域。

java 卡表,关于jvm:聊一聊Java垃圾回收与卡表技术相关推荐

  1. Java进阶 JVM 内存与垃圾回收篇(一)

    JVM 1. 引言 1.1 什么是JVM? 定义 Java Vritual Machine - java 程序的运行环境(Java二进制字节码的运行环境) 好处 一次编译 ,到处运行 自动内存管理,垃 ...

  2. JAVA之JVM分代垃圾回收策略(一)

    一.为什么要分代 分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的.因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率. 在Java程序运行的过程中,会产生大量的对 ...

  3. Java GC系列(4):垃圾回收监视和分析

    转载自  Java GC系列(4):垃圾回收监视和分析 在这个Java GC系列教程中,让我们学习用于垃圾回收监视和分析的工具.然后,选用一种工具来监视一个Java示例程序的垃圾回收过程.如果你是一名 ...

  4. 深入理解JVM虚拟机之垃圾回收

    深入理解JVM虚拟机之垃圾回收 什么叫做垃圾? 没有引用指向得对象都称为垃圾,好比如我们放风筝,哪些断了线得风筝都称之为垃圾. JVM怎么查找这些垃圾 一般又两种算法,1.可达性分析.2.引用计数 引 ...

  5. jvm内存与垃圾回收重点总结

    文章目录 一.jvm简介 1.jvm的位置 2.JVM的整体结构 3.java代码执行流程 二.类加载子系统 1.类的加载过程 2.类加载器分类 ⭐3.双亲委派机制 三.运行时数据区及线程 四.程序计 ...

  6. JVM分代垃圾回收策略的基础概念

    JVM分代垃圾回收策略的基础概念 由于不同对象的生命周期不一样,因此在JVM的垃圾回收策略中有分代这一策略.本文介绍了分代策略的目标,如何分代,以及垃圾回收的触发因素. 文章总结了JVM垃圾回收策略为 ...

  7. 【JVM基础】垃圾回收算法详解(引用计数、标记、清除、压缩、复制)

    前言 笔记参考 Java 全栈知识体系.星羽恒.星空茶 文章目录 前言 垃圾回收概述 引用计数法 案例 优点 缺点 标记.清除.压缩 标记 清除 压缩 标记清除算法 优点 缺点 标记压缩算法 优点 缺 ...

  8. JVM内功心法-GC垃圾回收之GC垃圾回收过程

    JVM内功心法-GC垃圾回收之GC垃圾回收算法 GC 全称garbagecollection,垃圾回收.JAVA 为了屏蔽操作系统和平台之间的差异.选择的是采用 java 虚拟机来运行 java 应用 ...

  9. 移卡科技java_聊一聊Java垃圾回收与卡表技术

    专注于系统架构.高可用.高性能.高并发类技术分享 在读博士的时候,我曾经写过一个统计 Java 对象生命周期的动态分析,并且用它来跑了一些基准测试. 其中一些程序的结果,恰好验证了许多研究人员的假设, ...

最新文章

  1. DB2 9 利用开辟(733 测验)认证指南,第 1 部分: 数据库工具与编程步调(6)
  2. AI大师张钹领衔,清华AI研究院推出知识计算开放平台
  3. 李飞飞当选美国医学科学院院士!用AI照亮医疗黑暗空间
  4. UML学习笔记(三):运用面向对象思想
  5. django filter查询多选_django model filter查询
  6. comment.html手机文件,comment.html
  7. Java虚拟机(三)——类文件结构
  8. 1. Magento2 --- (1) theme ---create a theme
  9. 多版本并发控制MVCC和乐观锁OCC 是什么 区别
  10. Linux 实用工具vi
  11. java中拦截器和过滤器详解
  12. 经典枚举——百钱百鸡问题
  13. 人工智能数学基础8:两个重要极限及夹逼定理
  14. 2022山东国际青少年眼睛健康产业展览会,护眼健康展9月开展
  15. 扇形导航 html svg
  16. matlab成功安装libsvm后,运行程序仍报错“svmtrain has been removed”解决方法记录
  17. Windows一键删除指定文件或文件夹
  18. 动手深度学习——Pytorch 入门语法一
  19. mac按键难回弹(按下去软软的)
  20. 抖音神曲是如何“造”出来的?

热门文章

  1. 数字IC设计入门篇:APB总线协议学习心得
  2. 相关子查询非相关子查询概念
  3. 财务分析之产品营业利润分析
  4. The location of the currently running pnpm differs from the location where pnpm will be installed
  5. Mybatis标签之association关联查询对象属性
  6. 应用magisk ROOT系统
  7. 14款免费的GIF制作软件
  8. 2020年7月云主机性能评测报告
  9. android浏览器多标签页面,小猿多标签浏览器
  10. 基于Python-PyQt5实现的桌宠