• [毕昇JDK] 【技术剖析】2. JVM锁bug导致G1 GC挂起问题分析和解决
    作者:宋尧飞编者按:笔者在AArch64中遇到一个G1 GC挂起,CPU利用率高达300%的案例。经过分析发现问题是由JVM的锁机制导致,该问题根因是并发编程中没有正确理解内存序导致。本文着重介绍JVM中Monitor的基本原理,同时演示了在什么情况下会触发该问题。希望通过本文的分析,读者能够了解到内存序对性能、正确性的影响,在并发编程时更加仔细。现象本案例是一个典型的弱内存模型案例,大致的现象就是AArch64平台上,业务挂死,而进程占用CPU持续维持在300%。配合top和gdb,可以看到是3个GC线程在offer_termination处陷入了死循环: 多个并行GC线程在Minor GC结束时调用offer_termination,在offer_termination中自旋等待其他并行GC线程到达该位置,才说明GC任务完成,可以终止。(关于并行任务的中止协议问题,可以参考相关论文,这里不做着重介绍。简单地说,在并行任务执行时,多个任务之间可能存在任务不均衡,所以JVM内部设计了任务均衡机制,同时必须设计任务终止的机制来保证多个任务都能完成,这里的offer_termination就是尝试终止任务)。在该案例中,部分GC线程完成自己的任务,等待其他的GC线程。此时出现挂起,很有可能是因为发生了死锁。所以问题很可能是由于那些尚未完成任务的GC线程上错误地使用锁。所以使用gdb观察了一下其他GC线程,发现其他GC线程全都阻塞在一把JVM的锁上: 而这把Monitor中的情况如下:cxq上积累了大量GC线程OnDeck记录的GC线程已经消失_owner记录的锁持有者为NULL分析在进一步分析前,首先普及一下JVM锁组件Monitor的基本原理,Monitor类主要包含4个核心字段:“Thread * volatile _owner;” 字段指向这把锁的持有线程“SplitWord _LockWord;” 字段被设计为1个机器字长,目的是为了确保操作时天然的原子性,它的最低位被设计为上锁标记位,而高位区域用来存放256字节对齐的竞争队列(cxq)地址“ParkEvent * volatile _EntryList;” 字段指向一个等待队列,跟cxq差别不大,个人理解只是为了缓解cxq的竞争压力而设计“ParkEvent * volatile _OnDeck;” 字段指向这把锁的法定继承人,同时最低位还充当了内部锁的角色接下来通过一组流程图来介绍加解锁的具体流程:上图是加锁的一个整体流程,大致分为3步:1. 首先走快速上锁流程,主要对应锁本身无人持有的最理想情况2. 接着是自旋上锁流程,这是预期将在短时间内获取锁的情况3. 最后是慢速上锁流程,申请者将会加入等待队列(cxq),然后进入睡眠,直到被唤醒后发现自己变成了法定继承者,于是进入自旋,直到完成上锁。 而且,基于性能考虑,整个上锁流程中的每一步几乎都做了“插队”的尝试:如上图代码中所示,“插队”的意思就是不经过排队(cxq),直接尝试置上锁标志位。  上图就是整个解锁流程了,显然真正的解锁操作在第二步中就已经完成了(意味着接下来时刻有“插队”现象发生),剩下的主要就是选出继承者的过程,大致分为以下几步:解锁线程首先需要将内部锁(_OnDeck)标记上锁从竞争队列(cxq)抽取所有等待者放入等待队列(_EntryList)_ EntryList取出头一个元素,写入_OnDeck的同时解除内部锁标记,这代表选出了继承者唤醒继承者当然伴随着整个解锁流程每一步的,还有对“插队”行为的处理。至此,JVM锁组件Monitor的原理就介绍到这里,再回归到问题本身,一个疑问就是_OnDeck上记录的继承者为何消失?作为继承者,既然已经消失在竞争队列和等待队列里,显然意味着它大概率已经持有锁、然后解锁走人了,所以问题很可能跟继承者选取过程有关。基于这种猜测,我们对相关代码着重进行了梳理,就发现了下图两处红框标记位置存在疑点,那就是在选继承者过程第3步中:写_ EntryList和写_OnDeck之间没有barrier来保证执行顺序,这可能出现_OnDeck先于_ EntryList写入的情况,一旦继承人提前持有锁,后果就可能非常糟糕… 这里贴了一张可能的问题场景:线程A处于解锁流程中,由于乱序,先写入了继承者同时解除内部锁线程B处于上锁流程,发现自己就是法定继承者后,立刻完成上锁线程B又迅速进入解锁流程,并从_EntryList中取出头元素(也就是线程B!)作为继承者写入_OnDeck,完成解锁走人线程A此时才更新_EntryList,然后唤醒继承者(也就是线程B!),完成解锁走人_OnDeck上的继承者线程B,实际已经完成加解锁离开,后续等待线程再也无法被唤醒正巧在社区的高版本上找到了一个相关的修复记录,这里贴出2个关键的代码片段: 上面这段代码位于慢速上锁流程,被唤醒后检查继承者是否是自己,修复后的代码在读_OnDeck时加了Load-Acquire的barrier。 上面这段代码位于解锁时选继承者流程,从_ EntryList取出头一个元素,写入_OnDeck的同时解除内部锁标记,修复后的代码在写_OnDeck时加了Store-Release的barrier。显然,围绕_OnDeck添加的这对One-way barrier可以确保:当继承者线程被唤醒时,该线程可以“看”到_EntryList已经被及时更新。总结在AArch64这种弱内存模型的平台上(关于内存序更多的知识在接下来的分享中会详细介绍),一旦涉及多线程对公共内存的每一次访问,必须反复确认是否需要通过barrier来严格保序,而且除非存在有效的依赖关系,否则barrier需要在读写端成对使用。后记如果遇到相关技术问题(包括不限于毕昇JDK),可以在论坛求助(目前毕昇JDK最新的官网http://bishengjdk.openeuler.org已经上线,可以进入官网查找所有相关资源,包括二进制下载、代码仓库、使用教学、安装、学习资料等)。毕昇JDK社区每双周周二举行技术例会,同时有一个技术交流群讨论GCC、LLVM、JDK和V8等相关编译技术,感兴趣的同学可以添加如下微信小助手,回复Compiler入群。原文转载自 openEuler-JVM 锁 bug 导致 G1 GC 挂起问题分析和解决
  • [技术干货] JVM的垃圾回收机制详解和调优
    什么是jvm呢? gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。java语言并不要求jvm有gc,也没有规定gc如何工作。不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作。在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能。有些垃圾收集专用于特殊的应用程序。比如,实时应用程序主要是为了避免垃圾收集中断,而大多数OLTP应用程序则注重整体效率。理解了应用程序的工作负荷和jvm支持的垃圾收集算法,便可以进行优化配置垃圾收集器。垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。gc首先要判断该对象是否是时候可以收集。两种常用的方法是引用计数和对象引用遍历。1.1.引用计数   引用计数存储对特定对象的所有引用数,也就是说,当应用程序创建引用以及引用超出范围时,jvm必须适当增减引用数。当某对象的引用数为0时,便可以进行垃圾收集。1.2.对象引用遍历  早期的jvm使用引用计数,现在大多数jvm采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,gc必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。下一步,gc要删除不可到达的对象。删除时,有些gc只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫做清除(sweeping)。这种方法的问题在于内存会分成好多小段,而它们不足以用于新的对象,但是组合起来却很大。因此,许多gc可以重新组织内存中的对象,并进行压缩(compact),形成可利用的空间。   为此,gc需要停止其他的活动活动。这种方法意味着所有与应用程序相关的工作停止,只有gc运行。结果,在响应期间增减了许多混杂请求。另外,更复杂的gc不断增加或同时运行以减少或者清除应用程序的中断。有的gc使用单线程完成这项工作,有的则采用多线程以增加效率。2.几种垃圾回收机制2.1.标记-清除收集器   这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。2.2.标记-压缩收集器   有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。2.3.复制收集器  这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,jvm生成的新对象则放在另一半空间中。gc运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。2.4.增量收集器   增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。2.5.分代收集器   这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。jvm生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。2.6.并发收集器   并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。2.7.并行收集器   并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多cpu机器上使用多线程技术可以显著的提高java应用程序的可扩展性。3.Sun HotSpot 1.4.1 JVM堆大小的调整   Sun HotSpot 1.4.1使用分代收集器,它把堆分为三个主要的域:新域、旧域以及永久域。Jvm生成的所有新对象放在新域中。一旦对象经历了一定数量的垃圾收集循环后,便获得使用期并进入旧域。在永久域中jvm则存储class和method对象。就配置而言,永久域是一个独立域并且不认为是堆的一部分。   下面介绍如何控制这些域的大小。可使用-Xms和-Xmx 控制整个堆的原始大小或最大值。  下面的命令是把初始大小设置为128M:   java –Xms128m   –Xmx256m为控制新域的大小,可使用-XX:NewRatio设置新域在堆中所占的比例。   下面的命令把整个堆设置成128m,新域比率设置成3,即新域与旧域比例为1:3,新域为堆的1/4或32M:   java –Xms128m –Xmx128m   –XX:NewRatio =3可使用-XX:NewSize和-XX:MaxNewsize设置新域的初始值和最大值。   下面的命令把新域的初始值和最大值设置成64m:   java –Xms256m –Xmx256m –Xmn64m   永久域默认大小为4m。运行程序时,jvm会调整永久域的大小以满足需要。每次调整时,jvm会对堆进行一次完全的垃圾收集。   使用-XX:MaxPerSize标志来增加永久域搭大小。在WebLogic Server应用程序加载较多类时,经常需要增加永久域的最大值。当jvm加载类时,永久域中的对象急剧增加,从而使jvm不断调整永久域大小。为了避免调整,可使用-XX:PerSize标志设置初始值。下面把永久域初始值设置成32m,最大值设置成64m。   java -Xms512m -Xmx512m -Xmn128m -XX:PermSize=32m -XX:MaxPermSize=64m   默认状态下,HotSpot在新域中使用复制收集器。该域一般分为三个部分。第一部分为Eden,用于生成新的对象。另两部分称为救助空间,当Eden充满时,收集器停止应用程序,把所有可到达对象复制到当前的from救助空间,一旦当前的from救助空间充满,收集器则把可到达对象复制到当前的to救助空间。From和to救助空间互换角色。维持活动的对象将在救助空间不断复制,直到它们获得使用期并转入旧域。使用-XX:SurvivorRatio可控制新域子空间的大小。   同NewRation一样,SurvivorRation规定某救助域与Eden空间的比值。比如,以下命令把新域设置成64m,Eden占32m,每个救助域各占16m:   java -Xms256m -Xmx256m -Xmn64m -XX:SurvivorRation =2如前所述,默认状态下HotSpot对新域使用复制收集器,对旧域使用标记-清除-压缩收集器。在新域中使用复制收集器有很多意义,因为应用程序生成的大部分对象是短寿命的。理想状态下,所有过渡对象在移出Eden空间时将被收集。如果能够这样的话,并且移出Eden空间的对象是长寿命的,那么理论上可以立即把它们移进旧域,避免在救助空间反复复制。但是,应用程序不能适合这种理想状态,因为它们有一小部分中长寿命的对象。最好是保持这些中长寿命的对象并放在新域中,因为复制小部分的对象总比压缩旧域廉价。为控制新域中对象的复制,可用-XX:TargetSurvivorRatio控制救助空间的比例(该值是设置救助空间的使用比例。如救助空间位1M,该值50表示可用500K)。该值是一个百分比,默认值是50。当较大的堆栈使用较低的sruvivorratio时,应增加该值到80至90,以更好利用救助空间。用-XX:maxtenuring threshold可控制上限。   为放置所有的复制全部发生以及希望对象从eden扩展到旧域,可以把MaxTenuring Threshold设置成0。设置完成后,实际上就不再使用救助空间了,因此应把SurvivorRatio设成最大值以最大化Eden空间,设置如下:   java … -XX:MaxTenuringThreshold=0 –XX:SurvivorRatio=50000 …4.BEA JRockit JVM的使用   Bea WebLogic 8.1使用的新的JVM用于Intel平台。在Bea安装完毕的目录下可以看到有一个类似于jrockit81sp1_141_03的文件夹。这就是Bea新JVM所在目录。不同于HotSpot把Java字节码编译成本地码,它预先编译成类。JRockit还提供了更细致的功能用以观察JVM的运行状态,主要是独立的GUI控制台(只能适用于使用Jrockit才能使用jrockit81sp1_141_03自带的console监控一些cpu及memory参数)或者WebLogic Server控制台。   Bea JRockit JVM支持4种垃圾收集器:   4.1.1.分代复制收集器   它与默认的分代收集器工作策略类似。对象在新域中分配,即JRockit文档中的nursery。这种收集器最适合单cpu机上小型堆操作。   4.1.2.单空间并发收集器   该收集器使用完整堆,并与背景线程共同工作。尽管这种收集器可以消除中断,但是收集器需花费较长的时间寻找死对象,而且处理应用程序时收集器经常运行。如果处理器不能应付应用程序产生的垃圾,它会中断应用程序并关闭收集。   分代并发收集器 这种收集器在护理域使用排它复制收集器,在旧域中则使用并发收集器。由于它比单空间共同发生收集器中断频繁,因此它需要较少的内存,应用程序的运行效率也较高,注意,过小的护理域可以导致大量的临时对象被扩展到旧域中。这会造成收集器超负荷运作,甚至采用排它性工作方式完成收集。   4.1.3.并行收集器   该收集器也停止其他进程的工作,但使用多线程以加速收集进程。尽管它比其他的收集器易于引起长时间的中断,但一般能更好的利用内存,程序效率也较高。   默认状态下,JRockit使用分代并发收集器。要改变收集器,可使用-Xgc:,对应四个收集器分别为gencopy,singlecon,gencon以及parallel。可使用-Xms和-Xmx设置堆的初始大小和最大值。要设置护理域,则使用-Xns:java –jrockit –Xms512m –Xmx512m –Xgc:gencon –Xns128m…尽管JRockit支持-verbose:gc开关,但它输出的信息会因收集器的不同而异。JRockit还支持memory、load和codegen的输出。   注意 :如果 使用JRockit JVM的话还可以使用WLS自带的console(C:\bea\jrockit81sp1_141_03\bin下)来监控一些数据,如cpu,memery等。要想能构监控必须在启动服务时startWeblogic.cmd中加入-Xmanagement参数。5.如何从JVM中获取信息来进行调整   -verbose.gc开关可显示gc的操作内容。打开它,可以显示最忙和最空闲收集行为发生的时间、收集前后的内存大小、收集需要的时间等。打开-xx:+ printgcdetails开关,可以详细了解gc中的变化。打开-XX: + PrintGCTimeStamps开关,可以了解这些垃圾收集发生的时间,自jvm启动以后以秒计量。最后,通过-xx: + PrintHeapAtGC开关了解堆的更详细的信息。为了了解新域的情况,可以通过-XX:=PrintTenuringDistribution开关了解获得使用期的对象权。6.Pdm系统JVM调整   6.1.服务器:前提内存1G 单CPU   可通过如下参数进行调整:-server 启用服务器模式(如果CPU多,服务器机建议使用此项)   -Xms,-Xmx一般设为同样大小。 800m   -Xmn 是将NewSize与MaxNewSize设为一致。320m   -XX:PerSize 64m   -XX:NewSize 320m 此值设大可调大新对象区,减少Full GC次数   -XX:MaxNewSize 320m   -XX:NewRato NewSize设了可不设。4   -XX: SurvivorRatio 4   -XX:userParNewGC 可用来设置并行收集   -XX:ParallelGCThreads 可用来增加并行度 4   -XXUseParallelGC 设置后可以使用并行清除收集器   -XX:UseAdaptiveSizePolicy 与上面一个联合使用效果更好,利用它可以自动优化新域大小以及救助空间比值   6.2.客户机:通过在JNLP文件中设置参数来调整客户端JVM   JNLP中参数:initial-heap-size和max-heap-size   这可以在framework的RequestManager中生成JNLP文件时加入上述参数,但是这些值是要求根据客户机的硬件状态变化的(如客户机的内存大小等)。建议这两个参数值设为客户机可用内存的60%(有待测试)。为了在动态生成JNLP时以上两个参数值能够随客户机不同而不同,可靠虑获得客户机系统信息并将这些嵌到首页index.jsp中作为连接请求的参数。
  • [技术干货] 垃圾回收你懂,Java垃圾回收你懂吗?
     摘要:在用 C 之类的编程语言时,程序员需要自己手动分配和释放内存。而 Java 不一样,它有垃圾回收器,释放内存由回收器负责。本文接下来将介绍Java垃圾收集的知识原理。java内存区域Q: Java虚拟机规范将JVM虚拟机所管理的内存分为几部分?如果是多选题,估计会给一些不在里面的,例如直接内存。A:程序计数器、java虚拟机栈、本地方法栈、方法区、堆。java对象在内存上的分配:§ GC策略Q:java使用根搜索算法来确定对象是否存货,哪些对象可以作为GC Roots?A:虚拟机栈(栈帧中的本地变量表)中的引用的对象方法区中的类静态属性引用的对象方法区中的常量引用的对象本地方法栈中JNI(Native方法)的引用对象Q: 标记清除、标记整理、复制算法哪个块?A: 复制算法较快。3个算法含义如下:Q: SerialOld用的是什么算法?A: 标记整理算法,属于处理老年代算法。各收集器的变化图如下,主要关注一下变化和区别,Q: fullGC 会发生在老年代区还是新生代区?A: 会发生在老年代区。 相反,minorGC一般发送在新生代区。新生代、老生代以及minorGC、fullGC的发生流程如下:Q: 方法区里的class对象(即类对象)什么时候会被回收?A: 所有实例都被回收、对应classLoader也被回收、class对象不会再被引用或者反射(这个咋确定?当初书里看到的,没懂)§ finalized与GCQ: 什么时候会调用对象的finalized方法A: JVM启动垃圾回收,且该对象要被回收时。finalized应该更多是规范吧,很多规范里都要求我们不要自己实现finalized了,毕竟不确定性太大。§ Java虚拟机GC参数配置突然想起来当初看java虚拟机时,把那些参数给跳过了,感觉记不住。但现在发现还是得学的,赶紧恶补一下。Q:-client和-server的对比()启动较快()性能和内存管理效率高(注意启动快和性能好不是一回事)桌面应用一般使用(), 服务器一般使用()A:(-clien)启动较快(-server)性能和内存管理效率高桌面应用一般使用(-clien), 服务器一般使用(-server)有4个跟内存相关的参数-Xmn -Xms -Xmx -Xss回答下面的问题:Q:用于配置java初始堆内存的是()A:-Xms。-X、memory、size ,内存大小Q:用于配置java堆的最大值的是()A:-Xmx。-X、memory、max最大内存Q:如果不设置,-Xms和-Xmx的大小分别默认是多少?A:不设置的话,二者相等,默认是 物理内存/64(小于1G)Q:用于配置新生代内存大小的最大值是:()你问我什么是新生代内存?就是下面这个,1个E区加2个S区的这个内存大小A:-Xmn。-X、memory、new相类似的还有-XX:NewSize 和 -XX:MaxNewSize。Q: 如何根据上面的参数计算老年代内存大小?A:Xmx的值(堆最大值)- Xmn的值(新生代内存)Q: 用于配置线程栈内存的是()? 替代的还有哪个参数?A:-Xss。 另一个是-XX:ThreadStackSize-Xss指 -X stack size有下面3个和gc相关的参数-Xnoclassgc -Xincgc -Xloggc:file回答以下问题:Q:可用于关闭针对类对象的gc功能的是()可用于减少gc的程序停顿时间的是()用于输出gc相关日志的是()A:可用于关闭针对类对象的gc功能的是(-Xnoclassgc)可用于减少gc的程序停顿时间的是(-Xincgc)用于输出gc相关日志的是(-Xloggc:file)Q:-verbose 一般是用于什么的?A:查询gc问题。-verbose:class 输出jvm载入类的相关信息,当jvm报告说找不到类或者类冲突时可此进行诊断。-verbose:gc 输出每次GC的相关情况,后面会有更详细的介绍。-verbose:jni 输出native方法调用的相关情况,一般用于诊断jni调用错误信息。Q: -XX:PermSize和-XX:MaxPermSize设置的是什么内存?A:方法区的内存。就是最开始那个图里的这个通过配置-XX:PermSize以及-XX:MaxPermSize来控制这块内存的大小,jvm在启动的时候会根据-XX:PermSize初始化分配一块连续的内存块,这样的话,如果-XX:PermSize设置过大,可能会很浪费。而Max如果设置小了,可能会omm。Q:-XX:MetaspaceSize和-XX:MaxMetaspaceSize又是什么内存?A:元数据区内存。 java8引入的,用于替代上面的perm区。无论-XX:MetaspaceSize和-XX:MaxMetaspaceSize两个参数如何设置,随着类加载越来越多不断扩容调整,直到MetaspaceSize(如果没有配置就是默认20.8m)触发FGC,上限是-XX:MaxMetaspaceSize,默认是几乎无穷大本文分享自《Java云服务开发知识学习之 java垃圾收集》,原文作者:breakDraw 。
  • [Java] jvm输出到终端用什么编码?
    System.out.println( "Hello World!天" ); 在linux终端,正常显示中文,但改变linux的locale为gbk后就显示乱码了。我还以为JVM是根据系统默认编码来输出呢!又在windows下测试,发现了更难以解释的结果:https://segmentfault.com/q/1010000037689821
  • [Java] 常用的 jvm 调优的参数
     常用的 jvm 调优的参数-Xms2g:初始化推大小为 2g;-Xmx2g:堆最大内存为 2g;-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;-XX:+PrintGC:开启打印 gc 信息;-XX:+PrintGCDetails:打印 gc 详细信息。
  • [Java] jvm 的垃圾回收器
     jvm 的垃圾回收器Serial:最早的单线程串行垃圾回收器。Serial Old:Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选预案。ParNew:是 Serial 的多线程版本。Parallel 和 ParNew 收集器类似是多线程的,但 Parallel 是吞吐量优先的收集器,可以牺牲等待时间换取系统的吞吐量。Parallel Old 是 Parallel 老生代版本,Parallel 使用的是复制的内存回收算法,Parallel Old 使用的是标记-整理的内存回收算法。CMS:一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统。G1:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项。
  • [Java] JVM的生命周期
    JVM的生命周期jvm的生命周期主要包含三个部分,虚拟机的启动,虚拟机的运行,还有虚拟机的退出。虚拟机的启动       java虚拟机的启动是通过引导类加载器(boostrap class loader)创建一个初始类来完成,这个类是由虚拟机的具体实现指定的。虚拟机的运行       执行一个java程序的时候,其实执行的是一个java虚拟机进程虚拟机的退出有如下几种情况程序正常执行退出程序在执行中遇到异常或者错误而异常终止由于操作系统出现错误导致Java虚拟机进程终止某线程调用Runtime类或者System类的exit方法,或者Runtime类的halt方法,并且Java安全管理器也允许这次exit或者halt操作除此之外,JNI规范描述用JNI Invocation API来加载或者卸载Java虚拟机时,Java虚拟机的退出情况
  • [技术干货] 2020-09-24:jvm监控系统是通过jmx做的么?
    2020-09-24:jvm监控系统是通过jmx做的么?#福大大架构师每日一题#
  • [Java] jvm的主要组成部分
    类加载器运行时数据区执行引擎本地库接口
  • [Java] jvm的垃圾回收器
    标记-清除算法标记-整理算法复制算法分代算法
  • [Java] jvm的运行时数据区
    程序计数器虚拟机栈本地方法栈
  • [Java] jvm的垃圾算法
    标记-清除算法;标记-整理算法;复制算法;分代算法。
  • [技术干货] JVM监控java.lang.management简介
        java.lang.management是JDK自带的一套工具库。通过工厂类ManagementFactory对外提供一套管理接口,提供JVM和JVM所运行操作系统的各种信息,例如:内存使用情况、GC情况和操作系统版本等。基于以上信息,可以辅助我们对定位问题或者性能调优提供数据支撑。Management同时允许从本地和远程(JMX)对正在运行的JVM进行监视和管理。MXBean简介      根据ManagementFactory的javadoc,可以看出它提供了大量的工厂方法,使得我们可以通过调用这些方法来获取JVM各组件相关的JavaBean,通过这些JavaBean就可以获取到对应组件的运行状态数据。而这些JavaBean是各个服务启动时自动注册好的,运行数据也是提前计算好的,所以我们并不会因为获取相关数据,而导致额外消耗资源进行计算。       这些JavaBean被称之为MBean或者MXBean。MBean和MXBean两者的区别在于:MXBean是一个特殊的MBean,它的数据类型被限制为开放类型,基本上是原始类型、字符串及其组合。由于这些限制,MXBean可以在不加载类的情况下使用,这使得它们甚至可以与非Java客户机进行互操作。这个特性对于我们进行通用封装是十分重要的。使用案例1:      JVM频繁的FullGC或者Java服务发生了OOM,但是JVM进程并没有自动退出。这种场景下,仅仅在操作系统层面对JVM进程进行监控是不能及时发现相关问题的。可以使用MemoryMXBean和GarbageCollectorMXBean来获取相关信息;并结合其他监控或者定时执行机制,实现告警等功能。实现代码:public class MemoryMXBeanTest {    private static volatile MemoryMXBean memoryMBean;    private static volatile List<GarbageCollectorMXBean> garbageCollectorMXBeanList;     /**     *  获取memoryMBean     */    private static void initMemoryMXBean() {        synchronized (MemoryMXBeanTest.class) {            if (memoryMBean == null) {                try {                    memoryMBean = AccessController.doPrivileged(                            new PrivilegedExceptionAction<MemoryMXBean>() {                                @Override                                public MemoryMXBean run() throws DrsException {                                    return ManagementFactory.getMemoryMXBean();                                }                            });                } catch (Exception exp) {                    log.error("", exp);                }            }        }    }     /**     * 获取GarbageCollectorMXBean     */    private static void initGarbageCollectorMXBean() {        synchronized (MemoryMXBeanTest.class) {            if (garbageCollectorMXBeanList == null) {                try {                    garbageCollectorMXBeanList = AccessController.doPrivileged(                            new PrivilegedExceptionAction<List<GarbageCollectorMXBean>>() {                                @Override                                public List<GarbageCollectorMXBean> run() throws DrsException {                                    return ManagementFactory.getGarbageCollectorMXBeans();                                }                            });                } catch (Exception exp) {                    log.error("", exp);                }            }        }    }     public static MemoryMXBean getMemoryMBean() {        if (memoryMBean == null) {            initMemoryMXBean();        }        return memoryMBean;    }     public static List<GarbageCollectorMXBean> getGCMXBeanList() {        if (garbageCollectorMXBeanList == null) {            initGarbageCollectorMXBean();        }        return garbageCollectorMXBeanList;    }     private Map<String, Object> getMemStatus() {        Map<String, Object> memStatus = new HashMap<String, Object>();        // 堆内存        memStatus.put("Heap mem Committed", getMemoryMBean().getHeapMemoryUsage().getCommitted());        memStatus.put("Heap mem Init", getMemoryMBean().getHeapMemoryUsage().getInit());        memStatus.put("Heap mem Max", getMemoryMBean().getHeapMemoryUsage().getMax());        memStatus.put("Heap mem Used", getMemoryMBean().getHeapMemoryUsage().getUsed());        // 堆内存使用率        memStatus.put("Heap mem Used Rato",                (getMemoryMBean().getHeapMemoryUsage().getUsed() * 100 / getMemoryMBean().getHeapMemoryUsage().getMax()) + "%");         // 堆外内存        memStatus.put("Non-Heap mem Committed", getMemoryMBean().getNonHeapMemoryUsage().getCommitted());        memStatus.put("Non-Heap mem Init", getMemoryMBean().getNonHeapMemoryUsage().getInit());        memStatus.put("Non-Heap mem Used", getMemoryMBean().getNonHeapMemoryUsage().getUsed());        return memStatus;    }     private Map<String, Object> getGcStatus() {        Map<String, Object> gcStatus = new HashMap<String, Object>();        if (!CollectionUtils.isEmpty(getGCMXBeanList())) {            // 不同的垃圾回收器            for (GarbageCollectorMXBean gcMXBean : getGCMXBeanList()) {                gcStatus.put(gcMXBean.getName() + "-" + Arrays.toString(gcMXBean.getMemoryPoolNames()) + "-count", gcMXBean.getCollectionCount());                gcStatus.put(gcMXBean.getName() + "-" + Arrays.toString(gcMXBean.getMemoryPoolNames()) + "-time", gcMXBean.getCollectionTime());            }        }        return gcStatus;    }     public void print() {        log.info("jvm mem status: {}", GsonUtils.toJson(getMemStatus()));         log.info("jvm gc status: {}", GsonUtils.toJson(getGcStatus()));    } }运行结果:2020-03-09 08:24:30,001 jvm mem status: {"Non-Heap mem Used":201168408,"Heap mem Used":710231248,"Heap mem Used Rato":"48%","Heap mem Committed":1467482112,"Non-Heap mem Init":2555904,"Non-Heap mem Committed":207945728,"Heap mem Init":1474297856,"Heap mem Max":1467482112}2020-03-09 08:24:30,001 jvm gc status: {"PS Scavenge-[PS Eden Space, PS Survivor Space]-count":210,"PS MarkSweep-[PS Eden Space, PS Survivor Space, PS Old Gen]-time":842,"PS MarkSweep-[PS Eden Space, PS Survivor Space, PS Old Gen]-count":4,"PS Scavenge-[PS Eden Space, PS Survivor Space]-time":2213}使用案例2      有业务功能大量并发,同时占用了大量数据库连接;从而导致连接池中可用连接长时间无空闲的话,就会导致业务持久化操作异常。下面就以使用Druid数据库连接池为例,通过Druid自带的MXbean获取连接池相关信息。PS:      1、多数Java连接池都会注册MXbean并记录重要监控数据,具体实现和数据线略有差别;      2、druid自带monitor;需要配置额外的数据库进行存储,可参见:https://github.com/alibaba/druid/wiki/druid-monitor%E8%AE%BE%E8%AE%A1      通过JMX连接JVM,可以看到DruidDataSource下有两个数据源,他们的ObjectName为:com.alibaba.druid:type=DruidDataSource,id=XXX,每个数据源的MXBean中各有一组Attribute记录相关监控项。实现代码:public class DruidMXBeanTest {     private static volatile MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();     private static void initMBeanServer() {        synchronized (DruidMXBeanTest.class) {            if (mBeanServer == null) {                try {                    mBeanServer = AccessController.doPrivileged(                            new PrivilegedExceptionAction<MBeanServer>() {                                @Override                                public MBeanServer run() throws DrsException {                                    return ManagementFactory.getPlatformMBeanServer();                                }                            });                } catch (Exception exp) {                    log.error("", exp);                }            }        }    }     private static MBeanServer getMBeanServer() {        if (mBeanServer == null) {            initMBeanServer();        }        return mBeanServer;    }     public static Set<ObjectInstance> queryMBeans(String objectName) {        try {            if (getMBeanServer() != null) {                return getMBeanServer().queryMBeans(new ObjectName(objectName), null);            }        } catch (Exception e) {            log.error("", e);        }        return null;    }     public static Map<String, Object> getAttributes(String objectName, List<String> attrNameList) {        try {            return getAttributes(new ObjectName(objectName), attrNameList);        } catch (Exception e) {            log.error("", e);        }        return null;    }     public static Map<String, Object> getAttributes(ObjectName objectName, List<String> attrNameList) {        Map<String, Object> result = new HashMap<String, Object>();        if (CollectionUtils.isEmpty(attrNameList) || getMBeanServer() == null) {            return result;        }        try {            AttributeList attributeList = getMBeanServer().getAttributes(objectName, attrNameList.toArray(new String[] {}));            for (int i = 0; i < attrNameList.size(); i++) {                result.put(attrNameList.get(i), i < attributeList.size() ? ((Attribute) attributeList.get(i)).getValue() : "");            }            return result;        } catch (Exception e) {            log.error("", e);        }        return result;    }     private Map<String, Map<String, Object>> getDruidDataSourceStatus() {        Map<String, Map<String, Object>> druidDataSourceMap = new HashMap<String, Map<String, Object>>();        try {            // 结果数量不固定并且ID每次随机,使用带通配符的ObjectName进行批量查询            Set<ObjectInstance> druidDataSourceSet = queryMBeans("com.alibaba.druid:type=DruidDataSource,id=*");            if (druidDataSourceSet != null) {                 // 一个数据源一组attribute                druidDataSourceSet.stream().forEach(objectInstance -> {                    Map<String, Object> druidDataSource = new HashMap<String, Object>();                     /**                     * 需要获取到的监控项                     *                      * druid更多相关监控项可以在com.alibaba.druid.pool.DruidDataSource中查看(注意使用时需要大写驼峰格式)                     */                    List<String> attrList = new ArrayList<String>() {                        {                            // 数据源用户名                            add("Username");                            // 最大连接池数量                            add("MaxActive");                            // 最小连接池数量                            add("MinIdle");                            // 连接池中空闲连接数                            add("PoolingCount");                            // 连接池中正在使用的连接数                            add("ActiveCount");                        }                    };                    druidDataSource = getAttributes(objectInstance.getObjectName(), attrList);                    druidDataSourceMap.put(String.valueOf(druidDataSource.get("Username")), druidDataSource);                });            }         } catch (Exception e) {            log.error("", e);        }        return druidDataSourceMap;    }     public void print() {        log.info("druid dataSource status: {}", GsonUtils.toJson(getDruidDataSourceStatus()));    }} 运行结果:2020-03-09 08:24:30,002 druid dataSource status: {"db1":{"Username":"db1","MaxActive":20,"MinIdle":5,"ActiveCount":0,"PoolingCount":5},"db2":{"Username":"db2","MaxActive":20,"MinIdle":10,"ActiveCount":1,"PoolingCount":9}}  总结很多开源中间件都会使用Management方式进行自定义扩展,记录相关运行状况数据;例如Kafka、logback等,都可以使用类似方式获取数据。参考资料:https://www.jianshu.com/p/5d854245051dhttps://baike.baidu.com/item/java.lang.management/5179868?fr=aladdinhttp://www.voidcn.com/article/p-cwctaeex-bsq.htmlhttps://zimt8.com/questions/16275207/https://docs.oracle.com/javase/tutorial/jmx/mbeans/mxbeans.htmlhttps://docs.oracle.com/javase/8/docs/api/java/lang/management/package-summary.html