[讨论] 请教 jvm 里面的safe point 的有效实现问题
nkhanxh
2012-01-24
先澄清一下我的猜测,不知道对否。
因为每次垃圾回收之后有可能进行内存移动,那么如果引用所包含的指针所引用的地址会有所变化,于是每次垃圾回收之后会重新emit一个memory map。 所以垃圾回收不是任何点都能进行的,所以要等待一个安全点,否则有些指针访问会变成非法的。 如果是解释执行的,那么比较简单,如果jvm要进行垃圾回收,加一个全局标记,解释器在解释完成上一条指令之后,查看这个标记,如果设置了则呆在原地,直到垃圾回收完成。 但是如果是二进制翻译的呢?如果某个线程正在执行一个耗时比较长的翻译后的机器代码呢?控制权是不在解释器手里的。这样岂非垃圾收集要等待一个不确定的时间才能开始?但是如果内存已经很紧张需要马上收集了呢? 由于我不知道该看hotspot哪一段代码,所以想偷个懒,请哪位指导一下如何实现比较高效呢? 另外,我猜测一下,是否jvm会保存一下所有被翻译的代码区域涉及到的引用中所包含的指针指向了哪个区域呢?我记得有什么memory card之类的概念。 这样在进行某区域的垃圾回收的时候,就不用把所有线程都停下来了,只要停下正在执行和那个区域相关的代码段的线程就可以了。当然,如果有线程要进入所敏感代码段,也需要停下,但这也涉及到高效的互斥问题。 一个我想的到的方法就是,检查目前执行的二进制代码段的控制权出口,如果到达出口前会可能访问到目前需要收集垃圾的区域,那么就停下他,不知道对不对?还有其他方法吗? |
|
IcyFenix
2012-01-29
引用 如果某个线程正在执行一个耗时比较长的翻译后的机器代码呢?控制权是不在解释器手里的。这样岂非垃圾收集要等待一个不确定的时间才能开始?但是如果内存已经很紧张需要马上收集了呢?
JIT生成的机器代码就预留了伏笔,生成了轮询safepoint的指令。 例如hotspot在x86中为轮询safepoint会生成一条类似于“test %eax,0x160100”的指令,JVM需要进入gc前,先把0x160100设置为不可读,那所有线程执行到检查0x160100的test指令后都会停顿下来。 轮询safepoint的动作是足够密集的,JIT在方法调用、循环跳转、异常跳转处都会生成轮询指令,所以不应当会等待过长的时间。一个例外是在线程处sleep或者blocked状态的时候,这时不会分配cpu时间,不过可以通过saferegion来解决,把线程sleep这些包裹在saferegion之内,线程进入saferegion的时候做个标识,gc开始的时候不需要检查处于saferegion的线程,在线程退出saferegion的时候再检查能否出去,如果gc已经完了就出去,没完就待着。 |
|
nkhanxh
2012-01-29
IcyFenix 写道 引用 如果某个线程正在执行一个耗时比较长的翻译后的机器代码呢?控制权是不在解释器手里的。这样岂非垃圾收集要等待一个不确定的时间才能开始?但是如果内存已经很紧张需要马上收集了呢?
JIT生成的机器代码就预留了伏笔,生成了轮询safepoint的指令。 例如hotspot在x86中为轮询safepoint会生成一条类似于“test %eax,0x160100”的指令,JVM需要进入gc前,先把0x160100设置为不可读,那所有线程执行到检查0x160100的test指令后都会停顿下来。 轮询safepoint的动作是足够密集的,JIT在方法调用、循环跳转、异常跳转处都会生成轮询指令,所以不应当会等待过长的时间。一个例外是在线程处sleep或者blocked状态的时候,这时不会分配cpu时间,不过可以通过saferegion来解决,把线程sleep这些包裹在saferegion之内,线程进入saferegion的时候做个标识,gc开始的时候不需要检查处于saferegion的线程,在线程退出saferegion的时候再检查能否出去,如果gc已经完了就出去,没完就待着。 哦。多谢!有点明白了。不过我还有更进一步的问题, 麻烦看一下下面。 不过这样来说,还是会导致比一般的程序慢一些是吧。是不是这也是导致java程序比c++程序慢一些的重要因素之一呢? 毕竟c++程序清晰地知道自己合适需要释放、分配内存。也许会有个10%左右的损失? 而且这种也算分支指令吧?也有预测失效问题,也会导致一点点损失?不过因为估计好的cpu大多数时候猜对这个分支,一般都不发生流水线失效? 最后,关于saferegion问题,会否有这种情况,眼下没内存了,但是还有人block住并且占住了关键的内存区,这时候垃圾收集器岂非干瞪眼不能干什么? 是否jvm里面会有对应的检查,如果这种情况的话,会有对应的策略?比如进行更深的分析,采取如下手段: 0.分析一下是否值得采取1,2手段。比如该段代码占用的指针指向的内存超过某阈值。 1.收集该段会引用的内存,如果值得则开始1,2。 2.将该段机器码重新编译成为引用垃圾收集后指针的位置等等。 感觉垃圾策略收集说起来很简单,但是只要涉及高效实现就会很麻烦。 |
|
IcyFenix
2012-01-29
nkhanxh 写道 不过这样来说,还是会导致比一般的程序慢一些是吧。是不是这也是导致java程序比c++程序慢一些的重要因素之一呢?
毕竟c++程序清晰地知道自己合适需要释放、分配内存。也许会有个10%左右的损失? 而且这种也算分支指令吧?也有预测失效问题,也会导致一点点损失?不过因为估计好的cpu大多数时候猜对这个分支,一般都不发生流水线失效? 我的观点是高级语言中,编译器设计水平相等的前提下,提升开发效率的语言特性(如自动内存管理)都很可能会导致执行效率的下降。这个HLLVM圈子前面几页中有过讨论。 nkhanxh 写道 最后,关于saferegion问题,会否有这种情况,眼下没内存了,但是还有人block住并且占住了关键的内存区,这时候垃圾收集器岂非干瞪眼不能干什么?
是否jvm里面会有对应的检查,如果这种情况的话,会有对应的策略?比如进行更深的分析,采取如下手段: 0.分析一下是否值得采取1,2手段。比如该段代码占用的指针指向的内存超过某阈值。 1.收集该段会引用的内存,如果值得则开始1,2。 2.将该段机器码重新编译成为引用垃圾收集后指针的位置等等。 感觉垃圾策略收集说起来很简单,但是只要涉及高效实现就会很麻烦。 GC Heap不是线程私有的资源,能否回收只取决于Heap中的对象是否还有到GC Root的可达路径,与线程是否处于blocked状态没有关联。 也许前面对saferegion的描述不清晰,导致你误解为saferegion是保存Heap中某块内存不回收的区域?更详细的介绍可参见这里。 |
|
nkhanxh
2012-01-30
哦,"http://icyfenix.iteye.com/blog/1166660".看明白了.
原来是这样解决的,就等于是在safe region里面每条指令都是gc安全的。 多谢! nkhanxh 写道 哦,是这样的,我倒没理解成safe region是引用了某资源,可能是我表达不清。
我是想说,要进行的垃圾收集,可能会移动safe region中某段代码中某寄存器所存指针指向的内存区。偏偏这个线程还在某个点上被睡着了。而不移动它(去老年代,或者进行老年代的compact)恰恰没法完成一段比较大的内存的回收,这样垃圾收集器就有烦恼了吧? 另外也许垃圾收集器里面也有折衷,遇到这种情况就不compact老年代,不进行移动,这样最多产生一些内存碎片,但是不久的将来这个线程醒过来的时候就会得到解决。当然,又引入一个话题,如果这个线程是编写错误的,等待一个不可能等到的条件,又会涉及一些处理? 另外,我想应该是可以通过修改该段代码,使得该指针的值指向正确的位置吧,只不过这样代价稍微大一些,做之前需要判断一下是否值得,。 引用 GC Heap不是线程私有的资源,能否回收只取决于Heap中的对象是否还有到GC Root的可达路径,与线程是否处于blocked状态没有关联。 也许前面对saferegion的描述不清晰,导致你误解为saferegion是保存Heap中某块内存不回收的区域?更详细的介绍可参见这里。 |
|
nkhanxh
2012-01-31
不好意思,发现我还是没完全明白。
test指令遇到不可读的内存区域会怎样?是否会产生段保护错误触发自陷? 这样vm接管控制权?这样(用test而不用分支判断)是效率高吗?因为一般不会有垃圾收集? 我之所以有这个问题是因为我对汇编并不熟悉。。。 nkhanxh 写道 IcyFenix 写道 引用 如果某个线程正在执行一个耗时比较长的翻译后的机器代码呢?控制权是不在解释器手里的。这样岂非垃圾收集要等待一个不确定的时间才能开始?但是如果内存已经很紧张需要马上收集了呢?
JIT生成的机器代码就预留了伏笔,生成了轮询safepoint的指令。 例如hotspot在x86中为轮询safepoint会生成一条类似于“test %eax,0x160100”的指令,JVM需要进入gc前,先把0x160100设置为不可读,那所有线程执行到检查0x160100的test指令后都会停顿下来。 轮询safepoint的动作是足够密集的,JIT在方法调用、循环跳转、异常跳转处都会生成轮询指令,所以不应当会等待过长的时间。一个例外是在线程处sleep或者blocked状态的时候,这时不会分配cpu时间,不过可以通过saferegion来解决,把线程sleep这些包裹在saferegion之内,线程进入saferegion的时候做个标识,gc开始的时候不需要检查处于saferegion的线程,在线程退出saferegion的时候再检查能否出去,如果gc已经完了就出去,没完就待着。 哦。多谢!有点明白了。不过我还有更进一步的问题, 麻烦看一下下面。 不过这样来说,还是会导致比一般的程序慢一些是吧。是不是这也是导致java程序比c++程序慢一些的重要因素之一呢? 毕竟c++程序清晰地知道自己合适需要释放、分配内存。也许会有个10%左右的损失? 而且这种也算分支指令吧?也有预测失效问题,也会导致一点点损失?不过因为估计好的cpu大多数时候猜对这个分支,一般都不发生流水线失效? 最后,关于saferegion问题,会否有这种情况,眼下没内存了,但是还有人block住并且占住了关键的内存区,这时候垃圾收集器岂非干瞪眼不能干什么? 是否jvm里面会有对应的检查,如果这种情况的话,会有对应的策略?比如进行更深的分析,采取如下手段: 0.分析一下是否值得采取1,2手段。比如该段代码占用的指针指向的内存超过某阈值。 1.收集该段会引用的内存,如果值得则开始1,2。 2.将该段机器码重新编译成为引用垃圾收集后指针的位置等等。 感觉垃圾策略收集说起来很简单,但是只要涉及高效实现就会很麻烦。 |