JVM笔记

堆(Heap)和非堆(Non-heap)内存

官方:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。
JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,
所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。

内存基本概念

PermGen space:全称是Permanent Generation space,即永久代。就是说是永久保存的区域,用于存放Class和Meta信息,Class在被Load的时候被放入该区域,GC(Garbage Collection)应该不会对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。

Heap space:存放Instance。

Java Heap分为3个区,Young即新生代,Old即老生代和Permanent。

Young保存刚实例化的对象。当该区被填满时,GC会将对象移到Old区。Permanent区则负责保存反射对象。

堆内存分配

  • JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;
  • JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。
  • 默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;
  • 空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。
  • 服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。
  • 说明:如果-Xmx 不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM,不是Throwable的,无法用try…catch捕捉

JVM内存设置参数

设置项 说明
-Xms512m 表示JVM初始分配的堆内存大小为512m(JVM Heap(堆内存)最小尺寸,初始分配)
-Xmx1024m JVM最大允许分配的堆内存大小为1024m,按需分配(JVM Heap(堆内存)最大允许的尺寸,按需分配)
-XX:PermSize=512M JVM初始分配的非堆内存
-XX:MaxPermSize=1024M JVM最大允许分配的非堆内存,按需分配
-XX:NewSize 定义YOUNG段的尺寸,NewSize为JVM启动时YOUNG的内存大小
-XX:MaxNewSize MaxNewSize为最大可占用的YOUNG内存大小
-XX:SurvivorRatio 设置YOUNG代中Survivor空间和Eden空间的比例

内存申请过程

  • JVM会试图为相关Java对象在Eden中初始化一块内存区域
  • 当Eden空间足够时,内存申请结束。否则到下一步
  • JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收);释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区/OLD区
  • Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区
  • 当OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级)
    完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”

内存设置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-Xmx2000M -Xms2000M -Xmn500M
-XX:PermSize=250M -XX:MaxPermSize=250M
-Xss256K
-XX:+DisableExplicitGC
-XX:SurvivorRatio=1
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSClassUnloadingEnabled
-XX:LargePageSizeInBytes=128M
-XX:+UseFastAccessorMethods
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=60
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:+PrintClassHistogram
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-Xloggc:log/gc.log

内存回收算法

  • –XX:+UseSerialGC
  • –XX:+UseParallelGC
  • –XX:+UseParallelOldGC
  • –XX:+UseConcMarkSweepGC

Serial Collector
大部分平台或者强制 java -client 默认会使用这种。
young generation算法 = serial
old generation算法 = serial (mark-sweep-compact)
这种方法的缺点很明显, stop-the-world, 速度慢。服务器应用不推荐使用。

Parallel Collector
在linux x64上默认是这种,其他平台要加 java -server 参数才会默认选用这种。
young = parallel,多个thread同时copy
old = mark-sweep-compact = 1
优点:新生代回收更快。因为系统大部分时间做的gc都是新生代的,这样提高了throughput(cpu用于非gc时间)
缺点:当运行在8G/16G server上old generation live object太多时候pause time过长

Parallel Compact Collector (ParallelOld)
young = parallel = 2
old = parallel,分成多个独立的单元,如果单元中live object少则回收,多则跳过
优点:old old generation上性能较 parallel 方式有提高
缺点:大部分server系统old generation内存占用会达到60%-80%, 没有那么多理想的单元live object很少方便迅速回收,同时compact方面开销比起parallel并没明显减少。

Concurrent Mark-Sweep(CMS) Collector
young generation = parallel collector = 2
old = cms
同时不做 compact 操作。
优点:pause time会降低, pause敏感但CPU有空闲的场景需要建议使用策略4.
缺点:cpu占用过多,cpu密集型服务器不适合。另外碎片太多,每个object的存储都要通过链表连续跳n个地方,空间浪费问题也会增大。

内存监控方法

jmap -heap 查看java 堆(heap)使用情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
jmap -heap pid 

using thread-local object allocation.

Parallel GC with 4 thread(s) #GC 方式

Heap Configuration: #堆内存初始化配置

MinHeapFreeRatio=40 #对应jvm启动参数-XX:MinHeapFreeRatio设置JVM堆最小空闲比率(default 40)
MaxHeapFreeRatio=70 #对应jvm启动参数 -XX:MaxHeapFreeRatio设置JVM堆最大空闲比率(default 70)
MaxHeapSize=512.0MB #对应jvm启动参数-XX:MaxHeapSize=设置JVM堆的最大大小
NewSize = 1.0MB #对应jvm启动参数-XX:NewSize=设置JVM堆的‘新生代’的默认大小
MaxNewSize =4095MB #对应jvm启动参数-XX:MaxNewSize=设置JVM堆的‘新生代’的最大大小
OldSize = 4.0MB #对应jvm启动参数-XX:OldSize=<value>:设置JVM堆的‘老生代’的大小
NewRatio = 8 #对应jvm启动参数-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
SurvivorRatio = 8 #对应jvm启动参数-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值
PermSize= 16.0MB #对应jvm启动参数-XX:PermSize=<value>:设置JVM堆的‘永生代’的初始大小
MaxPermSize=64.0MB #对应jvm启动参数-XX:MaxPermSize=<value>:设置JVM堆的‘永生代’的最大大小

Heap Usage: #堆内存分步

PS Young Generation

Eden Space: #Eden区内存分布

capacity = 20381696 (19.4375MB) #Eden区总容量
used = 20370032 (19.426376342773438MB) #Eden区已使用
free = 11664 (0.0111236572265625MB) #Eden区剩余容量
99.94277218147106% used #Eden区使用比率

From Space: #其中一个Survivor区的内存分布

capacity = 8519680 (8.125MB)
used = 32768 (0.03125MB)
free = 8486912 (8.09375MB)
0.38461538461538464% used

To Space: #另一个Survivor区的内存分布

capacity = 9306112 (8.875MB)
used = 0 (0.0MB)
free = 9306112 (8.875MB)
0.0% used

PS Old Generation #当前的Old区内存分布

capacity = 366280704 (349.3125MB)
used = 322179848 (307.25464630126953MB)
free = 44100856 (42.05785369873047MB)
87.95982001825573% used

PS Perm Generation #当前的 “永生代” 内存分布

capacity = 32243712 (30.75MB)
used = 28918584 (27.57891082763672MB)
free = 3325128 (3.1710891723632812MB)
89.68751488662348% used

jmap 相关命令

jmap [ option ] pid (to connect to remote debug server)
jmap [ option ] executable core (to connect to remote debug server)
jmap [ option ] [server-id@]remote-hostname-or-IP (to connect to remote debug server)

[option] is one of:

  • [none]
  • -heap
  • -histo[:live]
  • -permstat
  • -finalizerinfo
  • -dump:[dump-options]
    [dump-options]:
  • live
  • format=b
  • file=

jmap -dump:live,format=b,file=heap.bin

参数如下:
-heap:打印jvm heap的情况
-histo:打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。
-histo:live :同上,但是只答应存活对象的情况
-permstat:打印permanent generation heap情况

  • jmap -histo:live pid>a.log
    可以观察heap中所有对象的情况(heap中所有生存的对象的情况)。包括对象数量和所占空间大小。 可以将其保存到文本中去,在一段时间后,使用文本对比工具,可以对比出GC回收了哪些对象。
    jmap -histo:live 这个命令执行,JVM会先触发gc,然后再统计信息。
  • jmap -dump:live,format=b,file=a.log pid
    JVM会将整个heap的信息dump写入到一个文件,heap如果比较大的话,就会导致这个过程比较耗时,并且执行的过程中为了保证dump的信息是可靠的,所以会暂停应用。

cache内存回收

1
2
3
echo 1 >/proc/sys/vm/drop_caches
echo 2 >/proc/sys/vm/drop_caches
echo 3 >/proc/sys/vm/drop_caches

java实现jvm监控