游戏服务器如何防止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的异常。那么如果防止这种异常出现呢。
首先,我们先明确一下引起这种异常的条件:
- 业务量过多,导致内存中数据太多,配置的JVM堆内存太小
- 就是传说中的内存泄漏,比如服务器中有些缓存只增不减,一直到内存不足。
- 某个资源被无限使用,比如线程被无限创建。
所以为了防止游戏服务器上线之后,由于用户数量越来越多,操作越来越频繁产生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等。
这个工具的详细用法由于篇幅所限,以后再说,有兴趣的朋友可以先研究一下。