[size=large]简介[/size]
HotSpot JVM中,和性能相关的三个部分为:Heap, JIT Compiler, Garbage Collector。
[img]http://dl2.iteye.com/upload/attachment/0106/1947/03c1819c-8fa9-37b6-8df5-4c060164ad07.png[/img]
其中Heap是数据对象存储区域,由Garbage Collector来管理。
大多数的调优选项都是通过设定合适的Garbage Collector来调整Heap区域大小来进行调优的。
[size=large]性能的两个衡量指标:[/size]
1. 响应时间(Responsiveness)
2. 吞吐量(Throughput)
[size=large]Garbage Collection的简介[/size]
垃圾回收的过程,是在内存中区分出正在使用的对象和不再使用的对象,然后把不用的对象删除的过程。具体步骤如下:
1. 标记(Marking), 区别出哪些内存区别在使用,哪些不在使用。文献1中有图片. 以引用计数为例, 找出被引用的对象,和没有被引用的对象. 这个过程耗时比较长.
2. 普通删除(Normal Deletion), 直接删除掉没有被引用的对象. 会引起内存碎片.
2a. 归整删除(Deletion with Compacting), 删除掉没有被引用的对象后, 并且把剩下的被引用的对象移动整理到一起. 这样会使新内容的分配更容易和更快.
[size=large]分代回收[/size]
由于标识和归整的效率不高, 当分配的内存对象越来越多时, 垃圾回收的次数和时间也会迅速增长. 然而,经验表明,大多数的对象都是临时对象,生存时间很短(尽量少new对象, 如果必须不断的new对象,也可以使用thread local方式来建立对象).
所以JVM对Heap内存进行了分代: 年轻代(Young Generation), 年老代(Old Generation)和永久代(Permanent Generation).
[img]http://dl2.iteye.com/upload/attachment/0106/1973/2befe6d9-3e41-3f71-9e0d-3f7737c99f49.png[/img]
所有新的对象都是年轻代中分配. 当年轻代满了时, 触发小的垃圾回收过程(minor garbage collection). 在这个过程中, 不用的对象会被回收掉, 而留下的对象会被标记上年龄(age),根据年龄情况,来最终移动到年老代中.
应用线程暂停事件(Stop the World Event), 所有小的垃圾回收过程均是应用线程暂停事件, 直到垃圾回收完成后结束.
年老代存储生存时间较长的对象. 当年轻代中的对象年龄达到阈值时, 就会进行年老代. 年老代中的对象最终也需要进行垃圾回收的,会在大的垃圾回收过程(major garbage collection, 即Full GC)中进行回收.
大的垃圾回收过程也是应用线程暂停事件. 由于大的垃圾回收过程包含所有对象会更慢, 所以对响应时间要求高的应用中,应该尽量减少大的垃圾回收次数. [color=red]需要注意的是[/color],大的垃圾回收过程中,线程暂停事件的长短,是受到年老代的垃圾回收算法影响的.
永久代包含类和方法的元信息, 在运行时由JVM来控制. 此外,Java SE库中的类和方法也是在永久代存储的.
当类和方法不再使用的时候, 会被JVM回收(unloaded). 这个回收过程包含在Full Garbage Collection中.
[size=large]一般的垃圾回收过程[/size]
1. 最开始, 所有的新对象在Eden区分配, 两个survivor区(from和to区,或者称为S0和S1区)均为空.
2. 当Eden区分配满时, 触发minor GC(minor garbage collection). 被引用的对象会移动到第一个survivor区, 没有被引用的对象在Eden区清空的时候会被删除掉. (Copying回收过程)
3. 在下一个minor GC发生时, Eden区的情况还是一样的, 没有被引用的对象在Eden区清空的时候会被删除掉, 被引用的对象会移动到survivor区. 但是, 被引用的对象移动到的是第二个survivor区(S1). 此外, 之间minor GC时在S0中的对象的age会增加, 然后也移动到S1区. 当所有的对象均移动到了S1中时, S0和Eden区会被清空. 需要注意的是, 这时候, S1区中不同的对象的年龄有可能是不同的了.
[color=red]空间比例:Eden:S0==8:1。设定方法:-XX:SurvivorRatio=8[/color]
4. 在下一个minor GC中,会发生同样的过程, 只是两个survivor区的位置会对调下. 被引用的对象会被移动到S0中,同时生存下来的对象的年龄会增加. Eden和S1会清空.
5. 在下一个minor GC中, 当对象的年龄达到指定条件时, 会从年轻代进入年老代(Tenured区, 终身区. 年老代只有这一个区).
[color=red]最大年龄阈值设定:-XX:MaxTenuringThreshold[/color]
[color=red]大对象(比如大的数组)直接进入老年代。阈值设定:-XX:PretenureSizeThreshold[/color]
6. 当minor GC次数越来越多时, 会有越来越多的对象进入年老代.
7. 最终, 年老代会发生一个major GC, 同时的年老代进行归整. (注: major GC即 Full GC)
[size=large]Java GC参数[/size]
[table]
| 选项 | 描述 | 示例 |
| -Xms | heap值的初始值 | -Xms10m, -Xms10240k |
| -Xmx | heap值的最大值 | -Xmx80m, -Xmx81920k, -Xmx83886080 |
| -Xmn | 年轻代(Young Generation)的值 | |
| -XX:PermSize | 永久代(Permanet Generation)的起始值 | |
| -XX:MaxPermSize | 永久代(Permanet Generation)的最大值 | |
[/table]
[size=large]串行GC[/size]
Java SE5和6中, client模式默认使用的GC方式是串行GC(Serial GC). 在此模式下, minor GC和minor GC均是使用单一个虚拟CPU来串行执行的, 使用mark-compact回收算法. 这种回收算法会把老的内存移动到heap的开头, 来加快分配新内存的效率.
适用情况:
串行GC适合于client模式的大多数应用, 这些应用不需要很短的响应时间要求. 对于一个几百MBs的内存heap来说,整个Full GC过程约需要几秒钟.
串行GC适用的另一个场景中,一台机器上有很多JVM应用,超过了机器CPU核心数. 在这种情况下, 一个JVM在GC时,最好使用单一虚拟的CPU来进行,可以最大程序的避免对其它JVM的影响. 虽然这可能会导致GC时间延长, 但这是最平衡的做法.
对于小内存和CPU核心的嵌入式设备来讲,串行GC是最优的做法.
命令行参数:-XX:+UseSerialGC
例子: -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC
[size=large]并行GC[/size]
并行GC(Parallel GC)使用多线程来对年轻代进行GC. 当一个机器有N个CPU核心时,默认会使用N个GC线程. 可以使用-XX:ParallelGCThreads=<desired number>来执行GC线程数.
对于一个单CPU的机器, 即使指定了并行GC,也会使用默认的串行GC方式.
对于一个双CPU的机器, 会使用一般的并行GC.
对于更多的CPU核心的机器, 年轻代GC的时间会显著缩短.
适用情况:
并行GC会使用多CPU来增加应用的吞吐量. 当GC操作比较多并且长的等待时间可以接受时, 推荐使用并行GC. 例如, 批量打印报表, 或者大量的数据库查询.
命令行参数:-XX:+UseParallelGC
例子: -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelGC
命令行参数:-XX:+UseParallelOldGC
使用上面的参数时,不仅年轻代会并行GC, 年老代也会并行GC. 同样, 这个并行GC也是归整GC(compat GC). HotSpot仅仅在年老代中使用归整GC. 在年轻代中, 会进行Copy GC, 不需要归整.
例子: -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelOldGC
[size=large]Concurrent Mark Sweep (CMS)算法[/size]
并发Mark Sweep算法, 也叫并发低暂停算法(Concurrent low pause collector), 用来对tenured区进行垃圾回收. [color=red]这种算法会通过并发GC的方式来尽可能的减少暂停时间, 因此通常不会copying和compat生存下来的对象. 如果遇到了内存碎片问题,会去申请新的系统内存. [/color]
注:对于年轻代, CMS算法使用与并行GC算法的同样的操作.
适用情况:
CMS算法适用于要求很低的暂停时间的应用, 同时可以和GC收集器共享资源. 如桌面UI应用, web服务器, 或者数据库查询服务.
命令行参数: -XX:+UseConcMarkSweepGC
线程数: -XX:ParallelCMSThreads=<n>
例子: -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=2
[size=large]G1 GC算法[/size]
在java 7中,首次出现了G1算法(也叫Garbage First算法). 这种算法的目标是替换CMS算法. G1算法是一种并行,并发,逐步的归整,低暂停的垃圾回收算法. 与上面的其它GC算法有着显著的区别.
jdk 1.6update14及以后也有GC算法. 详细的见参考文献2.
命令行参数: -XX:+UseG1GC
例子: -Xmx12m -Xms3m -XX:+UseG1GC -jar
参考文献:
1. [url=http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html]Java Garbage Collection Basics[/url]
2. [url=http://www.dongliu.net/post/404142]JDK7新特性之G1 GC[/url]
HotSpot JVM中,和性能相关的三个部分为:Heap, JIT Compiler, Garbage Collector。
[img]http://dl2.iteye.com/upload/attachment/0106/1947/03c1819c-8fa9-37b6-8df5-4c060164ad07.png[/img]
其中Heap是数据对象存储区域,由Garbage Collector来管理。
大多数的调优选项都是通过设定合适的Garbage Collector来调整Heap区域大小来进行调优的。
[size=large]性能的两个衡量指标:[/size]
1. 响应时间(Responsiveness)
2. 吞吐量(Throughput)
[size=large]Garbage Collection的简介[/size]
垃圾回收的过程,是在内存中区分出正在使用的对象和不再使用的对象,然后把不用的对象删除的过程。具体步骤如下:
1. 标记(Marking), 区别出哪些内存区别在使用,哪些不在使用。文献1中有图片. 以引用计数为例, 找出被引用的对象,和没有被引用的对象. 这个过程耗时比较长.
2. 普通删除(Normal Deletion), 直接删除掉没有被引用的对象. 会引起内存碎片.
2a. 归整删除(Deletion with Compacting), 删除掉没有被引用的对象后, 并且把剩下的被引用的对象移动整理到一起. 这样会使新内容的分配更容易和更快.
[size=large]分代回收[/size]
由于标识和归整的效率不高, 当分配的内存对象越来越多时, 垃圾回收的次数和时间也会迅速增长. 然而,经验表明,大多数的对象都是临时对象,生存时间很短(尽量少new对象, 如果必须不断的new对象,也可以使用thread local方式来建立对象).
所以JVM对Heap内存进行了分代: 年轻代(Young Generation), 年老代(Old Generation)和永久代(Permanent Generation).
[img]http://dl2.iteye.com/upload/attachment/0106/1973/2befe6d9-3e41-3f71-9e0d-3f7737c99f49.png[/img]
所有新的对象都是年轻代中分配. 当年轻代满了时, 触发小的垃圾回收过程(minor garbage collection). 在这个过程中, 不用的对象会被回收掉, 而留下的对象会被标记上年龄(age),根据年龄情况,来最终移动到年老代中.
应用线程暂停事件(Stop the World Event), 所有小的垃圾回收过程均是应用线程暂停事件, 直到垃圾回收完成后结束.
年老代存储生存时间较长的对象. 当年轻代中的对象年龄达到阈值时, 就会进行年老代. 年老代中的对象最终也需要进行垃圾回收的,会在大的垃圾回收过程(major garbage collection, 即Full GC)中进行回收.
大的垃圾回收过程也是应用线程暂停事件. 由于大的垃圾回收过程包含所有对象会更慢, 所以对响应时间要求高的应用中,应该尽量减少大的垃圾回收次数. [color=red]需要注意的是[/color],大的垃圾回收过程中,线程暂停事件的长短,是受到年老代的垃圾回收算法影响的.
永久代包含类和方法的元信息, 在运行时由JVM来控制. 此外,Java SE库中的类和方法也是在永久代存储的.
当类和方法不再使用的时候, 会被JVM回收(unloaded). 这个回收过程包含在Full Garbage Collection中.
[size=large]一般的垃圾回收过程[/size]
1. 最开始, 所有的新对象在Eden区分配, 两个survivor区(from和to区,或者称为S0和S1区)均为空.
2. 当Eden区分配满时, 触发minor GC(minor garbage collection). 被引用的对象会移动到第一个survivor区, 没有被引用的对象在Eden区清空的时候会被删除掉. (Copying回收过程)
3. 在下一个minor GC发生时, Eden区的情况还是一样的, 没有被引用的对象在Eden区清空的时候会被删除掉, 被引用的对象会移动到survivor区. 但是, 被引用的对象移动到的是第二个survivor区(S1). 此外, 之间minor GC时在S0中的对象的age会增加, 然后也移动到S1区. 当所有的对象均移动到了S1中时, S0和Eden区会被清空. 需要注意的是, 这时候, S1区中不同的对象的年龄有可能是不同的了.
[color=red]空间比例:Eden:S0==8:1。设定方法:-XX:SurvivorRatio=8[/color]
4. 在下一个minor GC中,会发生同样的过程, 只是两个survivor区的位置会对调下. 被引用的对象会被移动到S0中,同时生存下来的对象的年龄会增加. Eden和S1会清空.
5. 在下一个minor GC中, 当对象的年龄达到指定条件时, 会从年轻代进入年老代(Tenured区, 终身区. 年老代只有这一个区).
[color=red]最大年龄阈值设定:-XX:MaxTenuringThreshold[/color]
[color=red]大对象(比如大的数组)直接进入老年代。阈值设定:-XX:PretenureSizeThreshold[/color]
6. 当minor GC次数越来越多时, 会有越来越多的对象进入年老代.
7. 最终, 年老代会发生一个major GC, 同时的年老代进行归整. (注: major GC即 Full GC)
[size=large]Java GC参数[/size]
[table]
| 选项 | 描述 | 示例 |
| -Xms | heap值的初始值 | -Xms10m, -Xms10240k |
| -Xmx | heap值的最大值 | -Xmx80m, -Xmx81920k, -Xmx83886080 |
| -Xmn | 年轻代(Young Generation)的值 | |
| -XX:PermSize | 永久代(Permanet Generation)的起始值 | |
| -XX:MaxPermSize | 永久代(Permanet Generation)的最大值 | |
[/table]
[size=large]串行GC[/size]
Java SE5和6中, client模式默认使用的GC方式是串行GC(Serial GC). 在此模式下, minor GC和minor GC均是使用单一个虚拟CPU来串行执行的, 使用mark-compact回收算法. 这种回收算法会把老的内存移动到heap的开头, 来加快分配新内存的效率.
适用情况:
串行GC适合于client模式的大多数应用, 这些应用不需要很短的响应时间要求. 对于一个几百MBs的内存heap来说,整个Full GC过程约需要几秒钟.
串行GC适用的另一个场景中,一台机器上有很多JVM应用,超过了机器CPU核心数. 在这种情况下, 一个JVM在GC时,最好使用单一虚拟的CPU来进行,可以最大程序的避免对其它JVM的影响. 虽然这可能会导致GC时间延长, 但这是最平衡的做法.
对于小内存和CPU核心的嵌入式设备来讲,串行GC是最优的做法.
命令行参数:-XX:+UseSerialGC
例子: -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC
[size=large]并行GC[/size]
并行GC(Parallel GC)使用多线程来对年轻代进行GC. 当一个机器有N个CPU核心时,默认会使用N个GC线程. 可以使用-XX:ParallelGCThreads=<desired number>来执行GC线程数.
对于一个单CPU的机器, 即使指定了并行GC,也会使用默认的串行GC方式.
对于一个双CPU的机器, 会使用一般的并行GC.
对于更多的CPU核心的机器, 年轻代GC的时间会显著缩短.
适用情况:
并行GC会使用多CPU来增加应用的吞吐量. 当GC操作比较多并且长的等待时间可以接受时, 推荐使用并行GC. 例如, 批量打印报表, 或者大量的数据库查询.
命令行参数:-XX:+UseParallelGC
例子: -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelGC
命令行参数:-XX:+UseParallelOldGC
使用上面的参数时,不仅年轻代会并行GC, 年老代也会并行GC. 同样, 这个并行GC也是归整GC(compat GC). HotSpot仅仅在年老代中使用归整GC. 在年轻代中, 会进行Copy GC, 不需要归整.
例子: -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelOldGC
[size=large]Concurrent Mark Sweep (CMS)算法[/size]
并发Mark Sweep算法, 也叫并发低暂停算法(Concurrent low pause collector), 用来对tenured区进行垃圾回收. [color=red]这种算法会通过并发GC的方式来尽可能的减少暂停时间, 因此通常不会copying和compat生存下来的对象. 如果遇到了内存碎片问题,会去申请新的系统内存. [/color]
注:对于年轻代, CMS算法使用与并行GC算法的同样的操作.
适用情况:
CMS算法适用于要求很低的暂停时间的应用, 同时可以和GC收集器共享资源. 如桌面UI应用, web服务器, 或者数据库查询服务.
命令行参数: -XX:+UseConcMarkSweepGC
线程数: -XX:ParallelCMSThreads=<n>
例子: -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=2
[size=large]G1 GC算法[/size]
在java 7中,首次出现了G1算法(也叫Garbage First算法). 这种算法的目标是替换CMS算法. G1算法是一种并行,并发,逐步的归整,低暂停的垃圾回收算法. 与上面的其它GC算法有着显著的区别.
jdk 1.6update14及以后也有GC算法. 详细的见参考文献2.
命令行参数: -XX:+UseG1GC
例子: -Xmx12m -Xms3m -XX:+UseG1GC -jar
参考文献:
1. [url=http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html]Java Garbage Collection Basics[/url]
2. [url=http://www.dongliu.net/post/404142]JDK7新特性之G1 GC[/url]