OpenJDK17-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。转载请注明来源。
《面试1v1》JVM调试
我叫 javapub,一名Markdown程序员兼八股文种子选手。在一次面试中,面试官向我提问关于JVM调优的掌握程度。我首先对JVM调优的理解进行了谦虚的描述,承认自己还在学习阶段,但日常工作中有接触JVM参数与监控工具,算是有入门水平。
面试官接着询问了我对JVM调优具体的方法和手段。我回答,JVM调优主要通过调整JVM参数和使用监控工具来进行。我日常工作中最常用的JVM参数和监控工具,帮助我在日常的JVM调优和问题排查中发挥了很大作用。尽管如此,我还需要继续深入学习JVM调优的更深层次理论和实践。
面试官对我的回答给予了肯定,认为我已经掌握了JVM调优的无人直播拜年源码基础知识和日常工具的使用,是一个不错的入门水平。随后,面试官又问到了我作为JVM调优的进阶学习计划。我详细地介绍了我的学习计划,包括深入学习JVM调优的理论知识、实践技能等,需要通过不断学习和实践来提升。
最近,我正在更新《面试1v1》系列文章,以场景化的方式讲解在面试中遇到的问题,旨在帮助工程师们拿到心仪的offer。文章内容涵盖了JVM调优、数据结构与算法、Mybatis、搜索LuceneElasticsearch、Spring等技术点,以对话的形式、口语化描述技术点,涉及高频面试题、工作中如何使用,还穿插了部分源码解析。
《面试1v1》文章通过真实的面试场景模拟,帮助候选人如何自然地对答,避免紧张导致的知识点遗忘。文章以实际面试情况编写,使用大白话描述技术难题,避免背诵式回答。读者可以在这里找到面试中常考的知识点,甚至一些源码细节,以更好地准备面试。
《面试1v1》文章由我完全免费提供,并持续更新中。读者可以通过搜索“JavaPub”直接查看全系列文章。我将根据读者反馈和市场需要,持续更新和改进《面试1v1》系列文章,以帮助更多工程师们掌握面试技巧,顺利拿到心仪的莹火商城源码offer。
如果你对《面试1v1》系列文章感兴趣,欢迎关注我 @JavaPub,获取最新的文章更新和学习资源。希望我的分享能够帮助你在面试中表现出色,实现职业发展的目标。
分享一个调试Java程序生产问题工具:Lightrun
在本文中,我将分享 Lightrun,这是我最近在开发RevoGain时发现的一个非常有用的工具,用于调试生产中出现的问题。
Lightrun提供了动态日志记录、运行时快照和动态指标等功能,允许我们在运行时动态插入日志条目、捕获快照,甚至在不更改生产代码的情况下注入指标,这在调查客户报告的问题时特别有用。
设置Lightrun非常简单,配置它只需要不到5分钟。对于本地Windows环境,可以通过代理提供。对于生产系统,可以使用基于Linux的命令。动态日志记录允许我们添加消息,并通过定义过滤条件来控制是否记录该消息。
在解析交易报表时,如果交易余额加起来不出来,需要具有交易对账单。添加动态日志条目将帮助我们在用户执行可以复制问题的操作时发现问题。通过定义特定条件,如id值为1的用户,可以仅显示特定消息。
查看如何根据我们从对账单中解析的交易操作计算余额。如果计算的余额与语句提供的余额值不匹配,我们可以向客户确定导致问题的原因。没有Lightrun,我们不能只是调试生产系统,因为整个服务器将停止,从而影响可用性。8的源码是
除了动态日志记录,Lightrun还提供了运行时快照功能,这些快照包含堆栈跟踪和拍摄快照时可用的变量。这可以帮助我们一次聚合多个信息,而不必使用单个日志。
我们还可以动态添加指标,而无需更改我们正在监控的源代码。例如,我使用此功能来计算使用 Stop Forum Spam API验证电子邮件地址需要多长时间。这很重要,因为我正在为我的客户提供实时聊天,我可以在我们的实时对话中调试他们的生产问题。
Lightrun易于使用,但功能非常强大,因为它允许我们使用指标注入日志、收集快照或检测我们的代码,而无需更改需要重新部署的生产源代码。这有助于我为客户提供卓越的支持。
更多与Java性能工具相关的内容,包括JVM调优工具、顶级Java才懂的长尾请求hack工具、使用JDK中自带的JVM分析工具解决内存溢出问题、如何使用Eclipse内存分析工具定位内存泄漏、排查死锁的4种工具、用crash工具分析Linux内核死锁的一次实战、6个Java工具轻松分析定位JVM问题、JVM源码分析之jstat工具原理完全解读、如何使用JVisualVM进行故障排查以及学会这个Thread Dump分析工具,让您秒变性能分析大师等。
jvmè°è¯å·¥å ·ç±»ä½¿ç¨ (jvisualvm.exe)
ç®æ ï¼ä½¿ç¨JDKèªå¸¦çJVMçæµå·¥å ·è°è¯å å使ç¨æ åµåå æ é®é¢ææ¥ç®è¦è¯´æï¼å¨å®é 项ç®å¼åè¿ç¨ä¸ï¼å¦æ使ç¨å¤çº¿ç¨ï¼ä½æ¯æ²¡ææ§å¶å¥½çº¿ç¨æ°éçæ åµä¸ï¼å°±ä¼åºç°å å å溢åºé®é¢ï¼å¯¼è´åè½æå¡å®æºï¼å¦æ严éå¾å¯è½å¯¼è´æå¡å¨å®æºé®é¢ãå½åºç°å å溢åºæ¶ï¼åªè½çå°ç®åçå å溢åºæ¥å¿ï¼ææ¥é®é¢æ¯«æ 头绪ï¼ä¸ç¥éåªé线ç¨åºç°é®é¢ï¼è¿æ ·å°±å¾é¾è§£å³é®é¢ï¼
è¿æ¶æ们就å¯ä»¥éè¿JDKèªå¸¦çJVMçæ§å·¥å ·æ¥çæ¯ä¸ªçº¿ç¨ççå½å¨æ以åç¸å ³æºç 追溯ï¼è¿æ ·å°±å¯ä»¥æ¸ æ¥æäºççæ¸ é®é¢åºç°å¨åªéï¼ç¶åæ ¹æ®å®é æ åµè§£å³é®é¢ï¼
éè¿ä¸å¾å¯ä»¥çå°æ¬å°æå¡ä¸ææç线ç¨åç¸å ³ç¶æï¼å¦æ线ç¨åºç°é®é¢éè¦ææ¥æ¶ï¼éè¦æ¥çå ·ä½æ§è¡çæ¹æ³ï¼é£ä¹å°±éè¦å¿«ç §æ¹å¼æ¥çï¼å ·ä½æ¹å¼å¦ä¸ï¼
ç¹å»æ½æ ·å¨ï¼ç¶åéæ©CPUæ½æ ·ï¼ç¹å»åæ¢ï¼å¨ç¹å»ä¸é¢çå¿«ç §æé®ï¼å°±å¯ä»¥è·åææææ线ç¨çä¸æ¬¡å¿«ç §ï¼ç¶åå°±å¯ä»¥çå°æ¯ä¸ªçº¿ç¨æ§è¡çæºç ï¼å ·ä½æä½å¦ä¸å¾ï¼
éè¿ä¸è¿°çæä½å³å¯æ¥ç线ç¨å ·ä½æ¶åçæºç ï¼ä»èææ¥é®é¢ï¼
ä¸é¢è¯´çæ¯æ¬å°æå¡çæµï¼ä½æ¯æå¾å¤æ åµæ¬å°æå¡æ¯ææ¥ä¸å°é®é¢çï¼åªæå¨æå¡å¨ä¸é¢æè½çåºé®é¢ï¼é£ä¹æ们就éè¦è¿ç¨è¿æ¥æå¡å¨ä¸é¢æå¡ï¼è¿è¡çæ§ï¼æ¥çå ·ä½çº¿ç¨çè¿è¡æ åµåæºç åæ
è¿ç¨é ç½®éè¦å¨å¯å¨Javaæå¡çæ¶åï¼å¨å¯å¨å½ä»¤ä¸é¢æ·»å æå®å¯å¨åæ°ï¼è¿éæä¾çå½ä»¤æ¯æ£å¸¸æ åµä¸å®æ´çå¯å¨jarå çå½ä»¤ï¼å ·ä½å½ä»¤åæ°ä¹å¾æ¸ æ¥ï¼å½ä»¤å¦ä¸ï¼
ps:portæ¯çæ§æéç端å£ï¼ä¹å°±æ¯å¯å¨æå¡æå®ç端å£ï¼ä½æ¯ç«¯å£è¦å¯¹å¤å¼æ¾ï¼ä¸è¬çæ®éæå¡ç«¯å£æ¯ä¸ä¼å¯¹å®å¼æ¾çï¼è¿ä¸ç¹éè¦æ³¨æ
hostnameæ¯å¯¹åºçæ§çipï¼æè¿éç¨çå°±æ¯æå¡å¨ipï¼å¦æè¿æ¥ä¸ä¸çæ åµä¸ï¼å¯ä»¥å°è¯ä½¿ç¨ hostname -i è·åç对åºipï¼å ·ä½æ åµå ·ä½åæ
ç¶åå¯å¨æå¡ä¹åï¼éä¸è¿ç¨å³é®æ·»å è¿ç¨ä¸»æºï¼è¾å ¥è¿ç¨ä¸»æºipåç¡®å®å³å¯ï¼ç¶åéæ©ä¸»æºipï¼å³é®æ·»å JMXè¿æ¥ï¼è¾å ¥è®¾å®ç端å£å·ï¼ç¹å»ç¡®å®å³å¯ï¼ç¶åå°±å¯ä»¥çæ§è¿ç¨æå¡å¨äºï¼æ¥ä¸æ¥çæ¥ç线ç¨çæ åµãçæ§æå¡æ åµåææ¥é®é¢ä½¿ç¨å¿«ç §ï¼å°±åä¸é¢æ¬å°çæä½ä¸æ ·äºï¼
ç»è¿ä¸è¿°æä½å°±å¯ä»¥éè¿JDKèªå¸¦ççæ§è½¯ä»¶ï¼è¿è¡çæ§Javaç¨åºçè¿è¡æ åµä»¥åæå¡å¨çè¿è¡æ åµå¦ï¼
æ¬äººåèï¼å¦æé®é¢æ¬¢è¿å¤§å®¶ææ£ï¼å ±åè¿æ¥ï¼
JVM角度看方法调用-MethodHandle篇
在我们日常编程中,方法调用主要涉及三种方式:直接调用、反射调用、以及 MethodHandle 调用。这一系列文章旨在深入探讨这三种调用方式的原理和性能分析,文章基于 JDK 1.8 版本进行阐述。在前一篇文章中,我们总结了反射调用的诸多缺点,影响性能,因此建议在热点代码中使用 MethodHandle 替代反射调用。通过与极致优化后的反射调用进行比较,我们发现 MethodHandle 在性能和直接调用方面几乎不相上下,这背后的原因是通过深入剖析 HotSpot 源码得以揭示。
接下来,我们逐一揭开 MethodHandle 的神秘面纱。首先,让我们明确,MethodHandle 是一个强类型的引用,能够直接执行,类似于反射中的 Method 类,是对目标方法的引用。它被比喻为函数指针,可以指向静态方法或实例方法、构造器或字段。MethodHandle 的执行通过 `invoke` 和 `invokeExact` 方法实现,其中 `invokeExact` 要求参数类型与底层方法的参数完全匹配,而 `invoke` 在参数类型不匹配时会做适当的调整,如包装类型。
MethodHandle 在 JDK 7 中首次引入,其相关核心类包括 MethodType 和 MethodHandles.Lookup。MethodType 用于确认方法句柄是否适配,由所指向方法的参数类型和返回类型组成;MethodHandles.Lookup 用于创建方法句柄,提供多个 API,既可以使用反射 API 中的 Method 来查找,也可以根据类、方法名以及方法句柄类型来查找。
在实际应用中,我们可以通过以下例子创建 MethodHandle 并进行方法调用。例如,尝试在外部类 MethodHandleDemo 中调用 Animal 类中的私有方法 calculation(int one, int two)。若直接使用反射调用,可能会遇到外部类无法访问 Animal 类中私有方法句柄的异常。然而,通过修改 MethodHandles.Lookup 的获取方式,改为调用 Animal 中的 getLookup() 方法,此时外部类可以正常调用私有方法,说明方法句柄的访问权限不取决于句柄的创建位置,而是取决于 Lookup 对象的创建位置。
MethodHandle 在设计时就将方法修饰符权限检查放在了通过 MethodHandles.Lookup 获取 MethodHandle 的阶段,而调用时则不会进行权限检查,避免了重复的开销。这正是 MethodHandle 相对于反射调用的一个显著优势。
接下来,我们深入探讨 MethodHandle 调用的原理。首先,我们关注 MethodHandle 的动态签名。在编译时,javac 会对 MethodHandle 的 `invoke` 方法进行动态签名处理,与普通的 `invokevirtual` 指令不同,它根据实际参数和返回类型派生符号类型描述符。此外,通过 @PolymorphicSignature 注解,可以实现多态签名处理。进一步执行时,若签名不一致,会抛出异常。
然后,我们解释了 `invoke` 方法的调用实际上不是通过 JNI(Java Native Interface)进行 native 方法调用,而是执行 `invokehandle` 指令。这一过程发生在类加载阶段,JVM 会扫描类中的所有方法,对字节码进行优化,将 `invokehandle` 指令重写为其他指令,从而实现 MethodHandle 的内联优化。这一机制同时解答了为什么 MethodHandle 虽然是一个 native 方法,却可以被 JIT(Just-In-Time)编译器进行内联优化的问题。
在解释 `invokehandle` 指令后,我们分析了如何从 `MethodHandle.invoke()` 调用到实际执行的 Java 代码中的 `java.lang.invoke.LambdaForm$MH/.invoke_MT()` 方法。通过剖析 HotSpot 源码,我们发现 `invokehandle` 指令执行时会调用 `java.lang.invoke.MethodHandleNatives::linkMethod()` 方法,该方法返回一个 `MemberName` 对象,该对象描述了一个具体的方法。通过 `MemberName` 对象,JIT 可以直接访问方法的实现,从而避免了后续的解析过程,实现了高效的调用。
我们还探讨了 `final` 关键字对 MethodHandle 性能的影响。标记为 `final` 的 MethodHandle 可以被更有效地内联,从而更接近直接调用的性能。通过分析 `MethodHandle.invoke()` 调用栈以及 HotSpot 源码,我们发现 `final` 关键字的使用与否,直接关系到后续调用栈链路能否被内联,从而影响性能。
最后,我们注意到在 `MethodHandle` 的调用链路中,某些关键步骤,如 `linkMethod`,会在第一次调用时执行,之后的调用则从常量池中获取缓存。理解这些细节有助于我们更好地优化代码性能,尤其是在涉及大量方法调用的场景中。
这究竟是为什么呢?都说JVM能实际使用的内存比-Xmx指定的少,头大
这确实是个挺奇怪的问题,特别是当最常出现的几种解释理由都被排除后,看来JVM并没有耍一些明显的小花招:
要弄清楚这个问题的第一步就是要明白这些工具的实现原理。通过标准APIs,我们可以用以下简单语句得到可使用的内存信息。
而且确实,现有检测工具底层也是用这个语句来进行检测。要解决这个问题,首先我们需要一个可重复使用的测试用例。因此,我写了下面这段代码:
这段代码通过将new int[1__]置于一个循环中来不断分配内存给程序,然后监测JVM运行期的当前可用内存。当程序监测到可用内存大小发生变化时,通过打印出Runtime.getRuntime().maxMemory()返回值来得到当前可用内存尺寸,输出类似下面语句:
实际情况也确实如预估的那样,尽管我已经给JVM预先指定分配了2G对内存,在不知道为什么在运行期有M内存不见了。你大可以把 Runtime.getRuntime().maxMemory()的返回值2,,K 除以来转换成MB,那样你将得到1,M,正好和M差M。
在成功重现了这个问题之后,我尝试用使用不同的GC算法,果然检测结果也不尽相同。
除了G1算法刚好完整使用了我预指定分配的2G之外,其余每种GC算法似乎都不同程度地丢失了一些内存。
现在我们就该看看在JVM的源代码中有没有关于这个问题的解释了。我在CollectedHeap这个类的源代码中找到了如下的解释:
我不得不说这个答案藏得有点深,但是只要你有足够的好奇心,还是不难发现的:有时候,有一块Survivor区是不被计算到可用内存中的。
明白这一点之后问题就好解决了。打开并查看GC logging 信息之后我们发现,在Serial,Parallel以及CMS算法回收过程中丢失的那些内存,尺寸刚好等于JVM从2G堆内存中划分给Survivor区内存的尺寸。例如,在上面的ParallelGC算法运行时,GC logging信息如下:
由上面的信息可以看出,Eden区被分配了,K,两个Survivor区都被分配到了,K,老年代(Old space)则被分配了1,,K。把Eden区、老年代以及一个Survivor区的尺寸求和,刚好等于2,,K,说明丢失的那M(,K)确实就是剩下的那个Survivor区。
总结而言,当JVM在运行时报告的可使用内存小于-Xmx指定的内存时,差值通常对应于一块Survivor区的大小。对于不同的GC算法,这个差值可能有所不同。
使用IDEA调试远程Java代码
在IDEA中调试远程Java代码是一项常见的需求。本文将指导你如何在IDEA中配置并实现远程调试,无需担忧步骤细节,只需跟随操作即可。
首先,打开IDEA的“Run/Debug Configurations”设置,输入远程服务器的IP和端口。红框中的信息在后续步骤会用到,暂时不用修改。
接下来,登录远程服务器,编辑tomcat的catalina.sh文件,添加配置,参数应与第一步的IP和端口对应。注意,根据远程服务器的JDK版本,可能需要使用红框中的不同输入框来获取相应的参数。
启动IDEA服务,为需要调试的代码设置断点。当远程服务器上的程序被访问时,IDEA会自动连接并进入断点模式。点击相应的启动按钮,观察控制台输出以确认连接成功。
关于配置参数的含义,绿框中的Transport选择IDEA与服务器的通信方式,通常选择Socket或Shared memory。Debugger mode建议使用Attach模式,等待IDEA连接。Host和Port则是远程服务器的地址和端口。
红框中的参数是自动生成的,与绿框设置相关,用于启动jdwp服务器。-Xrunjdwp指示JVM使用jdwp协议,transport根据上述选择进行配置,address则对应远程服务器的端口。
注意事项包括:确保远程服务器的指定端口未被占用且能被本地访问,通过telnet测试;代码部署需与本地源代码一致,以保证断点功能;在catalina.sh中的JAVA_OPTS参数添加新配置时,只需追加即可。
2024-12-23 23:58
2024-12-23 23:05
2024-12-23 22:49
2024-12-23 22:25
2024-12-23 21:59