美文网首页
20210322-20210326技术周报

20210322-20210326技术周报

作者: 别拿爱情当饭吃 | 来源:发表于2021-03-27 23:08 被阅读0次

    前言

    这周主要的技术内容是分享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/

    image

    因为我设置的是,有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类型后,也没去检查,开发时间急迫,可以理解。

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

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

    絮叨

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

    相关文章

      网友评论

          本文标题:20210322-20210326技术周报

          本文链接:https://www.haomeiwen.com/subject/grsuhltx.html