前言

这周主要的技术内容是分享JVM的基础知识和一些生产事故案例

JVM

先问一个问题:在公司,你负责的项目JVM的参数数多少?比如堆的大小MaxHeapSize,新生代的大小,晋升年龄,垃圾收集器是什么?

如果你不清楚,什么看都没看到过,甚至怎么查看JVM的参数都不清楚,那你就看对文章了。

首先,我们知道JVM的参数,无非就是读写。接下来,我们先看查看JVM的命令有哪些?

怎么查看JVM参数

命令一:java -XshowSettings:vm -version

aaron@aarondeMBP ~ % java -XshowSettings:vm -version
VM settings:
    Max. Heap Size (Estimated): 3.56G
    Ergonomics Machine Class: server
    Using VM: Java HotSpot(TM) 64-Bit Server VM

java version "1.8.0_281"
Java(TM) SE Runtime Environment (build 1.8.0_281-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.281-b09, mixed mode)
aaron@aarondeMBP ~ % 

这个命令,相对来说,展示的JVM配置信息比较少:只有最大的堆大小,jdk的版本,jdk的运行模式。这个命令,在生产上用得比较少,你就了解一下就好。

命令二:jcmd
可以用jcmd -l命令查询正在运行的程序的实时JVM参数

[work(tanghailin)@tjtx167-48-41 ~]$ jcmd -l
1041671 sun.tools.jcmd.JCmd -l
1467 com.bj58.spat.scf.server.bootstrap.Main -Dscf.service.name=testdemo //项目名称
[work(tanghailin)@tjtx167-48-41 ~]$ jcmd 1467 VM.flags
1467:
-XX:CICompilerCount=4 //最大并行编译数
-XX:CMSInitiatingOccupancyFraction=80 //当老年代空间被占用80%时,就需要进行垃圾回收
-XX:InitialHeapSize=2147483648 // -Xms=2G
-XX:MaxHeapSize=2147483648// -Xmx=2G
-XX:MaxNewSize=1073741824 //新生代=1G
-XX:MaxTenuringThreshold=6 //晋升年龄=6
-XX:MinHeapDeltaBytes=196608 //
-XX:NewSize=1073741824 //新生代=1G
-XX:OldPLABSize=16 
-XX:OldSize=1073741824 //老年代=1G
-XX:ParallelGCThreads=20 //并发线程数=20
-XX:ThreadStackSize=1024 //等价于-Xss,堆栈的大小
-XX:+UseCMSCompactAtFullCollection //在发生FULL GC时,进行压缩  https://blog.csdn.net/weixin_33978044/article/details/94321406
-XX:+UseCompressedClassPointers //压缩对象中的类型指针KlassPoniter
-XX:+UseCompressedOops //oop:ordinary object pointer 压缩对象的。一般UseCompressedOops和UseCompressedClassPointers是一起使用的。二者都是为了提高内存的利用率
-XX:+UseConcMarkSweepGC //使用CMS垃圾收集器,收集老年代
-XX:+UseFastUnorderedTimeStamps 
-XX:+UseParNewGC //使用并发NewGC垃圾收集器收集新生代

jcmd命令用法:

  • 第一步:先查出对应的进程号pid。
  • 第二步:jcmd pid VM.flags 来查对应进程的JVM参数配置

你看,上面的参数,明显多了很多,但不是全部JVM参数。

如果你想要查看全部的JVM参数,可以使用命令

java -XX:+PrintFlagsFinal -version
注意一下:
=代表初始参数值;
:=代表修改后的参数值

aaron@aarondeMBP ~ % java -XX:+PrintFlagsFinal -version
[Global flags]
     intx ActiveProcessorCount                      = -1                                  {product}
    uintx AdaptiveSizeDecrementScaleFactor          = 4                                   {product}
    uintx AdaptiveSizeMajorGCDecayTimeScale         = 10                                                       {product}
                                    {product}

    uintx InitialHeapSize                          := 268435456                           {product}
    uintx InitialRAMFraction                        = 64                                  {product}
   double InitialRAMPercentage                      = 1.562500                            {product}
    uintx InitialSurvivorRatio                      = 8                                   {product}
    uintx InitialTenuringThreshold                  = 7                                   {product}
    uintx InitiatingHeapOccupancyPercent            = 45                                  {product}
     bool Inline                                    = true                                {product}
    ccstr InlineDataFile                            =                                     {product}
     intx InlineSmallCode                           = 2000                                {pd product}
     bool InlineSynchronizedMethods                 = true                                {C1 product}
     bool InsertMemBarAfterArraycopy                = true                                {C2 product}
     intx InteriorEntryAlignment                    = 16                                  {C2 pd product}
     intx InterpreterProfilePercentage              = 33                                  {product}
     bool JNIDetachReleasesMonitors                 = true                                {product}

    uintx MaxHeapSize                              := 4294967296                          {product}

                                  {product}
     intx SelfDestructTimer                         = 0                                   {product}
    uintx SharedBaseAddress                         = 34359738368                         {product}
    ccstr SharedClassListFile                       =                                     {product}
    uintx SharedMiscCodeSize                        = 122880                              {product}
    uintx SharedMiscDataSize                        = 4194304                             {product}
    uintx SharedReadOnlySize                        = 16777216                            {product}
    uintx SharedReadWriteSize                       = 16777216                            {product}
     bool ShowMessageBoxOnError                     = false                               {product}
     intx SoftRefLRUPolicyMSPerMB                   = 1000                                {product}
     bool SpecialEncodeISOArray                     = true                                {C2 product}
     bool SplitIfBlocks                             = true                                {C2 product}
     intx StackRedPages                             = 1                                   {pd product}
     intx StackShadowPages                          = 20                                  {pd product}
     bool StackTraceInThrowable                     = true                                {product}
     intx StackYellowPages                          = 2                                   {pd product}
     bool StartAttachListener                       = false                               {product}
     intx StarvationMonitorInterval                 = 200                                 {product}
     bool StressLdcRewrite                          = false                               {product}
    uintx StringDeduplicationAgeThreshold           = 3                                   {product}
    uintx StringTableSize                           = 60013                               {product}
     bool SuppressFatalErrorMessage                 = false                               {product}
    uintx SurvivorPadding                           = 3                                   {product}
    uintx SurvivorRatio                             = 8                                   {product}
     intx SuspendRetryCount                         = 50                                  {product}
     intx SuspendRetryDelay                         = 5                                   {product}

     bool UseCompressedClassPointers               := true                                {lp64_product}
     bool UseCompressedOops                        := true                                {lp64_product}
                                    {C1 product}
     intx ValueMapMaxLoopSize                       = 8      
java version "1.8.0_281"
Java(TM) SE Runtime Environment (build 1.8.0_281-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.281-b09, mixed mode)
aaron@aarondeMBP ~ % 

上面三个命令,我比较常用的是第二个命令jcmd,因为这个命令提供的参数足以让我排查JVM问题。第三个命令信息太多了,是在第二个命令提供的参数信息不够时,再使用的。

怎么修改JVM的参数

知道怎么读后,就要开始修改JVM参数,然后进行调优。

  • 方式一:就是启动参数(生产中不常用)
  • 方式二:配置catalina.sh文件

    在catalina.sh文件中增加:
    JAVA_OPTS=-Xms64m -Xmx256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m -XX:+HeapDumpOnOutOfMemoryError

  • XX:HeapDumpPath=./

根据不同的需要配置不同的参数即可。参数的具体含义和作用,这里就不展开了。

在这里需要解答2个疑惑点:

  • 1.如果在此设置了jvm最大堆内存限制为2G,那么是该tomcat中所有war分享2G,还是每个war都可以独立有2G?
  • 2.tomcat本身也是一个运行在jvm上的程序,既然如此,它自己本身的jvm参数要如何调整?

问题1:是所有war包共享2G。问题2:与war包共享2G,因为都在同一个容器中。

知道JVM参数的读写后,应该如何进行调优

说到调优,肯定得有参照物对吧。否则怎么知道调整JVM参数后,是好还是坏呢。

其实就3个参照物。延迟,吞吐量,内存占用。

内存占用:程序正常运行需要的内存大小。

延迟:由于垃圾收集而引起的程序停顿时间。

吞吐量:用户程序运行时间占用户程序和垃圾收集占用总时间的比值。

当然,和CAP原则一样,同时满足一个程序内存占用小、延迟低、高吞吐量是不可能的。就是三个参照物,不可能同时满足的,必须牺牲其中一个。

那用什么来看这三个参照物呢?网上有一堆工具,比如MAT,VisualVM。

本人比较懒,所以都是直接用在线工具的。比如: http://gceasy.io/

因为我设置的是,有OOM时,会生成一个dump文件,然后我下载文件到本地,用工具查看即可。

但是如果你的项目没设置dump文件的话,发生OOM异常时。记得先摘流量,再去dump,尽可能减少对业务的影响。

因为业务的不同,导每个项目用的垃圾收集器也是不同的。

这里提供2个GC的指导原则。

Parallel GC调优的指导原则

1、除非确定,否则不要设置最大堆内存
2、优先设置吞吐量目标
3、如果吞吐量目标达不到,调大最大内存,不能让OS使用Swap,如果仍然达不到,降低目标

4、如果吞吐量达到,GC的时间太长,设置停顿时间的目标

CMS GC 调优的指导原则

直接参考:CMS GC调优指导原则

好了,这周的JVM的技术周报,大概就是这样子。就是JVM的读写,然后根据不同的指导原则进行调优,根据参照物进行调就好。调优时,用控制变量法来调就好,别一次改变多个值,否则你也不清楚究竟是哪个值优化的。

生产事故

JVM生产事故

JVM生产事故

这个报警,有点厉害啊,都直接告诉我原因。

新生代内存被占满,被置换到老生代。

看到这个,就立马想起了空间分配担保规则。

当时我看完数据视图和堆文件的分析后,根据引用树和可疑报告,定位到了一个项目中的本地消息队列。(相关图片,因涉及项目机密,不太方便透露)

造成JVM新生代内存被占满,被置换到老生代的原因如下:

上游系统作为生产者生产消息,平均700条/S,高峰时期高达1300条/s。我们这边的系统是下游系统,作为消费者,主动去拉取消息,进行消费。拉取的消息对象就有很多字段,从而使得对象本身很大。而且,消费者系统是在本地建立了一个本地消息队列,正常情况下,每秒700条,是可以正常消费完毕的,此时消费者的消费速度大于生产者的生产速度。从而不会导致消息堆积。而当流量大时,消息高达每秒1300条,此时消费速度小于生产速度。从而造成本地消息队列的消息堆积,又因为对象本身比较大。从而使得新生代的内存很快满了,满了后,因为还有更多的新消息被主动拉取到下游系统,且消息没被及时消费,因此对象还不能被及时回收掉。此时新生代已满,且对象不能被回收掉,从而使得对象直接进入老年代。从而使得老年代的空间很快满了。最后是怎么解决的呢?简单粗暴的横向扩张,说人话就是加机器。

我就简单总结一下:其实遇到JVM。一般排查思路如下:首先你肯定会收到OOM的报警,接着你需要通过分析dump文件,进行定位OOM是内存泄露,还是内存溢出原因。确定这个原因后,需要通过一些可视化的工具,比如MAT这些工具,通过分析引用树,可疑报告来定位到具体的业务代码。然后定位到业务代码是什么问题,根据不同的问题来进行设计方案即可。

Long和long类型引发的的订单不能支付问题

先上图哈
订单事故

bug的现象:就是订单无法进行支付
严重性:这是非常严重的bug,该收钱的时候,竟然不能收。

其实,你一看代码,就能发现清楚是什么问题了。Long类型,可不可以用==和!=比较。可以是可以。但只限于-128-127之间。超出这个范围的数都是一个新的Long对象。

拓展一下:在Java中,只有整形的包装类才有缓存,浮点型都没缓存。简单点说,就是:Short,Integer,Long才有缓存,并且范围都是-128-127。

Integer类
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
Long类
public static Long valueOf(long l) {
    final int offset = 128;
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}
Short
public static Short valueOf(short s) {
    final int offset = 128;
    int sAsInt = s;
    if (sAsInt >= -128 && sAsInt <= 127) { // must cache
        return ShortCache.cache[sAsInt + offset];
    }
    return new Short(s);
}

现在,我们既然知道问题的所在了。那肯定知道如何解决。直接用equals比较即可。

那我们再去关心一个事儿,为什么开发人员会犯这个错误呢?

其实,开发人员并没有犯!=和==的比较错误。一开始其实updateOrderState的buserId其实是long基础类型,因此完全是可以用!=和==来进行比较的。只不过后来,把buserId修改为Long包装类型了。修改Long类型后,也没去检查,开发时间急迫,可以理解。

总结一下:一,接口之前定义好了,后续就不应该在这个接口上进行直接修改。你可以新开一个接口,然后兼容老接口。但绝不能在老接口上直接修改,即使是数据类型的修改,也是不应该的。因为你也不清楚这个接口,谁在用,以及这个接口的内部逻辑是如何的。二,包装类型和基础类型的区别和底层,我们也应该掌握好。这就是为什么要知其然知其所以然的原因。

这周的技术周报就是这样子。拜拜,下周见。

絮叨

非常感谢你能看到这里,如果觉得文章写得不错 求关注 求点赞 求分享 (对我非常非常有用)。
如果你觉得文章有待提高,我十分期待你对我的建议,求留言。
如果你希望看到什么内容,我十分期待你的留言。
各位的捧场和支持,是我创作的最大动力!