SafePoint前提介绍

在高度优化的现代JVM里,Safepoint有几种不同的用法。GC safepoint是最常见、大家听说得最多的,但还有deoptimization safepoint也很重要。

在HotSpot VM里,这两种Safepoint目前实现在一起,但其实概念上它们俩没有直接联系,需要的数据不一样。

无论是哪种SafePoint,最简洁的定义是“A point in program where the state of execution is known by the VM”。这里“state of execution”特意说得模糊,是因为不同种类的safepoint需要的数据不一样。

GC safepoint

GC Safepoint需要知道在那个程序位置上,调用栈、寄存器等一些重要的数据区域里什么地方包含了GC管理的指针; 如果要触发一次GC,那么JVM里的所有Java线程都必须到达GC safepoint。

Deoptimization safepoint

Deoptimization safepoint需要知道在那个程序位置上,原本抽象概念上的JVM的执行状态(所有局部变量、临时变量、锁,等等)到底分配到了什么地方,是在栈帧的具体某个操作数栈slot,还是在某个寄存器里。

如果要执行一次deoptimization,那么需要执行deoptimization的线程要在到达deoptimization safepoint之后才可以开始deoptimize。

不同JVM实现会选用不同的位置放置safepoint。

HotSpotVM的SafePoint

解释器里每条字节码的边界都可以是一个safepoint,因为HotSpot的解释器总是能很容易的找出完整的“state of execution”。

JIT编译的世界里,HotSpot会在所有方法的临返回之前,以及所有非counted loop的循环的回跳之前放置safepoint,(counted loop则没有放置safepoint)。

HotSpot的JIT编译器不但会生成机器码,还会额外在每个safepoint生成一些“调试符号信息”,以便VM能找到所需的“state of execution”。

SafePoint的存储信息

为GC SafePoint生成的符号信息是OopMap,指出栈上和寄存器里哪里有GC管理的指针;

为deoptimization SafePoint生成的符号信息是debugInfo,指出如果要把当前栈帧从compiled frame转换为interpreted frame的话,要从哪里把相应的局部变量、临时变量、锁等信息找出来。

选择在SafePoint的位置地点

  • 挂在safepoint的调试符号信息要占用空间,如果允许每条机器码都可以是safepoint的话,需要存储的数据量会很大(当然这有办法解决,例如用delta存储和用压缩)

  • safepoint会影响优化,特别是deoptimization safepoint,会迫使JVM保留一些只有解释器可能需要的、JIT编译器认定无用的变量的值。本来JIT编译器可能可以发现某些值不需要而消除它们对应的运算,如果在safepoint需要这些值的话那就只好保留了。这才是更重要的地方,所以要尽量少放置safepoint。

像HotSpotVM这样,在Safepoint会生成(polling代码)主动请求询问JVM是否要进入safepoint,polling也有开销所以要尽量减少。

Native代码的特殊性

当某个线程在执行native函数的时候。此时该线程在执行JVM管理之外的代码,不能对JVM的执行状态做任何修改,因而JVM要进入safepoint不需要关心它。

所以也可以把正在执行native函数的线程看作“已经进入了safepoint”,或者把这种情况叫做“在safe-region里”。

JVM外部要对JVM执行状态做修改必须要通过JNI。所有能修改JVM执行状态的JNI函数在入口处都有safepoint检查,一旦JVM已经发出通知说此时应该已经到达safepoint就会在这些检查的地方停下来把控制权交给JVM。

JRockit选择放置safepoint的地方在方法的入口以及循环末尾回跳之前,跟HotSpot略为不同。

UseCountedLoopSafepoints:

可以避免GC发生时,线程因长时间运行counted loop,进入不到safepoint,而引起GC的STW时间过长。

JVM参数-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1

在控制台输出以下信息:

vmop [threads: total initially_running wait_to_block]  [time: spin block sync cleanup vmop] page_trap_count  370337.312: GenCollectForAllocation     [  1070     2       3  ]   [ 8830   0 8831   1  24  ]

YGC所花费的时间非常短,主要时间花费在所有线程达到安全点并暂停。

JVM参数配置如下:

-server -Xms8192M -Xmx8192M -Xmn1500M -Xss256k -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:-UseBiasedLocking -XX:MonitorBound=16384 -XX:+UseSpinning -XX:PreBlockSpin=1 -XX:+UseParNewGC -XX:ParallelGCThreads=8 -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=55 -XX:CMSMaxAbortablePrecleanTime=5 -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/xmail/jvm_heap.dump -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1

最有可能导致问题的是代码里有Java代码

for (int i = 0; i < ...; i++) { } 或者类似的循环代码。

这种循环称为“counted loop”,就是有明确的循环计数器变量,而且该变量有明确的起始值、终止值、步进长度的循环。

它有可能被优化为循环末尾没有safepoint,于是如果这个循环的循环次数很多、循环体里又不调用别的方法或者是调用了方法但被内联进来了,就有可能会导致进入safepoint非常耗时。

可惜的是现在没什么特别方便的办法直接指出是什么地方有这种循环。有的话,一种解决办法是把单层循环拆成等价的双重嵌套循环,这样其中一层循环末尾的safepoint就可能会留下来,减少进入safepoint的等待时间。

如何判断内联方法

从代码角度如何判断方法被内联进来了,主要是方法被final修饰。 final是可以帮助JIT编译器做出内联的判断,但不是必要条件。

  • -XX:+PrintCompilation -XX:+PrintInlining来看内联状况

  • -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 ”输出的结果“[time: spin block sync cleanup vmop] ”中spin是指什么呢?

    • PrintSafepointStatics:打印出来的spin值指的是SafepointSynchronize在同步每个线程时做的自旋。

thread locking / biased locking的spin完全没关系,自然设置那些参数也不会影响safepoint的自旋(UseSpinning之类控制的是thread locking的自旋)。

SafePoint存在的目的?

为什么把这些位置设置为jvm的安全点呢,主要目的就是避免程序长时间无法进入safepoint,比如JVM在做GC之前要等所有的应用线程进入到安全点后VM线程才能分派GC任务 ,如果有线程一直没有进入到安全点,就会导致GC时JVM停顿时间延长。

比如,写了一个超大的循环导致线程一直没有进入到安全点,GC前停顿了8秒。

产生的日志信息基本上STW的原因都是RevokeBias或者BulkRevokeBias。这个是撤销偏向锁操作,虽然每次暂停的时间很短,但是特别频繁出现也会很耗时。

GC如何找到不可用的对象

编写代码的时候是可以知道对象不可用的,但对于程序来说,需要一定的方式来知晓,可用方法比如:编译分析,引用计数,和对象是否可达。

可达性分析

因而可达性分析,只需要找到直接可达的引用,直接可达的引用就是根引用,根引用的集合就是根的集合

  1. 一个对象只要能够通过mutator触达,那么它就是“活”着的。

  2. 如果Mutator栈的一个槽位包含了对象的引用,那么对象就是直接可触达。

  3. 从直接可达对象可触达的对象必定也是可达的,

muator线程分析
  • mutator的上下文就包含了直接可达的数据,所以要获取对象根集合就是要找到mutator上下文中的对象引用,

  • mutator的上下文指的就是它的栈、它的寄存器文件以及一些线程上特定的数据。

静态数据

全局数据本身也是直接可达的

可达性分析为了确保能正确的决定对象是否存活,GC需要获取mutator 上下文的(当前)一致性快照,然后枚举所有的根对象。

  • 一致性指的是:快照的抽取就像只在一个时间点发生,来避免丢失一些活着的对象。
如何获取 mutator上下文的一致性快照

一种简单的方式就是在跟引用的过程中暂停所有的线程。当mutator暂停了它的执行时,只有将所有引用信息保存在其上下文中,才能枚举根的集合,这意味着,mutator需要能够告诉JVM哪些栈的槽位有用,哪些寄存器持有引用。

如果GC能够准确的获取上述引用信息,它就称作精准根集合枚举。而无法获取就是不精准的。

如何获取精准的引用信息枚举

对于java来说,JIT知晓所有的栈帧信息和寄存器的内容,当JIT编译一个方法时,对于每条指令,它都可以去保存根引用信息,保存意味着额外的存储空间,如果要存储所有的指令就显得花销太大,另外在真实的运行过程中也只有少数指令才会成为暂停点,因此JIT只需要保存这些指令点的信息就够了。而真正有机会成为暂停点的地方就称作 safe-points,即能够安全的枚举根集合的暂停点

如何保证mutator会在safe-point暂停

当GC想要触发一次回收时,它会设置一个标志,mutator则周期性的去检查(poll)这个标志,如果检查到了,就会立马暂停,这里的检查点(poll points)也是安全点,由JIT负责把poll points放到合适的位置,那些地方适合设置检查GC事件的标记

polling point插入的主要原则是:
  • polling point应该足够多,防止GC等一个mutator的暂停太长,导致其他mutator都走在等GC释放空间,程序整个等待过长

  • polling point不能太频繁导致运行时存储开销过大

  • polling本身也是有开销的,不能过多

  • 权衡下来只在必须和必要的地方加

  • 分配地址的时候强制添加,因为分配空间很有肯能导致回收,所以这里是一个安全点

  • 长时间的执行一般意味着循环和方法调用,所以方法调用和循环返回最好加上

但是有时候并不是长时间的执行,而是长时间的空闲,比如 sleep、block,线程在执行其他的native函数,这些时候JVM无法掌控执行能力,也就无法响应GC事件。

SafePoint无法解决sleep/block 带来的问题,当这段时间内JVM要发起GC时,就不管没到安全点但是在安全区域的线程。在线程要离开安全区域时,要检查系统是否已经完成了GC,故我们又定义了一个安全区域的概念.

SafeRegion的简介

safe-region是指代码快中没有用到会变异的部分,这样的代码块中,任何一个点都可以安全的枚举根。

  • 当进入到safe-region中时,mutator会设置一个准备标记,在离开safe-region区域之前,会检查GC是否已经完成了回收,如果没有,那么就暂停执行,如果有,就可以直接离开safe-region区域,不需要暂停mutator。

  • 关于Java/JVM的safepoint / safe-region,代码的执行过程中,如果需要执行某些操作,比如GC,deoptimize,等等,必须知道当前程序所有线程运行到的地方,是否能够恰好满足我执行对应操作,而不会对应用程序本身造成损害,能够正确执行的地方就是safepoint/saferegion

参考文献

虚拟机研究系列-「GC本质底层机制」SafePoint的深入分析和底层原理探究指南的更多相关文章

  1. 🏆【Alibaba微服务技术系列】「Dubbo3.0技术专题」回顾Dubbo2.x的技术原理和功能实现及源码分析(温故而知新)

    RPC服务 什么叫RPC? RPC[Remote Procedure Call]是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范.它允许程序调用另一个地址空间(通常是共享网络的另 ...

  2. Vue3.0系列——「vue3.0学习手册」第一期

    一.项目搭建 vite是尤大大开发的一款意图取代webpack的工具.其实现原理是利用ES6的import发送请求加载文件的特性.拦截这些请求,做一些编译,省去webpack冗长的打包时间.并将其与R ...

  3. ☕【Java技术指南】「并发编程专题」CompletionService框架基本使用和原理探究(基础篇)

    前提概要 在开发过程中在使用多线程进行并行处理一些事情的时候,大部分场景在处理多线程并行执行任务的时候,可以通过List添加Future来获取执行结果,有时候我们是不需要获取任务的执行结果的,方便后面 ...

  4. tensorflow入门教程和底层机制简单解说——本质就是图计算,自动寻找依赖,想想spark机制就明白了

    简介 本章的目的是让你了解和运行 TensorFlow! 在开始之前, 让我们先看一段使用 Python API 撰写的 TensorFlow 示例代码, 让你对将要学习的内容有初步的印象. 这段很短 ...

  5. JVM系列文章(四):类载入机制

    作为一个程序猿,只知道怎么用是远远不够的. 起码,你须要知道为什么能够这么用.即我们所谓底层的东西. 那究竟什么是底层呢?我认为这不能一概而论.以我如今的知识水平而言:对于Web开发人员,TCP/IP ...

  6. jvm系列:Java GC 分析

    Java GC就是JVM记录仪,书画了JVM各个分区的表演. 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之 ...

  7. 面试官,不要再问我“Java GC垃圾回收机制”了

    Java GC垃圾回收几乎是面试必问的JVM问题之一,本篇文章带领大家了解Java GC的底层原理,图文并茂,突破学习及面试瓶颈. 楔子-JVM内存结构补充 在上篇<JVM之内存结构详解> ...

  8. JVM架构和GC垃圾回收机制

    深入理解系列之JDK8下JVM虚拟机(1)——JVM内存组成 https://blog.csdn.net/u011552404/article/details/80306316 JVM架构和GC垃圾回 ...

  9. java面试题之----JVM架构和GC垃圾回收机制详解

    JVM架构和GC垃圾回收机制详解 jvm,jre,jdk三者之间的关系 JRE (Java Run Environment):JRE包含了java底层的类库,该类库是由c/c++编写实现的 JDK ( ...

随机推荐

  1. etcd学习(8)-etcd中Lease的续期

    etcd中的Lease 前言 Lease Lease 整体架构 key 如何关联 Lease Lease的续期 过期 Lease 的删除 checkpoint 机制 总结 参考 etcd中的Lease ...

  2. 20210712考试-2021noip11

    这篇总结比我写的好多了建议直接去看 T1 简单的序列 考场:愣了一会,想到以最大值分治.每次枚举最大值两侧更小的区间,st表预处理前缀和和最大值,用桶统计答案. 注意分治时要去掉最大值. const ...

  3. 史上最详细的信号使用说明(已被收藏和N次)

    Unix环境高级编程(第三版) 第10章 信号 文章目录 1. 引言 2. 信号的概念 2.1 信号操作之忽略信号 2.2 信号操作之捕捉信号 2.3 信号操作之执行系统默认操作 2.4 常见的信号 ...

  4. openswan专栏序言

    openswan专栏序言 "一杯茶,一包烟,一个bug解一天!!!". ​ 2020年春季,正值新冠病毒在全球肆虐之际,美国的疫情已经相当的严峻,每天仍以3万速度狂奔.而国内的疫情 ...

  5. 数据结构(c++)(第二版) Dijkstra最短路径算法 教学示范代码出现重大问题!

    前言 去年在数据结构(c++)的Dijkstra教学算法案例中,发现了一个 bug 导致算法不能正常的运行,出错代码只是4行的for循环迭代代码. 看到那里就觉得有问题,但书中只给了关键代码的部分,其 ...

  6. 文件流转换(一般用于axios设置接收文件流设置时responseType: 'blob')

    文件流转换 一般用于axios设置接收文件流设置时responseType: 'blob'当接口报错时,前端因已设置responseType: 'blob'无法再接收json格式数据,会把json格式 ...

  7. 源码编译安装LAMP

    LAMP架构是目前成熟的企业网站应用模式之一,指的是协同工作的一整套系统和相关软件,能够提供动态Web站点服务及其应用开发环境.LAMP是一个缩写词,具体包括Linux操作系统.Apache网站服务器 ...

  8. C# 下载远程http文件到本地

    System.Windows.Forms.FolderBrowserDialog dialog = new System.Windows.Forms.FolderBrowserDialog();   ...

  9. 【敏捷0】敏捷项目管理-为什么从敏捷开始?为什么从PMI-ACP开始?

    作为敏捷项目管理的开篇文章,还是先来简单地说一说为什么先从敏捷开始,为什么是以 PMI-ACP 为参考.当然,这一系列的文章可能不可避免地会为 PMI-ACP 做一些广告,但是我想告诉大家的是,敏捷以 ...

  10. mysql更新数据时:当想mysql某插入有某字段设置了unique且和之前相同时,会报错,并停止运行

    这个在mysql5.7会报错: 如openid设为unique: 1062 - Duplicate entry 'oTfYq6PKne00IrcTqphmKqKnsahM' for key 'qx_w ...