20210322-20210326技术周报

前言

这周主要的技术内容是分享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类型后,也没去检查,开发时间急迫,可以理解。

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

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

絮叨

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容