JVM通过Ergonomics技术已经尽可能的让jvm不要我们去操心底层的细节,而尝试提供给我们好的服务。
但是,内存管理和gc并没有一个一劳永逸的方案。
GC有可能成为性能的瓶颈。很多时候还是要程序员自己动手去做一些调优。
以下简要介绍一些关键概念。
JVM会自动选择使用server mode还是client mode。但是我们一样可以手工设置。
java -server -client
JVM的内存管理分为堆内存(Heap Memory)和非堆内存。
Heap Memory用来存储大部分对象。
非堆内存=Code Cache+Permanent Generation。
其中Code Cache用于编译和保存本地代码(native code)的内存
Permanent Generation保存虚拟机自己的静态(refective)数据,例如类(class)和方法(method)对象。
经过统计,大部分的内存垃圾都是刚刚new出来的对象产生的,于是,Heap Memory的内存管理是分代的。
JVM的堆内存划分为Young和Tenured。Young里面有1个Eden和2个Survivor,其中一个Survivor永远为空。
按照代的从新到老的顺序:Eden,Survivor,Tenured.
Heap Memory=Eden space+ 2个Survivor space+Tenured space。
为什么Survivor有两个呢.
其中一个一直为空.
大部分对象的分配是在Eden中进行的。当进行一次gc时,可以把Eden和Survivor中的live object复制到那个空的Survivor.
可以用设置标志来查看gc的运行情况.(-verbose:gc)
这个会在console中打出如下信息
[GC 325407K->83000K(776768K), 0.2300771 secs]
GC代表这是一次Minor collection,只回收Young中的对象。
Full GC代表一次Major collection,回收Tenured。
和gc相关的性能指标
Throughput 程序真正运行时间/(程序真正运行时间+GC时间)
Pauses GC导致的程序暂停时间
常见的GC,有多种GC可供我们选择.
serial collector:
单线程GC.
parallel collector:
并行GC,多线程,如果机器是多核的比较适合.
传统的parallel collector同时只能有一个线程作Major collection.
可以引入parallel compaction,多个线程并行做Major collection.
concurrent collector:
该GC大部分的工作都是和程序并行完成的,所以Pauses的时间比较少.
GC时可以同时工作,它的原理是,将一次Major GC的标记回收过程分为多个阶段,除了初始标记阶段与最终标记阶段的执行必须暂停应用之外,其它时间应用线程都可以执行,从而减少GC过程中应用的停顿时间。
concurrent collector有可能会失败。
Concurrent mode failure: CMS回收OldGen空间的速度跟不上OldGen空间的增长速度,或者OldGen过于碎片化。JDK的较早版本存在的BUG也会引发这种类型的失败。
Promotion failure: Minor GC时,OldGen空间不足以容纳新增对象。
如何设置GC.
0 大部分情况不用调用gc,让jvm自己做好了.
1 任何时候都是让jvm先自己选择gc,当性能有问题的时候再手工调.
2 当有Pauses时间要求的时候,尝试concurrent gc.
3 当没有Pauses时间要求时,尝试parallel collector.
4 合理的设置gc的其他参数以及堆的其他参数.
5 不要迷信任何主观的想法,一定要测试,比较,修改,测试,比较,修改...的坐下去,直到性能在合理的期望中.
6 server mode时内存的划分反向了,用jsonsole可以观察到,设置参数时需要注意.
Garbage Collection
vm参数设置
实例:
Geronimo在重复装载一个工程的时候抛java.lang.OutOfMemoryError: PermGen space 异常。
JVM的PermGen内存溢出了。
改动setjavaenv.bat中的设置解决该问题。
set JAVA_OPTS="-Xms512m" "-Xmx512m" "-XX:PermSize=512m" "-XX:MaxPermSize=512m"