Java内存与垃圾回收总结备忘

引入

1
2
3
4
5
6
[root@localhost sync]# free -m
total used free shared buffers cached
Mem: 32094 29292 2802 0 274 1647
-/+ buffers/cache: 27370 4724
Swap: 0 0 0
<!-- more -->

这是一台Java同步服务器的内存占用情况,使用jmap查看堆上信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@localhost sync]# jmap -histo 7840

num #instances #bytes class name
----------------------------------------------

1: 77399077 5156382528 [C
2: 41662587 3924901448 [I
3: 33678441 2424847752 java.util.LinkedHashMap$Entry
4: 64361432 2059565824 java.lang.String
5: 18967113 1669105944 java.util.regex.Matcher
6: 9996594 1534988024 [Ljava.lang.Object;
7: 7225435 1235477264 [Ljava.util.HashMap$Entry;
8: 11458421 1074750168 [B
·
·
·
Total 332044853 22585874416

上面这个输出中有好几个问题,暂时先不管。
看下Java的配置:Xms26g -Xmx26g -Xmn12g -XX:PermSize=1024M -Xss2M
实际占用的29G内存中Java heap占了22G,
再看下PS的结果:ps -aux|sort -k 1 -r |less

1
2
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root 7840 22.3 82.5 45956536 27124588 ? Sl May19 380:04 java

可以看到Java 进程占了27G。
一般来说同步程序并需要这么多的内存,查看GC情况:

1
2
3
[root@localhost sync]# jstat  -gc 7840
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
0.0 65536.0 0.0 65536.0 13148160.0 9207808.0 14049280.0 10819203.6 1048576.0 18093.7 125 25.202 0 0.000 25.202

问题1:

OLD区已经快满了(10/14),
回顾下对象是如何进入old区的:

  • 长期存活的对象将进入老年代,对象在Eden区中经过第一次MinorGC之后进入Survivor区中,每经过一次minorgc如果没有被回收,默认经过15次gc还未被回收就会进入old区
  • 大对象直接进入老年代,虚拟机提供了一个-XX:PretenureSize参数,大于这个值的对象直接在老年代中分配。避免了再Eden区和Survivor区中发生大量的内存复制(该参数只对几个收集器起作用)。
    JVM内存回收过程

    1、对象在Eden区完成内存分配
    2、当Eden区满了,再创建对象,会因为申请不到空间,触发minorGC,进行young(eden+1survivor)区的垃圾回收
    3、minorGC时,Eden不能被回收的对象被放入到空的survivor(Eden肯定会被清空),另一个survivor里不能被GC回收的对象也会被放入这个survivor,始终保证一个survivor是空的
    4、当做第3步的时候,如果发现survivor满了,则这些对象被copy到old区,或者survivor并没有满,但是有些对象已经足够Old,也被放入Old区 XX:MaxTenuringThreshold
    5、当Old区被放满的之后,进行fullGC

问题2

heap中char数组占用内存过多,但是程序并有显示生成char对象。
Why my Java heap is with so many char

811k of char[] correspond to 800k of Strings, so yes, you have too many Strings.
If you’re having a memory leak (judging by the tag), then it’s most probably are strings in a HashMap. You have 800k instances of strings, 200k instances of hash entries, 30k of maps. This probably means that your strings are kept in this cache and are not removed. Global map is a frequent cause of memory leaks, one needs to make sure that they are evicted from this cache.

Since all these values are not being GC-ed, you could try analyzing an object graph to see what’s holding them with a tool like JProfiler.

Why does the profiler show large numbers of char[] instances when I am not creating any?

Take a look at the String source code. The String object itself contains a cached hash code, a count of the number of characters (again, for optimisation purposes), an offset (since a String.substr()points to the original string data) and the character array, which contains the actual string data. Hence your metrics showing that String consumes relatively little, but the majority of memory is taken by the underlying character arrays.

String is the perfect example. It contains a handful of primitive fields, plus the char[]. The char[]accounts for the vast majority of the memory usage. The shallow size of String is very small, but it’s retained size is much larger, since that includes the char[].
大概就是说尽管没有直接使用char数组,但是对String的频繁使用也会造成这个问题。

参考链接:

Java内存与垃圾回收调优