JVM之OopMap,安全点,安全区

这篇具有很好参考价值的文章主要介绍了JVM之OopMap,安全点,安全区。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


一、什么是OopMap

由于目前几乎所有虚拟机都是用可达性分析算法来判定对象是否存活,即通过选定固定的gc roots作为起始节点,像剥洋葱一样往下溜达,只要存在任意节点从gc roots到该节点不可达,那表示这个对象不被任何对象所引用,这个对象最终就要被当做垃圾回收掉。


问题来了,如何找到这些gc roots呢?
从源代码上看,对象引用不是在类中,就是在方法中,如此,通过扫描所有的对象就可以获取到这些gc roots。但是目前随便一个Java应用相当庞大(低情商叫臃肿),内存中的类,对象,常量数不胜数,每次gc都去扫描一遍,这个性能损耗是不可能接受的,而这只是其一。其二为了保证内存的一致性,获取这些gc roots过程中,必须暂停用户线程。用户线程在这个阶段内不能工作,所以能做的就是尽量要缩短用户线程的停顿时间,也就是要尽快完成gc roots的扫描。惯用套路,既然后期处理遍历耗时,那就前期维护一套数据结构呗,所谓的空间换时间。而OopMap就是这套数据结构,通过OopMap提前记录类、方法的引用信息,查找gc roots时,直接通过OopMap去获取,而不必扫描整个对象。

  1. 首先对于一个类在加载进内存的时候,空间是“确定的”,即结构是确定的,比如定义了哪些变量,哪些引用,而且一定是连续内存,所以对象中的引用是可以通过地址偏移量计算得到的,所以把这个偏移量放在OopMap中,需要的时候OopMap去找就可以了
  2. 一个线程在运行过程中,有自己的栈空间,每一个方法都是一个栈帧,即时编译过程中会在特定位置记录下栈中和寄存器里哪些位置是引用。
public void test()
{
	Person person = new Person();
	person.setPhone(new Phone());
	Dog dog = new Dog();
	//...
}

一个栈帧可以有多个OopMap,这里假设一个栈帧只有一个,且记录状态是在方法返回之前,如下:
oopmap,jvm笔记,jvm,java,开发语言

二、安全点(safe point)

虽然OopMap避免了大量扫描内存的消耗,但是内存中对象繁多,对象之间的引用关系也时刻在发生变化,如果每条指令都去记录OopMap,将会消耗大量内存和cpu资源,垃圾回收反而变成了系统的负担,为了解决这个问题,引入了安全点(safe point)的概念。即只在指令流的特定位置记录OopMap,垃圾回收行为发生后,线程如果没有到达安全点,将继续执行,直到到达最近的一个安全点才停下来,等待垃圾回收器完成gc roots的选取。

就像公交车一样,每个乘客到达的地点是不同的,但公交车不会为每一个人去停车,必须等到提前设定的站台才会停下,这个时候乘客才可以下车。

当线程到达安全点后,有两种方式中断线程:

  1. 抢占式中断,在发生垃圾回收时,系统将所有线程中断,如果发现有线程还没有到达安全点,则恢复该线程的执行,等待一会儿继续中断,直到到达安全点上,目前几乎没有虚拟机采用这种方案去停止用户线程
  2. 主动式中断,在特定位置设置一个中断标志位,所有线程在执行过程中不断去轮询这个标志位,如果发现该标志位被置位,就在距离自己最近的安全点主动挂起自己,等待垃圾回收器工作

轮询标志的地方和安全点是重合的,还需要加上所有创建对象和其他需要在对上分配空间的地方,这是为了检查是否要发生gc,避免没有足够的内存分配对象

适合插入安全点的地方:

  • 方法(栈帧)结束前,但并不意味着一个方法只能有一个安全点
  • 非计数循环末尾,避免循环体执行时间太长,导致长时间无法到达安全点
  • 每条Java编译后的字节码边界

根据以上的概括,我们能总结出一个点,安全点既不能太多,也不能太少,如果安全点过多,会对虚拟机资源产生更多的挤压,如果安全点太少,则会导致垃圾回收器等待时间过长,因此,需要在这两者之间取其平衡。

三、安全区(safe region)

安全区域可以理解是对安全点的存在问题的补充,上边说到线程会执行到附近的安全点停下来等待垃圾回收器介入处理,但如果线程没有执行呢,换句话就是说没有获得cpu执行权,比如某一个线程正在sleep或者等待磁盘输入,那么这个线程是不会走到安全点挂起自己的。
这个时候就要引入安全区概念了,顾名思义,安全区就是一段指令域,在这个域中的指令不会对当前内存中的引用造成修改,当线程进入该区域后,会主动将自己的状态标记为“进入安全区”,这个时候如果发生gc,垃圾回收器发现该线程处于安全区域内,认为该线程不会对内存安全造成影响,便会跳过该线程,不会等待该线程到达安全点。
而线程在到达安全区边界时,同样也会检查当前gc是否在工作,如果gc正在工作,这个时候线程便会主动停下来,等待gc动作完成后再继续执行。

四、卡表(card table)

为了解决在垃圾回收算法中,无论哪种算法,都需要先对对象进行标记,然后再进行回收操作。标记过程中,存在跨代引用问题,为了完整的标记对象引用链,将不得不对跨代内存中的对象进行遍历,尤其是老年代对象,对象存活率相对高,遍历的性价比极低。于是就引入了记忆集的概念,即将老年代内存划分为若干个小块,同时在新生代特定位置维护一块数据区域用来标记老年代中的哪个小块内存中存在跨代引用,当发生gc时,只需要检查这块数据区域中哪些老年代内存块中存在跨代引用,然后再对这一小块内存进行遍历。
这个过程相当于将老年代整个内存的搜索粒度降低了,从搜索整个老年代到“只搜索其中的某几小块”。
此外,老年代中内存小块具体设置多大,也需要综合考虑,如果设置太小,虽然粒度更低,精度更高,遍历单个块效率会提高,但分块会变多,同时也需要更多新生代空间去维护这个分块信息;如果划分的太大,则会降低老年代搜索效率。
以下是记忆集可供选择的记录精度:

  • 字长精度:每个记录精确到机器字长,64位机下就是8字节空间,该区域存在跨代指针
  • 对象精度:每个记录精确到一个对象,该区域存在跨代指针
  • 卡精度:每个记录精确到一块内存区域,该区域存在跨代指针

而card table可以理解为是Java虚拟机对记忆集的一种实现,hotspot用一个字节数组完成了卡表的记录,字节数组的每一个元素都标识老年代内存区域中的一段特定长度的区域,这个区域称为卡页。

CARD_TABLE[(this address >> 9)] = 0;
//根据以上的代码可以看出一个卡页的大小是512byte,即2^9 。
//将内存块地址右移9位,即除以512,得到的余数正好对应该内存区域对应的卡表标号

oopmap,jvm笔记,jvm,java,开发语言
只要卡页中存在跨引用对象,则将对应的卡表标识位置位为1,称之为变脏,发生gc时,只需要检查卡表变脏的元素,就能很快定位到哪一块内存中存在跨代指针。

五、写屏障(write barier)

通过记忆集减少了跨代指针扫描的问题,那卡表示如何维护的呢?答案就是写屏障(write barier),
首先卡表变脏肯定发生在引用赋值那一刻,写屏障可以理解成在指定的操作前后设置一组“保护措施”,可以理解成Spring框架中的AOP,在引用赋值时会产生一个环形通知,供程序执行额外的动作。在赋值前的部分叫做写前屏障,在赋值后的部分叫做写后屏障。除了G1收集器,其他的收集器都只用到了写后屏障。
应用写屏障后,虚拟机就会为所有的赋值操作生成相应的指令,这意味着所有的引用赋值都会增加更新卡表的操作,对产生一定的开销浪费,但是相比扫描整个老年代代价要小得多。

一个伪共享问题:
众所周知,cpu内部存在高速缓存,缓存中的存储单位是以缓存行为单位的,也就是说一个缓存行可以存储多个数据单位,而这些数据可能来自于不同线程,都知道不同线程的变量是相互隔离的,并不会互相影响。但是在并发场景下,缓存中的数据行尽管是来自不同线程的数据,但是由于存储单位是一个缓存行,此时线程不得不进行同步、或者通过cas算法以达到“有序执行”,从而降低了性能。本来数据实际上是相互独立、相互隔离的,但是在这里线程却认为访问的是共享空间,故名“伪共享”。

同样应用写屏障面临这个问题,如果卡表元素被缓存在一个缓存行中,多个线程对该卡表的更新操作就会出现“伪共享”,为了避免该问题,当内存对象引用发生变化需要更新卡表时,先判断一下该对象所对应的卡表是否已经变脏,如果没有变脏,再进行更新,避免多次重复标记导致的线程同步。
JDK7之后增加了新参数 -XX:+UseCondCardMark用来决定是否开启卡表更新的条件判断

总结

看过的东西总是容易忘记… ,就重新对垃圾回收算法其中的一些理解性概念进行了简单的记录和总结,同时也为后续的文章笔记做些一些铺垫,能力有限,如有问题,还望看官们指正~文章来源地址https://www.toymoban.com/news/detail-763806.html

到了这里,关于JVM之OopMap,安全点,安全区的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • uniapp系列-改变底部安全区-顶部的手机信号、时间、电池栏颜色样式

    下图是底部安全区原始状态,感觉和整个页面格格不入 修改代码配置safearea manifest.json(下面代码仅支持ios) manifest.json(下面代码支持android) 底部区域颜色已配置成功(下图仅供参考,随便选的颜色,有点丑哈哈) 配置顶部导航栏颜色 方案一:仅适用于原生导航配置,非

    2023年04月22日
    浏览(58)
  • Unity解决:Unity SpriteRenderer屏幕自适应的多种模式【动态调整大小 以遮盖Ipad所谓的安全区问题】

    上代码: 如何使用: 1.把脚本挂在Camera上 2.把需要进行屏幕适配的SpriteRender对象放在Member队列中 3.选择更新类型EUpdateType、选择适配类型EFillModel即可

    2024年02月20日
    浏览(36)
  • Java安全——JVM类加载器(1),啃下这些Framework技术笔记

    先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7 深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前! 因此收集整理了一份《2024年最新网络安全全套学习资料》

    2024年04月29日
    浏览(36)
  • JVM(Java虚拟机)-史上最全、最详细JVM笔记

    一、JVM概述 1、1为什么要学习JVM 1、2虚拟机 1、3JVM作用 1、4JVM整体组成部分  二、JVM结构--类加载器 2、1类加载子系统 2、2类加载过程 2、2、1加载 2、2、2链接 2、2、3初始化 2、3类加载器分类 2.3.1 引导类加载器(启动类加载器 BootStrap ClassLoader) 2.3.2 扩展类加载器(Extension ClassL

    2024年02月03日
    浏览(68)
  • JVM安全退出(如何优雅的关闭java服务)

    上线!重启!你还在为丢失任务而烦恼么?看这里看这里,从此不再丢任务,JVM可以安全退出的 在交易流程中,为了提升服务的性能,我们做了一些异步化的优化,比如更新用户最近使用的收货地址、提单完成后通过MQ去发送各种通知类消息、清理用户的购物车等等这些操作,

    2024年03月22日
    浏览(46)
  • 重温《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)》 –– 学习笔记(一)

    第1章:走近Java 1.1 Java的技术体系 SUN 官方所定义的 Java 技术体系包括:Java程序设计语言、Java虚拟机、Class文件格式、Java API类库、第三方(商业机构和开源社区)Java类库。 其中,「Java程序设计语言」、「Java虚拟机」、「Java API类」这三个被称为 JDK(Java Deployment Kit),即

    2024年01月23日
    浏览(53)
  • 学习笔记-JVM-工具包(JVM分析工具)

    常用工具 JDK工具 ① jps: JVM Process status tool:JVM进程状态工具,查看进程基本信息 ② jstat: JVM statistics monitoring tool : JVM统计监控工具,查看堆,GC详细信息 ③ jinfo:Java Configuration Info :查看配置参数信息,支持部分参数运行时修改 ④ jmap:Java Memory Map :分析堆内存工具,du

    2024年02月13日
    浏览(75)
  • JVM知识——安全点

            安全点是JVM要准备进行垃圾回收时,选定一些特定的位置,当应用程序线程执行到这些位置时,安全的先暂停下来,以便JVM执行GC线程进行垃圾回收。         JVM在字节码指令中会选取一些指令作为安全点,安全点的选取一般是循环结束、方法调用前后、异常

    2024年02月22日
    浏览(32)
  • jvm学习笔记

    从面试开始: 请谈谈你对JVM 的理解?java8 的虚拟机有什么更新? 什么是OOM ?什么是StackOverflowError?有哪些方法分析? JVM 的常用参数调优你知道哪些? 内存快照抓取和MAT分析DUMP文件知道吗? 谈谈JVM中,对类加载器你的认识? ​位置: JVM 是运行在操作系统之上的,它与硬

    2024年02月11日
    浏览(35)
  • 学习笔记-JVM

    JVM是运行在操作系统上的虚拟机,存在于JRE当中 HotSpot Sun公司 用的基本都是这个 JRockit BEA J9VM IBM JNI的作用 拓展java的使用,融合不同的编程语言为java所用 最初是C/C++ 因为最初java诞生的时候,市面上全是C/C++,java要想立足,必须有能调用C/C++的方法 于是在内存中设置了本地方法栈

    2024年02月06日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包