Progress on JDK

  • JDK 10: http://openjdk.java.net/projects/jdk/10/

    • http://openjdk.java.net/jeps/310 Application Class-Data Sharing
      # Determining the classes to archive
      java -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=hello.lst \
          -cp hello.jar HelloWorld
      
      # Creating the AppCDS archive
      java -Xshare:dump -XX:+UseAppCDS -XX:SharedClassListFile=hello.lst \
          -XX:SharedArchiveFile=hello.jsa -cp hello.jar
      
      # Verify classpath mistach and mmap
      java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=hello.jsa \
          -cp hello.jar -Xlog:class+load=info HelloWorld
      
      # Production deployment
      java -Xshare:auto -XX:+UseAppCDS -XX:SharedArchiveFile=hello.jsa \
          -cp hello.jar HelloWorld
      
  • JDK 11: http://openjdk.java.net/projects/jdk/11/

  • JDK 12: http://openjdk.java.net/projects/jdk/12/

    • http://openjdk.java.net/jeps/189 Shenandoah: A Low-Pause-Time Garbage Collector

      java -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
      

      -XX:ShenandoahUncommitDelay=MILLISECONDS 归还内存,默认五分钟。

    • http://openjdk.java.net/jeps/341 Default CDS Archives 64-bit JDK 构建时自动对系统类执行 -Xshare:dump,在 JDK 11 中 -Xshare:auto 是默认选项,所以用户可以自动使用 CDS。

    • http://openjdk.java.net/jeps/346 Promptly Return Unused Comitted Memory from G1

      java -XX:G1PeriodicGCInterval=300000 -XX:G1PeriodicGCSystemLoadThreshold=LOAD \
           -XX:-G1PeriodicGCInvokesConcurrent -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=30
      

      关掉 G1PeriodicGCInvokesConcurrent 会触发 Full GC 以归还更多内存,但对应用干扰更大。注意 G1PeriodicGCInterval 默认是 0。

  • JDK 13: https://openjdk.java.net/projects/jdk/13/

    • http://openjdk.java.net/jeps/350 Dynamic CDS Archives 简化 AppCDS 的生成,并且依赖 JDK 默认的 CDS archive

      java -XX:ArchiveClassesAtExit=hello.jsa -cp hello.jar Hello
      java -XX:SharedArchiveFile=hello.jsa -cp hello.jar Hello
      
    • http://openjdk.java.net/jeps/351 ZGC: Uncommit Unused Memory -XX:ZUncommitDelay=SECONDS 启用,需要 -Xms 小于 -Xmx,只支持 Linux >= 3.5 or >= 4.3。

  • JDK 14: https://openjdk.java.net/projects/jdk/14/

通常 ZGC 在吞吐量和暂停时间方面比 Shenandoah 更好,但是 Shenandoah 在极端情况比如堆接近耗尽、突发的高速内存分配时表现更好,而且 ZGC 在设计上要求 64bit 指针,因此不能使用 compressed oops,意味着在小于 32G 的 Java heap 上比较浪费内存,读操作由于 CPU 缓存缘故略慢,好处是写操作略快,因为不需要额外处理 compressed oop。

Tuning Recommendation

For JDK 13.

JAVA_OPTS="-server \
           -XX:+UnlockDiagnosticVMOptions \
           -XX:+UnlockExperimentalVMOptions \
           -XX:+UseShenandoahGC \
           -XX:+UseLargePages \
           -XX:+UseNUMA \
           -Xms64m \
           -Xmx256m \
           -XX:MinHeapFreeRatio=10 \
           -XX:MaxHeapFreeRatio=30 \
           -XX:+ClassUnloadingWithConcurrentMark \
           -XX:+ExplicitGCInvokesConcurrent \
           -XX:+ExitOnOutOfMemoryError \
           -Xlog:gc*,gc+age=trace,safepoint:file=log/gc.log:time,pid,level,tags:filecount=10,filesize=50m \
           -Xbatch \
           -XX:+PrintCommandLineFlags \
           -XX:+PrintFlagsFinal \
           --show-version \
           -Djdk.nio.maxCachedBufferSize=65536 \
           -Djava.awt.headless=true \
           -Duser.timezone=Asia/Shanghai \
           -Dfile.encoding=UTF-8"

# For Debug
#JAVA_OPTS="$JAVA_OPTS -XX:NativeMemoryTracking=detail -XX:+PrintNMTStatisticsec"

# build time
java -XX:ArchiveClassesAtExit=hello.jsa -cp hello.jar Hello

# run time
java -XX:SharedArchiveFile=hello.jsa -cp hello.jar Hello

Debug off-heap memory usage

  1. -XX:+UnlockDiagnosticVMOptions 打开后可以使用 jcmd PID GC.class_stats

  2. -XX:NativeMemoryTracking=summary|detail 打开后可以使用 jcmd PID VM.native_memory,-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatisticsec 打开后 JVM 会在退出时输出 native memory 信息

  3. pmap -x PID 查看 /proc/PID/smaps 信息

  4. jcmd 7 VM.native_memory summary scale=MB,输出分为如下几类,其中占用内存最多的是 Class, Thread, Code, Internal:

    • Java Heap;
    • Class,meta space,包含类的元信息以及 String.intern 的字符串
    • Thread,thread stack,每个线程占用 1MB stack
    • Code,code cache JIT 生成的机器码
    • GC,GC 算法所需的内部数据结构
    • Internal,包含 DirectByteBuffer 分配的 off-heap memory(新版本 JDK放到了 Other 类)
    • Symbol
    • Unknown
  5. 没有实现 hashCode()equals() 方法的对象,作为 HashSet 和 HashMap 的 key 时会发生内存泄漏

  6. HeapByteBuffer 使用 DirectByteBuffer 来拷贝数据,为了避免频繁分配内存,每个线程都会缓存一些 DirectByteBuffer,使用 -Djdk.nio.maxCachedBufferSize=65536 (bytes)来限制缓存,超过此大小的 DirectByteBuffer 会在拷贝完数据后立刻释放。

  7. -XX:MaxDirectMemorySize=xx 默认跟 -Xmx 参数的值相同,当 Java heap 非常大或者非常小时,可以通过这个参数调节 DirectByteBuffer 可以使用的最大内存。

  8. 在 Java heap 富裕时,很少触发 full gc,可能导致 old region 引用的 DirectByteBuffer 不能尽早释放,可以尝试定期 full GC。

  9. glibc < 2.12 的线程局部内存分配区可能保留太多内存:64MB * num_cores * MALLOC_ARENA_MAX(默认为 8),可以使用 pmap 检查 64MB 的内存段数量,并通过 export MALLOC_ARENA_MAX=${MALLOC_ARENA_MAX:-4} 来缓解,或者换用 jemalloc、tcmalloc、mimalloc。

References

  1. https://docs.oracle.com/en/java/javase/13/gctuning/index.html
  2. https://dzone.com/articles/troubleshooting-problems-with-native-off-heap-memo
  3. https://docs.oracle.com/en/java/javase/13/vm/native-memory-tracking.html#GUID-710CAEA1-7C6D-4D80-AB0C-B0958E329407
  4. https://docs.oracle.com/en/java/javase/13/troubleshoot/diagnostic-tools.html#GUID-1F53A50E-86FF-491D-A023-8EC4F1D1AC77
  5. https://ionutbalosin.com/2019/12/jvm-garbage-collectors-benchmarks-report-19-12/
  6. https://ionutbalosin.com/2020/01/hotspot-jvm-performance-tuning-guidelines/
  7. https://chriswhocodes.com/ VM Options Explorer
  8. https://www.youtube.com/watch?v=MU8NapbG1IQ Understanding Low Latency JVM GCs by Jean Philippe Bempel
  9. https://www.slideshare.net/JeanPhilippeBEMPEL/understanding-low-latency-jvm-gcs
  10. https://github.com/GregBowyer/ManagedRuntimeInitiative/tree/master/MRI-J/hotspot/src/azshare/vm/gc_implementation/genPauseless C4(之前叫GPGC) 2010 年开源的代码
  11. https://www.infoq.com/articles/azul_gc_in_detail/
  12. https://shipilev.net/talks/jugbb-Sep2019-shenandoah.pdf
  13. http://mail.openjdk.java.net/pipermail/zgc-dev/2017-December/000012.html C4, ZGC, Shenandoah 2.0 使用的 multi-mapping 会导致 Linux 内核重复计算 RSS