我正在研究HotSpot在JNI代码运行时如何执行垃圾收集和/或堆压缩.
似乎众所周知,可以随时在Java中移动对象.我试图明白,如果JNI受到垃圾收集的影响.存在许多JNI函数来明确地防止垃圾收集; 如GetPrimitiveArrayCritical.如果引用确实是volatile,则存在这样的函数是有道理的.但是,如果不是,那就没有意义.
关于这个问题似乎存在大量相互矛盾的信息,我正试图解决这个问题.
JNI代码在安全点运行并且可以继续运行,除非它回调到Java或调用某些特定的JVM方法,此时可以停止它以防止离开安全点(感谢Nitsan的注释).
上面让我认为垃圾收集将与JNI代码同时运行.那不可能是安全的,对吗?
为了实现本地引用,Java VM为从Java到本机方法的每次控制转换创建了一个注册表.注册表将不可移动的本地引用映射到Java对象,并防止对象被垃圾回收.传递给本机方法的所有Java对象(包括那些作为JNI函数调用结果返回的对象)都会自动添加到注册表中.在本机方法返回后删除注册表,允许其所有条目被垃圾回收.
https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp16789
好的,所以local引用是不可移动的,但是没有说明压缩的内容.
JVM必须确保将作为参数从Java™传递到本机方法的对象以及由本机代码创建的任何新对象仍可由GC访问.为了处理GC要求,JVM分配了一个称为"本地参考根集"的小型专用存储区域.
在以下情况下创建本地引用根集:
- 线程首先附加到JVM(线程的"最外层"根集).
- 每次发生J2N转换.
JVM使用以下命令初始化为J2N转换创建的根集:
- 对调用者的对象或类的本地引用.
- 对作为参数传递给本机方法的每个对象的本地引用.
除非使用PushLocalFrame JNI函数创建新的"本地帧",否则在本机代码中创建的新本地引用将添加到此J2N根集.
好的,所以IBM将传递的对象存储在其中,local reference root set但它没有讨论内存压缩.这只是说对象不会被垃圾收集.
GC可能随时决定是否需要压缩垃圾收集堆.压缩涉及将对象从一个地址物理移动到另一个地址.这些对象可能由JNI本地或全局引用引用.为了允许安全地进行压缩,JNI引用不是指向堆的直接指针.至少一个间接级别将本机代码与对象移动隔离开来.
如果本机方法需要获得对象内部的直接可寻址性,则情况会更复杂.在需要对大型原始数组进行快速,共享访问的情况下,直接寻址或固定堆的要求是典型的.示例可能包括屏幕缓冲区.在这些情况下,可以使用JNI临界区,这对程序员提出了额外的要求,如JNI对这些函数的描述中所规定的那样.有关详细信息,请参阅JNI规范.
- GetPrimitiveArrayCritical返回Java™数组的直接堆地址,禁用垃圾收集,直到调用相应的ReleasePrimitiveArrayCritical.
- GetStringCritical返回java.lang.String实例的直接堆地址,在调用ReleaseStringCritical之前禁用垃圾收集.
好的,所以IBM基本上说JNI传递的对象可以随时移动!HotSpot怎么样?
GetArrayElements系列函数被记录为复制数组或将它们固定到位(并且这样做可以防止压缩垃圾收集器移动它们).它被记录为GetPrimitiveArrayCritical的更安全,限制性更小的替代方案.但是,我想知道哪些虚拟机和/或垃圾收集器(如果有的话)实际固定数组而不是复制它们.
Aleksandr似乎认为访问传递对象的内存的唯一安全方法是通过Get<PrimitiveType>ArrayElements或GetPrimitiveArrayCritical
特伦特的回答并不令人兴奋.
至少在当前的JVM中(我还没有检查过它被反向移植的程度),CMS GC,因为它不移动不受JNI关键部分的影响(模数如果有并发的话,可能会发生非停止压缩)模式失败 - 在这种情况下,分配线程必须停止,直到关键部分被清除 - 后一种停顿可能比你可能更频繁地看到的旧病理学中的慢速路径直接分配更罕见.请注意,旧版本中的直接分配不仅速度慢(一阶性能影响),而且可能反过来导致更多的任期(因为所谓的裙带关系),以及由于卡片更加严重而导致的后续清理速度变慢需要扫描(后者都是二次效果).
http://mail.openjdk.java.net/pipermail/hotspot-runtime-dev/2007-December/000074.html
OpenJDK邮件列表上的这封电子邮件似乎表明ConcurrentMarkAndSweep GC是不动的.
https://www.infoq.com/articles/G1-One-Garbage-Collector-To-Rule-Them-All
关于G1的这篇文章提到它确实压缩了堆,但没有特别关于移动数据.
现在,我一直在尽力遵循HotSpot代码.让我们来看看GetByteArrayElements.在复制元素之前,该方法必须确保指针正确,这似乎是合乎逻辑的.让我们试着找出方法.
这是宏 GetByteArrayElements
#ifndef USDT2
#define DEFINE_GETSCALARARRAYELEMENTS(ElementTag,ElementType,Result, Tag)
JNI_QUICK_ENTRY(ElementType*,
jni_Get##Result##ArrayElements(JNIEnv *env, ElementType##Array …Run Code Online (Sandbox Code Playgroud) 解决了
在我们的例子中,问题是对于SuggestRequestHandler(requestHandler name ="/ suggest")现在已经设置了facelimit:10此外,对应用程序发出的每个建议请求也有几个请求.为什么这导致(仅)小时的高峰尚不清楚......
谢谢大家的提示和帮助 - 我很感激!
每隔一小时(12:00,13:00,14:00,...,20:00,21:00,22:00,23:00)我们的Solr/Java进程都有一个高峰 - 这意味着Java进程Solr运行的地方增加了3倍的CPU使用率和响应时间 - 通常需要msecs才能响应,最多9秒.总是2到3分钟,只有当我们网站上有流量时(有一个叫做Java的php应用程序).Crond完全禁用,但每整个小时仍然存在问题.基本上我认为我们几乎尝试了每个GC和内存组合(或者可能不是?)
有人知道为什么会这样 - 这里有一些细节:
使用的处理程序(solrconfig.xml):
<requestHandler name="standard" class="solr.SearchHandler" default="true">
<requestHandler name="dismax" class="solr.SearchHandler" >
<requestHandler name="/suggest" class="solr.SearchHandler">
<requestHandler name="/update" class="solr.XmlUpdateRequestHandler" />
<requestHandler name="/analysis/document" class="solr.DocumentAnalysisRequestHandler" />
<requestHandler name="/analysis/field" class="solr.FieldAnalysisRequestHandler" />
<requestHandler name="/admin/" class="org.apache.solr.handler.admin.AdminHandlers" />
<requestHandler name="/admin/ping" class="PingRequestHandler">
<requestHandler name="/debug/dump" class="solr.DumpRequestHandler" >
<requestHandler name="/replication" class="solr.ReplicationHandler" >
Run Code Online (Sandbox Code Playgroud)
(也测试没有复制和ping)
二手过滤器:
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1"
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.PortugueseMinimalStemFilterFactory"/>
<filter …Run Code Online (Sandbox Code Playgroud)