游戏服务器如何防止OOM异常

OOM异常是java.lang.OutOfMemoryError:Java heap space的简称,即堆内存溢出。在启动游戏服务的时候,一般会指定JVM的内存大小上限,比如:java -Xms512m -Xmx512m -Xmn256m xxx.jar :

  • -Xmx512m:设置JVM最大可用内存为512M.
  • -Xms512m:设置JVM促使内存为512M.此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存.
  • -Xmn256:设置年轻代大小为256,.整个堆大小=年轻代大小 + 年老代大小 + 持久代大小.持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
    如果服务器在运行过程中,内存的使用量超过上面配置的JVM最大可用内存,就会出现OOM的异常。那么如果防止这种异常出现呢。
    首先,我们先明确一下引起这种异常的条件:
  1. 业务量过多,导致内存中数据太多,配置的JVM堆内存太小
  2. 就是传说中的内存泄漏,比如服务器中有些缓存只增不减,一直到内存不足。
  3. 某个资源被无限使用,比如线程被无限创建。

所以为了防止游戏服务器上线之后,由于用户数量越来越多,操作越来越频繁产生OOM异常,在游戏服务器上线之前一定要做充足的压力测试。在压力测试的时候,可以通过观察内存对象数量的变化,查看是否有内存泄漏。如果没有内存泄漏,就要计算一下配置的JVM堆适应的用户上限,这样服务器上线之后,可以预估同时在线人数,配置相应的JVM堆大小。

根据内存使用情况查看

  • 查看JVM配置与使用情况

如果Java环境是JDK8或以下版本,使用命令:jmap -heap pid,pid表示要查看的进程号,例如:jmap -heap 2334
如果Java环境是JDK8以上版本,使用命令:jhsdb jmap --heap --pid yourPId,例如:jhsdb jmap --heap --pid 2343
这两个命令会打印当前进程的内存配置与使用详情,如下面所示:(我的环境是jdk10)

Attaching to process ID 17674, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 10.0.1+10

using thread-local object allocation.
Garbage-First (G1) GC with 4 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 1073741824 (1024.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 643825664 (614.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 1048576 (1.0MB)

Heap Usage:
G1 Heap:
   regions  = 1024
   capacity = 1073741824 (1024.0MB)
   used     = 295734056 (282.03397369384766MB)
   free     = 778007768 (741.9660263061523MB)
   27.54238024353981% used
G1 Young Generation:
Eden Space:
   regions  = 33
   capacity = 253755392 (242.0MB)
   used     = 34603008 (33.0MB)
   free     = 219152384 (209.0MB)
   13.636363636363637% used
Survivor Space:
   regions  = 1
   capacity = 1048576 (1.0MB)
   used     = 1048576 (1.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 249
   capacity = 284164096 (271.0MB)
   used     = 259033896 (247.03397369384766MB)
   free     = 25130200 (23.966026306152344MB)
   91.15644785750835% used

37933 interned Strings occupying 2777136 bytes.

由此可以判断JVM内存是否配置过小,或内存分配不良

  • 查看是否有内存泄漏

查看是否有内存泄漏,就是要找到哪个对象在内存中最大或数量最多,找到这个对象之后,就需要在代码中查看都对这个对象做了哪些操作,是否是只有增加没有减少。
可以使用命令:jmap -histo:live pid | more,其中,pid是要查看的进程id,它输出如下所示:

num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:       1326424       57739472  [B (java.base@10.0.1)
   2:       1321229       31709496  java.lang.String (java.base@10.0.1)
   3:        305884       24788952  [Ljava.util.HashMap$Node; (java.base@10.0.1)
   4:        346683       16640784  java.util.HashMap (java.base@10.0.1)
   5:        510859       16347488  java.util.HashMap$Node (java.base@10.0.1)
   6:        186800       12360448  [Ljava.lang.Object; (java.base@10.0.1)
   7:        262046        6289104  com.xinyue.util.BadWordsFilter$Node
   8:        172722        4145328  java.util.ArrayList (java.base@10.0.1)
   9:         86594        3463760  com.xinyue.common.dataconfig.OldPlayer
  10:        139967        3359208  com.google.common.collect.ImmutableMapEntry
  11:          4799        2346088  [I (java.base@10.0.1)
  12:           414        1855664  [Lcom.google.common.collect.ImmutableMapEntry;
  13:          4730        1730512  [C (java.base@10.0.1)
  14:         11874        1444664  java.lang.Class (java.base@10.0.1)
  15:         56537        1356888  com.google.common.collect.ImmutableMapEntry$NonTerminalImmutableMapEntry
  16:         81115        1297840  com.alibaba.fastjson.JSONObject
  17:          8951        1288944  com.xinyue.common.dataconfig.EnemyInfo
  18:         51979        1247496  com.xinyue.common.dataconfig.ForbiddenName
  19:         51103        1226472  com.alibaba.fastjson.JSONArray
  20:         34108        1091456  java.util.concurrent.ConcurrentHashMap$Node (java.base@10.0.1)
--More--

从这里可以看到每个对象的实例数量,占用内存大小。上面的例子中,属于业务代码占用内存最大的数是:com.xinyue.util.BadWordsFilter$Node,只要查看一下自己的代码,看看这里发生了什么操作,就可以知道是否会产生内存泄漏了。

使用监控工具

另外在测试环境下,可以使用监控工具,时时查看JVM使用情况,比较有名的一个是JProfiler,JProfiler是由ej-technologies GmbH公司开发的一款性能瓶颈分析工具(该公司还开发部署工具)。
其特点:

  • 使用方便
  • 界面操作友好
  • 对被分析的应用影响小
  • CPU,Thread,Memory分析功能尤其强大
  • 支持对jdbc,noSql, jsp, servlet, socket等进行分析
  • 支持多种模式(离线,在线)的分析
  • 跨平台,支持 Windows MacOS,Linux,FreeBSD等。

这个工具的详细用法由于篇幅所限,以后再说,有兴趣的朋友可以先研究一下。

原文地址:https://www.cnblogs.com/wgslucky/p/11764100.html