参考链接:
http://www.iteye.com/topic/1119491
为了分代垃圾回收,Java堆内存分为3代:新生代,老年代和永久代。
新的对象实例会优先分配在新生代,在经历几次Minor GC后(默认15次),还存活的会被移至老年代(某些大对象会直接在老年代分配)。
永久代是否执行GC,取决于采用的JVM。
Minor GC发生在新生代,当Eden区没有足够空间时,会发起一次Minor GC,将Eden区中的存活对象移至Survivor区。Major GC发生在老年代,当升到老年代的对象大于老年代剩余空间时会发生Major GC。
发生Major GC时用户线程会暂停,会降低系统性能和吞吐量。
JVM的参数-Xmx和-Xms用来设置Java堆内存的初始大小和最大值。依据个人经验这个值的比例最好是1:1或者1:1.5。比如,你可以将-Xmx和-Xms都设为1GB,或者-Xmx和-Xms设为1.2GB和1.8GB。
Java中不能手动触发GC,但可以用不同的引用类来辅助垃圾回收器工作(比如:弱引用或软引用)。
在Java中,对象实例都是在堆上创建。一些类信息,常量,静态变量等存储在方法区。堆和方法区都是线程共享的。
GC机制是由JVM提供,用来清理需要清除的对象,回收堆内存。
GC机制将Java程序员从内存管理中解放了出来,可以更关注于业务逻辑。
在Java中,GC是由一个被称为垃圾回收器的守护进程执行的。
在从内存回收一个对象之前会调用对象的finalize()方法。
作为一个Java开发者不能强制JVM执行GC;GC的触发由JVM依据堆内存的大小来决定。
System.gc()和Runtime.gc()会向JVM发送执行GC的请求,但是JVM不保证一定会执行GC。
如果堆没有内存创建新的对象了,会抛出OutOfMemoryError。
典型的垃圾收集算法
标记-清扫(mark-sweep)。最基础,最容易。一般很少会用。
问题:会产生内存碎片。复制(copying)。解决了碎片问题。
问题:耗费内存。而且如果存活对象很多,此算法效率大大降低。
'标记-整理(mark-compact)。对1算法的优化,区别是:在完成标记后,不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉边界以外的内在。
分代收集算法(generational collection)。这是目前大部分JVM的垃圾回收算法,主要目的就是为了增加吞吐量或降低停顿时间。老年代需要回收的对象较少,新生代需要回收的对象较多。所有对它采用不同的算法:老板代用标记-整理算法,新生对用复制算法。
(一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。)
HotSpot(JDK 7)虚拟机提供的几种垃圾收集器
- Parallel Scavenge,新生代的多线程收集器,Copying算法。
- Parallel Old,老年代的多线程收集器,Mark-Compact算法。
- G1收集器,JDK1.7中正式发布,当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。
如果一个应用大多是短生命周期的对象,那么应该确保Eden区足够大,这样可以减少Minor GC的次数。
并发垃圾回收器的内存回收过程是与用户线程一起并发执行的。通常情况下,并发垃圾回收器可以在用户线程运行的情况下完成大部分的回收工作,所以应用停顿时间很短。
但由于并发垃圾回收时用户线程还在运行,所以会有新的垃圾不断产生。作为担保,如果在老年代内存都被占用之前,如果并发垃圾回收器还没结束工作,那么应用会暂停,在所有用户线程停止的情况下完成回收。这种情况称作Full GC,这意味着需要调整有关并发回收的参数了。
由于Full GC很影响应用的性能,要尽量避免或减少。特别是如果对于高容量低延迟的电商系统,要尽量避免在交易时间段发生Full GC。
Minor GC或叫Young GC。注意,Minor GC并不代表年轻代内存不足,它事实上只表示在Eden区上的GC。
在发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行Full GC(这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发Full GC,哪怕老年代还有很多内存,所以,最好不要这样做)。
永久代的回收有两种:常量池中的常量,无用的类信息
在Java虚拟机的参数中,有3种表示方法:
标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容;
非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足,且不保证向后兼容;
非Stable参数(-XX),此类参数各个jvm实现会有所不同,将来可能会随时取消,需要慎重使用(但是,这些参数往往是非常有用的);
-verbose
这是查询GC问题最常用的命令之一,具体参数如:
-verbose:class
输出jvm载入类的相关信息,当jvm报告说找不到类或者类冲突时可此进行诊断。
-verbose:gc
输出每次GC的相关情况,后面会有更详细的介绍。
-verbose:jni
输出native方法调用的相关情况,一般用于诊断jni调用错误信息。
jps命令用于查询正在运行的JVM进程:
jps -l 输出主类的全类名,如果进程执行的是Jar包,输出Jar路径
jstat可以实时显示本地或远程JVM进程中类装载、内存、垃圾收集、JIT编译等数据(如果要显示远程JVM信息,需要远程主机开启RMI支持)。如果在服务启动时没有指定启动参数-verbose:gc,则可以用jstat实时查看gc情况。
-gc:监听Java堆状况,包括Eden区、两个Survivor区、老年代、永久代等的容量,以用空间、GC时间合计等信息
jstat -gc 309 1000 5 (搜集vid为309的java进程的整体gc状态, 每1000ms收集一次,共收集5次;)
jinfo 用于查询当前运行这的JVM属性和参数的值。
jinfo -sysprops 28336
jmap 用于显示当前Java堆和永久代的详细信息(如当前使用的收集器,当前的空间使用率等).
jmap -heap 28336 查看对详细信息
jmap -dump:file=./test.prof 28336 生成dump文件
jhat 用于分析使用jmap生成的dump文件,是JDK自带的工具,使用方法为: jhat -J -Xmx512m [file]
不过jhat没有mat好用,推荐使用mat,mat速度更快,而且是图形界面。
jstack 用于生成当前JVM的所有线程快照,线程快照是虚拟机每一条线程正在执行的方法,目的是定位线程出现长时间停顿的原因。
jstack 28336
监控和分析GC也有一些可视化工具,比较常见的有JConsole和VisualVM
如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化;如果GC时间超过1-3秒,或者频繁GC,则必须优化;
注:如果满足下面的指标,则一般不需要进行GC:
Minor GC执行时间不到50ms;
Minor GC执行不频繁,约10秒一次;
Full GC执行时间不到1s;
Full GC执行频率不算频繁,不低于10分钟1次;
FULL GC 产生的几种情况
除直接调用System.gc外,触发Full GC执行的情况有如下四种。
System.gc()方法的调用
此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。老年代空间不足。
老年代只有在或者创建大对象、大数组时才会出现不足的现象,当执行FULL-GC后空间仍然不足,则抛出:java.lang.OutOfMemoryError: Java heap space。(所以调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。)永久代空间满。
Permanet Generation中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:
java.lang.OutOfMemoryError: PermGen space
为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。CMS GC时出现promotion failed和concurrent mode failure。
统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间。
对象分配规则
1.对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
2.大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
3.长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
4.动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
5.空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。