JVM中对java.lang.ref.Reference$Lock上的数组分配的大量锁

JVM中对java.lang.ref.Reference$Lock上的数组分配的大量锁,java,performance,garbage-collection,locking,jfr,Java,Performance,Garbage Collection,Locking,Jfr,我们使用Java Flight Recorder评测应用程序,并在Java.lang.ref.Reference$Lock对象上发现了大量锁 我调查了stacktraces中的一些地方,发现在所有情况下都存在数组分配 代码示例(图像上的位置3): public static char[]copyOfRange(char[]original,int-from,int-to){ int newLength=to-from; if(newLength”+到); //在下一行堆叠跟踪点 char[]c

我们使用Java Flight Recorder评测应用程序,并在Java.lang.ref.Reference$Lock对象上发现了大量锁

我调查了stacktraces中的一些地方,发现在所有情况下都存在数组分配

代码示例(图像上的位置3):

public static char[]copyOfRange(char[]original,int-from,int-to){
int newLength=to-from;
if(newLength<0)
抛出新的IllegalArgumentException(从+“>”+到);
//在下一行堆叠跟踪点
char[]copy=new char[newLength];
System.arraycopy(原件、原件、副本、0、,
Math.min(original.length-from,newLength));
返回副本;
}
我怀疑这种锁定与GC有关,但找不到任何相关信息。 我在哪里可以阅读更多关于这个主题的内容

活动的最终目标:了解在这种情况下PAPPENING是什么,影响PAPPENING的因素,以及我们如何减少此类操作的锁定时间

评论中的一些细节:

  • 爪哇8
  • 堆512Mb
  • GC-G1
  • 通过实验我发现,锁定时间随着堆大小的增加而减少

  • Java Flight Recorder的一大缺点是它只显示Java堆栈,完全忽略了本机和VM部分

    从这个意义上讲更准确。如果在启用本机堆栈的情况下以
    lock
    评测模式运行它,它将显示获取这些锁的确切位置。命令示例:

    ./profiler.sh -d 60 -e lock --cstack fp -f profile.html -o flamegraph=total PID
    
    • -d 60
      运行分析60秒
    • -e lock
      配置文件锁争用
    • --cstack fp
      记录C(本机)堆栈
    • -f profile.html
      输出文件名(async profiler 2.0中的html格式,或1.x中的SVG)
    • -o flamegraph=total
      使用总锁等待时间作为计数器构建火焰图
    • PID
      Java进程ID

    在本例中,Flame图突出显示了
    引用$lock
    实例上的锁争用。堆栈跟踪的Java部分显示为绿色。这与您在JFR中看到的堆栈跟踪相匹配。与您的示例一样,顶部的Java框架是
    Arrays.copyOfRange
    (图中还显示了其他堆栈,但让我们关注第一个堆栈)

    黄色部分是本机C++代码。让我解释一下那里发生了什么

  • array.copyOfRange
    调用VM运行时函数
    OptoRuntime::new_array\u nozero\u C
    。实际的数组分配发生在JVM的C++代码中。
  • JVM无法从现有线程本地分配缓冲区(TLAB)分配数组,然后返回到新TLAB的慢速路径分配

  • 慢路径分配也不会成功,因为Java堆中没有足够的可用内存。因此,JVM同步调用垃圾收集器

  • 在GC序言中,JVM尝试获取保护挂起引用列表的锁。这是为了确保
    ReferenceHandler
    线程在GC启动之前离开关键部分。在持有此锁的同时,JVM可以安全地将新发现的弱引用附加到挂起列表

  • 但是,锁已经被另一个线程获取,该线程同时尝试以相同的方式调用垃圾收集器。当前线程将挂起,直到GC完成

  • 总之,多个Java线程同时尝试从堆中分配一个对象,但堆已满。因此,垃圾收集开始了,分配线程在引用挂起列表锁上被阻塞

    Reference$Lock
    上的争用本身不是问题。在GC回收足够的内存之前,分配线程无论如何都无法继续。实际问题是,并发垃圾收集跟不上分配速率

    要缓解此问题,请尝试以下一种或多种方法:

    • 增加堆的大小
    • 降低分配率
    • 增加并发GC线程的数量-
      congcthreads
    • 降低
      启动eapOccupencyPercent
      以更早地启动并发GC循环
    增加堆可能是最有效的方法

    顺便说一句,async profiler还有其他有用的模式来诊断GC相关问题:

    • -e cpu
      显示占用最多cpu时间的内容。Java和VM线程一起显示在同一个图上,因此您可以了解GC活动相对于应用程序工作是否过高
    • -e alloc
      显示分配最多的代码。在研究如何降低分配率时,它特别有用

    哪个GC和JVM版本?另一个问题。虽然我当然理解您想了解造成这种情况的原因,但您是否有实际的性能问题?Java 8,GC-G1,heap size-512MB垃圾收集器获取此锁,因此,当您发现增加堆大小会缩短时间时,您可能只是有过多垃圾收集的问题,换句话说,您的堆大小对于正在执行的操作来说太小。与其看方法和锁,不如看垃圾收集与应用程序代码CPU时间的比率。@Eugene据我所知,垃圾收集器在开始工作时总是会获得这个锁。真是太好了。我们最近才开始使用async profiler(只是一点点),而且我们在几个小时内从它的使用中获得了如此多的见解,这简直是疯了。我们真诚地感谢您。我希望我能在Windows下使用它…@Holger还没有,对不起。我希望Windows是POSIX:)