记一次内存溢出排查,JVM调优-jmap,jstat,jstack

本次JVM调优,是因为计算管理引擎在跑数计算时,突然报内存溢出,目前的数据量只有100W,之前200W跑数是正常的。

错误日志:

2020-12-03 08:47:15 78 [Thread-4] INFO  org.springframework.context.annotation.AnnotationConfigApplicationContext -Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@224edc67: startup date [Thu Dec 03 08:25:33 CST 2020]; root of context hierarchy
2020-12-03 08:47:15 79 [Thread-4] INFO  com.vprisk.engine.service.ContextHolder -Application Context Started
2020-12-03 08:47:15 89 [AlmEngineSystem-akka.actor.default-dispatcher-15] ERROR akka.actor.ActorSystemImpl -Uncaught error from thread [AlmEngineSystem-akka.actor.default-dispatcher-21] shutting down JVM since 'akka.jvm-exit-on-fatal-error' is enabled
java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3181) ~[na:1.8.0_211]
	at java.util.ArrayList.grow(ArrayList.java:265) ~[na:1.8.0_211]
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239) ~[na:1.8.0_211]
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231) ~[na:1.8.0_211]
	at java.util.ArrayList.add(ArrayList.java:462) ~[na:1.8.0_211]
	at com.vprisk.quantlib.time.Schedule.<init>(Schedule.java:77) ~[app-vpalm-quantlib-4.4.0.jar:na]
	at com.vprisk.quantlib.time.Schedule.<init>(Schedule.java:28) ~[app-vpalm-quantlib-4.4.0.jar:na]
	at com.vprisk.quantlib.processer.assetliability.ProcesserOfFixedRateInstrument.prepare(ProcesserOfFixedRateInstrument.java:626) ~[app-vpalm-quantlib-4.4.0.jar:na]
	at com.vprisk.quantlib.processer.assetliability.ProcesserOfFixedRateInstrument.process(ProcesserOfFixedRateInstrument.java:532) ~[app-vpalm-quantlib-4.4.0.jar:na]
	at com.vprisk.quantlib.processer.ProcesserFactory.processLeg(ProcesserFactory.java:53) ~[app-vpalm-quantlib-4.4.0.jar:na]
	at com.vprisk.quantlib.Calculator.calculate(Calculator.java:20) ~[app-vpalm-quantlib-4.4.0.jar:na]
	at com.vprisk.prophet.engine.alm.ProcessProductCurrent.calcInstruments(ProcessProductCurrent.java:180) ~[app-vpalm-engine-service-4.4.0.jar:na]
	at com.vprisk.prophet.engine.alm.ProcessProductCurrent.processCurrentPositions(ProcessProductCurrent.java:125) ~[app-vpalm-engine-service-4.4.0.jar:na]
	at com.vprisk.prophet.engine.alm.ProcessProductCurrent.execute(ProcessProductCurrent.java:119) ~[app-vpalm-engine-service-4.4.0.jar:na]
	at com.vprisk.alm.engine.actor.AlmEngineActor.executeAlm(AlmEngineActor.java:199) ~[app-vpalm-engine-service-4.4.0.jar:na]
	at com.vprisk.alm.engine.actor.AlmEngineActor.onReceive(AlmEngineActor.java:106) ~[app-vpalm-engine-service-4.4.0.jar:na]
	at akka.actor.UntypedActor$$anonfun$receive$1.applyOrElse(UntypedActor.scala:167) ~[akka-actor_2.10-2.3.9.jar:na]
	at akka.actor.Actor$class.aroundReceive(Actor.scala:465) ~[akka-actor_2.10-2.3.9.jar:na]
	at akka.actor.UntypedActor.aroundReceive(UntypedActor.scala:97) ~[akka-actor_2.10-2.3.9.jar:na]
	at akka.actor.ActorCell.receiveMessage(ActorCell.scala:516) [akka-actor_2.10-2.3.9.jar:na]
	at akka.actor.ActorCell.invoke(ActorCell.scala:487) [akka-actor_2.10-2.3.9.jar:na]
	at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:254) [akka-actor_2.10-2.3.9.jar:na]
	at akka.dispatch.Mailbox.run(Mailbox.scala:221) [akka-actor_2.10-2.3.9.jar:na]
	at akka.dispatch.Mailbox.exec(Mailbox.scala:231) [akka-actor_2.10-2.3.9.jar:na]
	at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260) [scala-library-2.10.4.jar:na]
	at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339) [scala-library-2.10.4.jar:na]
	at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979) [scala-library-2.10.4.jar:na]
	at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107) [scala-library-2.10.4.jar:na]
2020-12-03 08:47:15 92 [Thread-4] INFO  org.springframework.cache.ehcache.EhCacheManagerFactoryBean -Shutting down EhCache CacheManager
2020-12-03 08:47:15 92 [Thread-4] WARN  org.springframework.beans.factory.support.DisposableBeanAdapter -Invocation of destroy method 'close' failed on bean with name 'sqlSession': java.lang.UnsupportedOperationException: Manual close is not allowed over a Spring managed SqlSession

1.内存调整
根据报错 “Java heap space” 首先怀疑是堆内存不够,通过参数调整内存资源。这里将内存加到14G

JAVA_OPTS="-Xms14336M  -Xmx14336M -XX:PermSize=2048M -XX:MaxPermSize=2048M -XX:SurvivorRatio=6 -XX:MaxTenuringThreshold=15"

堆大小设置参数说明:

java -Xmx14336M -Xms14336M -Xmn2g –Xss128k

-Xmx14336M:设置JVM最大可用内存为14336M。

-Xms14336M:设置JVM促使内存为14336M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。

-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

java -Xmx14336M -Xms14336M -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=5

-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代),默认为2。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5

-XX:SurvivorRatio=4:设置年轻代中Eden区与每一个Survivor区的比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

-XX:MaxPermSize=16m:设置持久代大小为16m。

-XX:MaxTenuringThreshold=0:在可自动调整对象晋升老年代年龄阈值的GC中,该参数用于设置上述年龄阈值的最大值,参数值最大为15,Parallel Scavenge中默认值为15,CMS中默认值为6,G1中默认值为15。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。

更多详细说明可参考:https://www.cnblogs.com/andy-zhou/p/5327288.html


2.内存使用监控
增加了内存后还是报溢出,通过以下命令进行内存监控。

  • top
    首先我们可以使用top命令查看系统CPU的占用情况,如下是系统CPU较高的一个示例:
    这里11603 刚好是我们运行的服务,如果不确定哪个服务占用率高时,可以先通过top命令查看当前占用最多的进程 PID。

    [vp@localhost ~]$ top
    top - 10:24:21 up 282 days, 17:28, 14 users,  load average: 1.99, 1.85, 1.83
    Tasks: 345 total,   1 running, 343 sleeping,   1 stopped,   0 zombie
    %Cpu(s):  0.1 us,  0.0 sy,  0.0 ni, 99.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem : 32946688 total, 20490544 free,  9668696 used,  2787448 buff/cache
    KiB Swap:  4194300 total,  4180460 free,    13840 used. 22594404 avail Mem 
    
      PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                       
    11603 vp        20   0 19.548g 5.217g  17568 S   1300 16.6  47:48.58 java                                                                                                          
    13249 vp        20   0 16.312g 1.730g  19844 S   1.0  5.5   8:26.76 java                                                                                                          
        1 root      20   0  216132  30480   2336 S   0.0  0.1  10:08.04 systemd                                                                                                       
        2 root      20   0       0      0      0 S   0.0  0.0   0:02.70 kthreadd                                                                                                      
        3 root      20   0       0      0      0 S   0.0  0.0   0:01.68 ksoftirqd/0                                                                                                   
        7 root      rt   0       0      0      0 S   0.0  0.0   0:02.68 migration/0                                                                                                   
        8 root      20   0       0      0      0 S   0.0  0.0   0:00.00 rcu_bh                                                                                                        
        9 root      20   0       0      0      0 S   0.0  0.0   0:00.00 rcuob/0 
    

    top -Hp 11603
    查看该进行下的线程,找到CPU占用率高的线程

    [vp@localhost ~]$ top -Hp 11603
    top - 10:26:40 up 282 days, 17:30, 14 users,  load average: 2.00, 1.91, 1.85
    Threads:  57 total,   0 running,  57 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.1 us,  0.1 sy,  0.0 ni, 99.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem : 32946688 total, 20482212 free,  9677060 used,  2787416 buff/cache
    KiB Swap:  4194300 total,  4180460 free,    13840 used. 22586072 avail Mem 
    
      PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                        
    13244 vp        20   0 19.548g 5.217g  17568 S  99.7 16.6  11:40.31 java                                                                                                           
    13227 vp        20   0 19.548g 5.217g  17568 S  99.7 16.6   2:03.16 java                                                                                                           
    13194 vp        20   0 19.548g 5.217g  17568 S  99.0 16.6   0:00.00 java                                                                                                           
    13195 vp        20   0 19.548g 5.217g  17568 S  99.0 16.6   0:02.59 java                                                                                                           
    13196 vp        20   0 19.548g 5.217g  17568 S  99.0 16.6   0:00.79 java                                                                                                           
    13197 vp        20   0 19.548g 5.217g  17568 S  99.0 16.6   0:00.68 java                                                                                                           
    13198 vp        20   0 19.548g 5.217g  17568 S  99.0 16.6   0:00.76 java                                                                                                           
    13199 vp        20   0 19.548g 5.217g  17568 S  99.0 16.6   0:00.76 java                                                                                                           
    13200 vp        20   0 19.548g 5.217g  17568 S  99.0 16.6   0:00.74 java                                                                                                           
    13201 vp        20   0 19.548g 5.217g  17568 S  99.0 16.6   0:00.73 java 
    
  • jstat

    jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:

     jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
    

    1.类加载统计

     jstat -class pid
    

    2.编译统计

     jstat -compiler pid
    

    3.垃圾回收统计

     jstat -gc pid
    

    4.堆内存统计

    jstat -gccapacity pid
    

    5.新生代垃圾回收统计

    jstat -gcnew pid
    

    6.新生代内存统计

    jstat -gcnewcapacity  pid
    

    7.老年代垃圾回收统计

    在这里插入代码片
    

    8.老年代内存统计

    jstat -gcoldcapacity PID
    

    9.元数据空间统计

    jstat -gcmetacapacity PID
    

    10.总结垃圾回收统计

    jstat -gcutil PID
    

    更多说明,请参考:https://www.cnblogs.com/lizhonghua34/p/7307139.html

    以下是本次监控使用情况:
    注意:使用的jdk版本是jdk8.

    [vp@localhost ~]$ jstat -gcutil 11603  10000
      S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
      0.00   0.00  66.88  91.65  95.41  91.81     18   17.404    15  370.763  388.167
      0.00 100.00 100.00   5.70  95.45  91.91     19   17.404    16  376.879  394.283
      0.00 100.00  28.01  55.58  95.37  92.68     21   22.705    16  376.879  399.583
    100.00   0.00   0.00  80.52  95.37  92.68     22   25.635    17  376.879  402.514
    100.00   0.00   0.00  80.52  95.37  92.68     22   25.635    17  376.879  402.514
      0.00  99.99   0.00  82.32  95.39  92.68     23   33.473    18  466.279  499.752
      0.00  99.99   0.00  82.32  95.39  92.68     23   33.473    18  466.279  499.752
      0.00   0.00  25.33  88.51  95.39  92.68     23   33.473    18  552.576  586.048
      0.00   0.00 100.00  88.51  95.39  92.68     23   33.473    19  552.576  586.048
      0.00   0.00 100.00  88.51  95.39  92.68     23   33.473    19  552.576  586.048
      0.00   0.00 100.00 100.00  95.39  92.68     23   33.473    20  656.183  689.655
      0.00   0.00 100.00 100.00  95.39  92.68     23   33.473    20  656.183  689.655
      0.00   0.00  96.49 100.00  95.43  92.68     23   33.473    21  800.797  834.269
      0.00   0.00  96.49 100.00  95.43  92.68     23   33.473    21  800.797  834.269
      0.00   0.00  86.84 100.00  95.46  92.69     23   33.473    22  935.317  968.790
      0.00   0.00  86.84 100.00  95.46  92.69     23   33.473    22  935.317  968.790
    

    说明:
    S0:幸存1区当前使用比例
    S1:幸存2区当前使用比例
    E:伊甸园区使用比例
    O:老年代使用比例
    M:元数据区使用比例
    CCS:压缩使用比例
    YGC:年轻代垃圾回收次数
    FGC:老年代垃圾回收次数
    FGCT:老年代垃圾回收消耗时间
    GCT:垃圾回收消耗总时间

  • jmap
    jmap命令是一个可以输出所有内存中对象的工具;可以打印出某个java进程(使用pid)内存内的,所有对象的情况。

    命令格式

    jmap [option] <pid>
        (to connect to running process) 连接到正在运行的进程
    
    jmap [option] <executable <core>
        (to connect to a core file)     连接到核心文件
    
    jmap [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server) 连接到远程调试服务
    

    参数说明:

    >    pid:    目标进程的PID,进程编号,可以采用ps -ef | grep java 查看java进程的PID;
    >    executable:     产生core dump的java可执行程序;
    >    core:     将被打印信息的core dump文件;
    >    remote-hostname-or-IP:     远程debug服务的主机名或ip;
    >    server-id:     唯一id,假如一台主机上多个远程debug服务;
    
  1. -dump:[live,]format=b,file= 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的,假如指定live选项,那么只输出活的对象到文件.

    命令:jmap -dump:live,format=b,file=file.txt PID

  2. -finalizerinfo 打印正等候回收的对象的信息

    命令:jmap -finalizerinfo PID

  3. -heap 打印heap的概要信息,GC使用的算法,heap(堆)的配置及JVM堆内存的使用情况

    命令:jmap -heap PID

  4. -histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量

    命令:jmap -heap PID

  5. -permstat 打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来

    命令:jmap -permstat PID

  6. -F 强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效.

  7. -h | -help 打印辅助信息

  8. -J 传递参数给jmap启动的jvm

    以下是本次查看信息:
    jmap -heap PID:查看当前线程堆内存分配情况

    [vp@localhost logs]$ jmap -heap 11603
    Attaching to process ID 11603, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.211-b12
    
    using thread-local object allocation.
    Parallel GC with 13 thread(s)  ## 13线程并行GC
    
    Heap Configuration:    ##堆配置情况
       MinHeapFreeRatio         = 0                        #堆最小使用比例
       MaxHeapFreeRatio         = 100                      #堆最大使用比例
       MaxHeapSize              = 15032385536 (14336.0MB)  #堆最大空间
       NewSize                  = 5010620416 (4778.5MB)    #新生代初始化大小
       MaxNewSize               = 5010620416 (4778.5MB)    #新生代可使用最大容量大小
       OldSize                  = 10021765120 (9557.5MB)   #老生代大小
       NewRatio                 = 2                        #新生代比例
       SurvivorRatio            = 6                        #新生代与suvivor的占比
       MetaspaceSize            = 21807104 (20.796875MB)   #元数据空间初始大小
       CompressedClassSpaceSize = 1073741824 (1024.0MB)    #类指针压缩空间大小, 默认为1G
       PermSize                 = 134217728 (128.0MB)      #perm区 永久代大小
       MaxPermSize              = 134217728 (128.0MB)      #最大可分配perm区 也就是永久代大小
       MaxMetaspaceSize         = 17592186044415 MB        #元数据空间的最大值, 超过此值就会触发 GC溢出( JVM会动态地改变此值)
       G1HeapRegionSize         = 0 (0.0MB)                #区块的大小
    
    Heap Usage:                                            #堆使用情况
    PS Young Generation
    Eden Space:#伊甸区
       capacity = 4940365824 (4711.5MB) #伊甸区容量
       used     = 1982208968 (1890.3817825317383MB)#伊甸区使用
       free     = 2958156856 (2821.1182174682617MB)#伊甸区剩余容量
       40.12271638611352% used  #伊甸区使用比例
    From Space:                                            #survior1区
       capacity = 19398656 (18.5MB)
       used     = 19392656 (18.494277954101562MB)
       free     = 6000 (0.0057220458984375MB)
       99.96907002217061% used
    To Space:                                              #survior2 区
       capacity = 35127296 (33.5MB)
       used     = 0 (0.0MB)
       free     = 35127296 (33.5MB)
       0.0% used
    PS Old Generation                                      #老年代使用情况
       capacity = 10021765120 (9557.5MB)
       used     = 47347800 (45.154380798339844MB)
       free     = 9974417320 (9512.34561920166MB)
       0.47244970754213805% used
    Perm Generation:                                       #永久代使用情况
    
       capacity = 134217728 (128.0MB)                      #perm区容量
    
       used     = 47303016 (45.111671447753906MB)          #perm区已使用容量
    
       free     = 86914712 (82.8883285522461MB)            #perm区剩余容量
    
       35.24349331855774% used                             #perm区使用比例
    
    17020 interned Strings occupying 1440352 bytes.
    

    jmap -histo PID

    [vp@localhost app-cal-service]$ jmap -histo 11603|head -100
    
     num     #instances(实例 ) #bytes(字节大小)  class name
    ----------------------------------------------
       1:     264341164     6344187936  com.vprisk.quantlib.time.QuantDate
       2:         46627     1264148272  [Ljava.lang.Object;
       3:         12791      152887392  [I
       4:        403980       28221072  [C
       5:         14794       14411056  [B
       6:        361860        8684640  java.lang.String
       7:            20        3605336  [D
       8:         13768        1211584  java.lang.reflect.Method
       9:          3038        1166592  com.vprisk.alm.engine.dao.entity.CurrentPosition
      10:         31315        1002080  java.util.HashMap$Node
      11:          8753         978368  java.lang.Class
    
  • jstack

    jstack主要用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
    参数解释一下

    -F:当正常输出的请求不被响应时,强制输出线程堆栈。例如:

    Unable to attach to 64-bit process The -F option can be used when the target process is not respond
    

    -l:除堆栈外,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况

    -m:如果调用到本地方法的话,可以显示C/C++的堆栈

    更多说明,请参考:https://www.imooc.com/article/43986?block_id=tuijian_wz

可以看到, com.vprisk.quantlib.time.QuantDate,这个类的实例很多,而且根据打印的错误日志,溢出的位置有循环实例化QuantDate操作,最后根据数据排查,此处死循环。后对term数据进行判断。

在这里插入图片描述

参考博文:
https://www.cnblogs.com/andy-zhou/p/5327288.html
https://www.imooc.com/article/43986?block_id=tuijian_wz
https://www.cnblogs.com/lizhonghua34/p/7307139.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值