1.如何评价jdk10?
2.见了鬼,我JVM的Survivor区怎么只有20M了?
3.OpenJDK17-JVM 源码阅读 - ZGC - 并发标记 | 京东物流技术团队
4.完全体!千字详解:“Java性能调优六大工具”之JConsole工具
5.Java native 关键字
6.深入浅出 Java FileChannel 的堆外内存使用
如何评价jdk10?
JDK 虽然只是一个小版本更新,但仍引入了一些关键改变,下文将聚焦几个对一般开发者影响重大的特性进行阐述。
在Java 中引入了局部变量类型推断,阶梯计价源码通过使用var替代局部变量声明时的类型部分,可以避免重复编写显而易见的类型。例如:
var o = new Object();
var特性允许在局部变量声明、增强型for循环循环变量声明及try-with-resources内资源变量声明中使用。Java 中将支持使用var替代lambda参数类型。在兼容原有代码的前提下,var非关键字,仍可作为变量名使用。值得注意的是,final var声明的变量不可变,即便未显式声明为final。
JDK 中G1垃圾回收器实现了完全并行化,减少了full gc的发生,提升性能。G1是JDK 9的默认垃圾回收器。
JDK 彻底移除了javah工具,因为JDK 1.8中javac已引入-h选项生成JNI头文件,javah功能被替代。移除对非Java JVM语言及特殊工具(如SBT javah插件)可能产生影响。可尝试使用其他库替代。
JDK 对OpenJDK源码结构进行了重大调整,简化结构以提高清晰度,源码结构对比信息可参考编码gist。遗憾的是,项目loom和valhalla未能包含在Java 中,希望它们能在Java 中实现,暴风涨停指标源码否则需等待下一个长期支持版本。
见了鬼,我JVM的Survivor区怎么只有M了?
某日,一位同学在群里分享了使用 jmap -heap 查看内存使用情况的截图,发现 Survivor 区占比总是在%以上,这引发了讨论。通过分析图中的信息,我们注意到 Eden、From、To 之间的比例并非默认的8:1:1,这提示我们可能存在 AdaptiveSizePolicy 的影响。使用 jinfo -flags pid 确认使用的是 JDK 1.8 的默认回收算法 UseParallelGC,并且默认开启了 AdaptiveSizePolicy。这表示每次垃圾回收(GC)后,JVM 会重新计算 Eden、From 和 To 区的大小,以适应不同的 GC 时间、吞吐量和内存占用量。在默认配置下,虽然 SurvivorRatio 的默认值是8,但年轻代三个区域的比例仍会变动,从而导致 Survivor 区的大小变化。
在讨论中,我们发现老年代的内存使用量也较高,这引起了进一步的调查。通过使用 jmap -histo 查看堆中的实例,发现有两个类的实例数量较多,分别是 ExpiringCache 和 LinkedHashMap。进一步分析发现 ExpiringCache 的构造函数初始化了一个 LinkedHashMap,这可能是问题的源头。通过分析 ExpiringCache$Entry 类和其使用场景,目标跟踪算法源码我们定位到问题出在使用 getCanonicalPath() 方法的类上,这会导致大量 ExpiringCache$Entry 对象进入老年代。使用 jstat -gcutil 查看 GC 情况,发现从应用启动到执行 jmap -histo 命令期间,触发了次 Full GC(FGC),耗时9.秒,平均每次 FGC 耗时约为毫秒。这表明内存管理存在优化空间。
为了解决 Survivor 区变小、老年代占比变高的问题,有几种解决方案可以考虑。一种简单的方法是不使用缓存,可以通过设置参数-Dsun.io.useCanonCaches=false 来关闭缓存。另一种方案是保持使用 UseParallelGC,显式设置 -XX:SurvivorRatio=8,以固定年轻代三个区域之间的比例。最后,可以考虑使用 CMS 垃圾回收器,因为它默认关闭 AdaptiveSizePolicy,从而提供更稳定的内存管理。
对于源码层面的理解,JDK 1.8 中的 UseParallelGC 和 AdaptiveSizePolicy 的互动主要通过参数配置来实现。当显式设置 SurvivorRatio 时,JVM 会确保这个参数在 Parallel Scavenge 回收器中生效,从而固定年轻代三个区域的比例。在 AdaptiveSizePolicy 的实现中,JVM 会在 GC 过程完成后根据预期停顿时间和实际的 GC 时间动态调整内存大小。然而,在使用 CMS 垃圾回收器时,JDK 1.8 中的逻辑会将 UseAdaptiveSizePolicy 设置为 false,这限制了 AdaptiveSizePolicy 的暗黑战神源码教程应用,从而影响内存管理的优化。
通过这次讨论和分析,我们不仅发现了内存管理中的问题,还学习了如何通过参数配置来优化 JVM 的垃圾回收策略。对于开发者而言,了解并合理配置这些参数可以显著提升应用的性能和稳定性。
OpenJDK-JVM 源码阅读 - ZGC - 并发标记 | 京东物流技术团队
ZGC简介:
ZGC是Java垃圾回收器的前沿技术,支持低延迟、大容量堆、染色指针、读屏障等特性,自JDK起作为试验特性,JDK起支持Windows,JDK正式投入生产使用。在JDK中已实现分代收集,预计不久将发布,性能将更优秀。
ZGC特征:
1. 低延迟
2. 大容量堆
3. 染色指针
4. 读屏障
并发标记过程:
ZGC并发标记主要分为三个阶段:初始标记、并发标记/重映射、重分配。本篇主要分析并发标记/重映射部分源代码。
入口与并发标记:
整个ZGC源码入口是ZDriver::gc函数,其中concurrent()是一个宏定义。并发标记函数是concurrent_mark。
并发标记流程:
从ZHeap::heap()进入mark函数,使用任务框架执行任务逻辑在ZMarkTask里,具体执行函数是work。工作逻辑循环从标记条带中取出数据,直到取完或时间到。此循环即为ZGC三色标记主循环。之后进入drain函数,智能盯盘源码从栈中取出指针进行标记,直到栈排空。标记过程包括从栈取数据,标记和递归标记。
标记与迭代:
标记过程涉及对象迭代遍历。标记流程中,ZGC通过map存储对象地址的finalizable和inc_live信息。map大小约为堆中对象对齐大小的二分之一。接着通过oop_iterate函数对对象中的指针进行迭代,使用ZMarkBarrierOopClosure作为读屏障,实现了指针自愈和防止漏标。
读屏障细节:
ZMarkBarrierOopClosure函数在标记非静态成员变量的指针时触发读屏障。慢路径处理和指针自愈是核心逻辑,慢路径标记指针,快速路径通过cas操作修复坏指针,并重新标记。
重映射过程:
读屏障触发标记后,对象被推入栈中,下次标记循环时取出。ZGC并发标记流程至此结束。
问题回顾:
本文解答了ZGC如何标记指针、三色标记过程、如何防止漏标、指针自愈和并发重映射过程的问题。
扩展思考:
ZGC在指针上标记,当回收某个region时,如何得知对象是否存活?答案需要结合标记阶段和重分配阶段的代码。
结束语:
本文深入分析了ZGC并发标记的源码细节,对您有启发或帮助的话,请多多点赞支持。作者:京东物流 刘家存,来源:京东云开发者社区 自猿其说 Tech。转载请注明来源。
完全体!千字详解:“Java性能调优六大工具”之JConsole工具
JConsole工具作为JDK自带的图形化性能监控工具,为Java应用程序的性能分析提供了便利。本文将为您详细介绍JConsole的基本使用方法和各项功能。
首先,启动JConsole,连接Java应用程序。在启动界面,可以通过"新建连接"功能选择本地或远程Java应用程序,需要配置远程进程的IP地址与端口号以实现远程连接。
连接成功后,JConsole将展示Java应用程序的概览,包括堆内存使用情况、线程数量、类加载数量以及CPU使用率。通过这些数据,可以快速了解应用程序的运行状况。
在内存监控功能中,JConsole提供了详细的内存使用信息,包括堆内存、非堆内存(永久代)的使用情况。通过"执行GC"按钮,可以强制执行垃圾回收操作,优化内存管理。
线程监控部分展示了系统内的线程数量和详细信息。用户可以查看单个线程的栈信息,并利用"检测到死锁"功能快速定位死锁问题。这对于多线程应用程序的调试和优化尤为重要。
类加载情况通过JConsole的"类"选项卡直观呈现,显示已装载和已卸载的类数量,有助于理解类加载过程的效率和稳定性。
虚拟机信息部分,JConsole提供了虚拟机的摘要信息,包括类型、版本、堆信息、垃圾回收器类型等,为深入理解虚拟机运行环境提供了帮助。
MBean管理功能允许用户通过JConsole对MBean进行操作,包括属性查看与设置、方法运行等,实现对Java应用程序的精细化管理。
为了扩展功能,JConsole支持插件扩展。通过加载内置的JTop插件,用户可以访问JTop页面,对CPU占用时间排序,快速定位占用资源最大的线程。此外,JTop插件的源代码可以提供进一步的自定义和扩展可能性。
JConsole的综合使用,能够有效提升Java应用程序的性能分析效率,从多个维度提供详尽的监控和诊断信息,为开发者优化代码和提高应用性能提供了强有力的支持。
Java native 关键字
在浏览 JDK 源代码时,常会发现大量使用了 native 关键字的方法。此关键字用于表明方法的实现并非使用 Java,而是在其他语言如 C 或 C++ 中完成。实际上,native 方法是一个 Java 调用非 Java 代码的接口。
所谓 Native Method,指的是 Java 调用非 Java 代码实现的方法。具体来说,它是一个原生态方法,其对应实现不在当前文件内,而位于使用其他语言(如 C 和 C++)实现的文件中。使用 native 关键字标识的方法,意味着其用 C/C++ 语言实现,编译成 DLL,由 Java 进行调用。
在 JVM 的底层实现中,很多部分都是通过 C 语言实现的。例如,openj9 源代码中,GC 功能的实现便是用 C 语言完成。Java 在调用这些底层实现时,实际上调用的是外部动态库等,因此通过 native 关键字标记,表明具体的实现位于 JVM 内。
实现 native 方法的关键技术是 JNI(Java Native Interface)。JNI 是 Java 的本地接口,它允许 Java 调用本地代码(如 C 或 C++),并支持 Java 方法调用本地功能。通过 JNI,实现可调用过程变得简单。在 JDK 中,native 关键字仅用于标记,并不涉及实现细节。
总之,native 关键字在 Java 中用于标识方法的实现非 Java,而是通过其他语言完成,通常通过 JNI 实现调用。这一设计允许 Java 利用不同语言的强项,提高性能和功能。
深入浅出 Java FileChannel 的堆外内存使用
从一个线上系统 OOM 讲起,我们通过解决用户反馈的 IoTDB 查询卡住问题,深入探讨了 Java FileChannel 中的堆外内存使用。
首先,让我们了解一下背景知识。FileChannel 是 Java NIO 提供的文件通道类,它允许对文件进行读写操作。而堆外内存是指直接分配在系统内存中的内存区域,不受 Java 堆管理。
FileChannel 使用堆外内存的原因是提高性能。当使用 DirectByteBuffer 时,数据本来就在堆外内存中,因此在进行 I/O 操作时没有拷贝的过程,这被称为“零拷贝”。然而,操作系统需要将堆上的数据拷贝到堆外内存中进行 I/O 操作,因为操作系统通过内存地址进行数据交互。
当 JVM 进行垃圾回收(GC)时,可能会导致内存地址的变化,影响正在执行的 I/O 操作。因此,将数据从堆复制到堆外内存,可以保证数据地址在 I/O 过程中保持不变。
在 JDK 的源码分析中,我们发现 DirectByteBuffer 的分配和回收机制。DirectByteBuffer 在分配时创建的 Cleaner 对象用于堆外内存的回收,当 DirectByteBuffer 仅被 Cleaner 引用时,其可以在任意 GC 时段被回收。这样,虽然堆外内存并非完全不受 GC 控制,但通过 Cleaner 实现了有效的回收机制。
FileChannel 在读写过程中,使用 DirectByteBuffer 进行数据操作。在分配和回收临时 DirectByteBuffer 时,考虑到系统的资源限制,适当调整 TEMP_BUF_POOL_SIZE 的值可以避免 OOM 的问题。
回到开头提到的线上问题,用户在使用 IoTDB 时遭遇 OOM。通过源码分析,我们发现没有适当配置 MAX_CACHED_BUFFER_SIZE,导致额外分配的堆外内存缓存过大,最终引发 OOM。通过调整配置,解决了这个问题。
Java FileChannel 的堆外内存使用,提高了 I/O 操作的性能,但也需要合理配置和管理,避免资源浪费和内存泄露,确保系统的稳定运行。