内存结构
JVM在启动后内存分成如下几个区域:
线程私有部分:
- PC, Program Counter Register, 当前执行指令地址,用于线程切换后恢复
- JVM Stack, 调用方法时存放栈帧,局部变量存放地,栈帧大小在编译器就确定,记录在class文件中method_info的属性表中,Stack默认大小为1M,可通过启动参数-Xss或new Thread时传递参数指定;栈内部不足时会抛StackOverFlowException, jstack pid可看出线程栈
- Native Method Stack,本地方法区,调用Native方法的Stack, HotSpot将其合并到了JVM Stack中
线程共享部分:
- Heap, 堆区,空间最大的区域,new Object对存放到此处,通过-Xms指定最小值,-Xmx指定最大值
- Method Area,方法区,存放类加载后的元信息即Class Object,方法区包含运行时常量池,其中存储各种基本数据类型及String类型的常量以及类方法、字段的符号引用,方法区默认64M,-XX:PermSize指定大小,-XX:MaxPermSize指定最大值
线程共享部分在多线程环境下需保证线程安全,当此部分内存不足时会抛OutOfMemoryException,GC也是在此部分上进行。
ThreadLocal数据在Heap区,每个线程在Heap区分配一块地方。
GC
GC作用在Heap区和Method Aera, GC 线程是守护线程,自动回收不再存活对象占用内存,为便于GC, 将线程共享部分划分为:
Heap区划分为Young Generation和Old Generation:
- Young Generation,新生代,对象从新生代消失为Minor/Young GC
- Old Generation, 老年代,对象从老年代消失为Major/Full GC
Method Area对应永久代:
- Permanet Generation,永久代GC也是Major GC
对不通的分代,根据数据生命周期不同,采用的算法也不同。
GC介绍可参看:http://www.importnew.com/1993.html
Minor GC
Minor GC运行在新生代,新生代被划分成三个部分:
- Eden
- Survivor
- Survivor1(S#1)
- Survivor2(S#2)
两个Survivor是因为在新生代进行GC时采用了复制算法,将一个Survivor区直接copy至另外一个,两倍空间,速度快,无内存碎片,此种算法适用于频繁创建、生命周期短的对象。
算法步骤:
- 绝大多数刚刚被创建的对象会存放在Eden
- Eden满后执行了一次GC,存活的对象被移动到其中一个Survivor
- 此后,在Eden执行GC之后,存活的对象会被堆积在同一个Survivor
- 当一个Survivor饱和,还在存活的对象会被移动到另一个Survivor,然后清空已经饱和的那个Survivor,同一时刻只有一个Survivor起作用,另一个Survivor为空
经过多次Young GC, Survivor中达到一定年龄的对象会进入老年代,对象年龄使用经过GC次数表示,记录在对象头中。
大多数对象生命周期较短,会在Young Generation消失,因此Minor GC发生频率较高,注重时间效率,空间换时间,采用复制-回收算法
Major GC
Major GC会发生在Old Generation和Permanent Generation,一般是在老年代或永久代空间满时发生,相对Youg GC执行频率低,一次执行时间久,因为存储的是大对象,且Old Generation空间一般比Young Generation大。
Major GC一般采用Mark-Sweep-Compact,即标记存活对象,清理无用对象,此时会产生内存碎片,压缩碎片整理,将存活对象聚集在一起,此种算法时间稍长,因此适用于生命周期长的对象。
Major GC具体执行的过程由配置的算法决定,任何一种算法都会导致SWT(Stop-The-Word),目前JDK提供以下几种算法:
- Serial GC,-XX:+UseSerialGC
- Parallel GC, -XX:+UseParallelGC
- Parallel Old GC (Parallel Compacting GC), -XX:+UseParallelOldGC
- Concurrent Mark & Sweep GC (or “CMS”),-XX:+UseConcMarkSweepGC
- Garbage First (G1) GC
目前一般采用CMS算法,CMS是一种低延迟GC,SWT时间很短,但是需要占用更多内存和CPU,默认不支持压缩。
标记垃圾对象
查找垃圾对象主要有两种方式:
引用计数法
在一个对象上存储其被引用次数,如果为0则是废弃对象,缺点是无法检测循环引用(A->B->A)
根搜索法
从GC Root集合作为起点向下搜索,可达对象为存活对象,不可达对象则为垃圾对象。GC Root一般由Method Area、JVM Stack中引用组成,HotSpot使用跟搜索算法。
GC配置
一般会根据实际物理资源配置JVM启动参数决定内存各个分区的大小,当然也可采用默认配置,启动配置参数见上图。
JVM启动后可以通过jinfo pid 查看启动参数,也可通过jmap -heap pid查看实际分配的各部分比例大小。
这里有一份关键业务系统JVM参数推荐,当然也包含GC相关:
http://calvin1978.blogcn.com/articles/jvmoption-2.html
GC监控
GC可能会影响程序性能,因此需要关注GC具体执行情况,两种方式可监控GC执行情况:
- 记录GC明细
- 统计GC次数及时间
记录GC明细
GC是GC线程的行为,因此可在执行时通过日志形式记录GC明细, 在JVM启动参数中添加:
-verbos:gc
-Xloggc:/xxx/gc.log
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-XX:+PrintGCDateStamps
统计GC次数及时间
jstat可统计GC执行情况,当然jstat也是根据GC执行时记录的明细汇总得到的, jstat -gc pid frequency count 会显示各分区的大小、使用情况以及GC执行次数和总时间。
GC监控参考:http://www.importnew.com/2057.html
GC调优
GC优化永远是最后一件事情,只有真正确认GC有问题才进行优化,盲目优化可能适得其反。以下文章详细介绍了何时该进行GC优化以及如何进行GC优化: