转载于:
内存管理和垃圾回收是JVM 非常关键的点,对Java性能的剖析而言,了解内存管理和垃圾回收的基本策略非常重要。本篇对Sun JVM 6.0的内存管理和垃圾回收做大概的描述。
1.内存管理
在程序运行过程当中,会创建大量的对象,这些对象,大部分是短周期的对象,小部分是长周期的对象,对于短周期的对象,需要频繁地进行垃圾回收以保证无用对 象尽早被释放掉,对于长周期对象,则不需要频率垃圾回收以确保无谓地垃圾扫描检测。为解决这种矛盾,Sun JVM 的内存管理采用分代的策略。 1)年轻代(Young Gen):年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden Space和2个Suvivor Space(命名为A和B) 当对象在堆创建时,将进入年轻代的Eden Space。 垃圾回收器进行垃圾回收时,扫描Eden Space和A Suvivor Space,如果对象仍然存活,则复制到B Suvivor Space,如果B Suvivor Space已经满,则复制 Old Gen 扫描A Suvivor Space时,如果对象已经经过了几次的扫描仍然存活,JVM 认为其为一个Old对象,则将其移到Old Gen。 扫描完毕后,JVM 将Eden Space和A Suvivor Space清空,然后交换A和B的角色(即下次垃圾回收时会扫描Eden Space和BSuvivor Space。我们可以看到:Young Gen垃圾回收时,采用将存活对象复制到到空的Suvivor Space的方式来确保不存在内存碎片,采用空间换时间的方式来加速内存垃圾回收。
2)年老代(Tenured Gen):年老代主要存放JVM 认 为比较old的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁(譬如可能几个小时一次)。年老代主要采用压缩的方式来避免内存碎片 (将存活对象移动到内存片的一边),当然,有些垃圾回收器(譬如CMS垃圾回收器)出于效率的原因,可能会不进行压缩。 3)持久代(Perm Gen):持久代主要存放类定义、字节码和常量等很少会变更的信息 1. Heap设定与垃圾回收
Java Heap分为3个区,Young,Old和Permanent。Young保存刚实例化的对象。当该区被填满时, GC 会将对象移到Old区。Permanent区则负责保存反射对象,本文不讨论该区。
JVM 的Heap分配可以使用-X参数设定,
-Xms | 初始Heap大小 |
-Xmx | java heap最大值 |
-Xmn | young generation的heap大小 |
JVM 有2个GC 线程。第一个线程负责回收Heap的Young区。第二个线程在Heap不足时,遍历Heap,将Young 区升级为Older区。Older区的大小等于-Xmx减去-Xmn,不能将-Xms的值设的过大,因为第二个线程被迫运行会降低JVM 的性能。
为什么一些程序频繁发生 GC ?有如下原因:
l 程序内调用了System. gc ()或Runtime. gc ()。
l 一些中间件软件调用自己的 GC 方法,此时需要设置参数禁止这些 GC 。
l Java的Heap太小,一般默认的Heap值都很小。
l 频繁实例化对象,Release对象。此时尽量保存并重用对象,例如使用StringBuffer()和String()。
如果你发现每次 GC 后,Heap的剩余空间会是总空间的50%,这表示你的Heap处于健康状态。许多Server端的Java程序每次 GC 后最好能有65%的剩余空间。
经验之谈:
1 . Server 端 JVM 最好将 -Xms 和 -Xmx 设为相同值。为了优化 GC ,最好让 -Xmn 值约等于 -Xmx 的 1/3[2]。
2 .一个 GUI 程序最好是每 10 到 20 秒间运行一次 GC ,每次在半秒之内完成 [2] 。
注意:
1.增加Heap的大小虽然会降低GC 的频率,但也增加了每次GC 的时间。并且GC 运行时,所有的用户线程将暂停,也就是GC 期间,Java应用程序不做任何工作。
2.Heap大小并不决定进程的内存使用量。进程的内存使用量要大于-Xmx定义的值,因为Java为其他任务分配内存,例如每个线程的Stack等。
2.Stack的设定
每个线程都有他自己的Stack。
-Xss | 每个线的 Stack 大小 |
Stack的大小限制着线程的数量。如果Stack过大就好导致内存溢漏。-Xss参数决定Stack大小,例如-Xss1024K。如果Stack太小,也会导致Stack溢漏。
3.硬件环境
硬件环境也影响 GC 的效率,例如机器的种类,内存,swap空间,和CPU的数量。
如果你的程序需要频繁创建很多transient对象,会导致 JVM 频繁 GC 。这种情况你可以增加机器的内存,来减少Swap空间的使用[2]。
4.4种 GC
第一种为单线程 GC ,也是默认的 GC 。,该 GC 适用于单CPU机器。
第二种为Throughput GC ,是多线程的 GC ,适用于多CPU,使用大量线程的程序。第二种 GC 与第一种 GC 相似,不同在于 GC 在收集Young区是多线程的,但在Old区和第一种一样,仍然采用单线程。-XX:+UseParallelGC参数启动该 GC 。
第三种为Concurrent Low Pause GC ,类似于第一种,适用于多CPU,并要求缩短因 GC 造成程序停滞的时间。这种 GC 可以在Old区的回收同时,运行应用程序。-XX:+UseConcMarkSweepGC参数启动该 GC 。
第四种为Incremental Low Pause GC ,适用于要求缩短因 GC 造成程序停滞的时间。这种 GC 可以在Young区回收的同时,回收一部分Old区对象。-Xincgc参数启动该 GC 。
4种 GC 的具体描述参见[3]。
参考文章:
1. JVM Tuning.
2. Performance tuning Java: Tuning steps
3. Tuning Garbage Collection with the 1.4.2 JavaTM Virtual Machine .