本文译自《Java and 64-Bit Architetures》,时间仓促,没有逐句翻译,不当之处,敬请原谅。
1. 为什么我们需要64位架构?
一直以来,如何处理呈几何级数增长的海量数据不仅仅是Java开发者面临的挑战,而且是硬件和软件制造商同样面临的挑战。今天,我们见证了一个计算机发展史上的进化: 从32位架构转移到64位架构。在过去的15年里, 32位架构被认为是足够运行大多数的企业应用;但是,今天日新月异的业务领域对系统提出了更高的需求,因此64位架构应需而生。
64位硬件已经成为了工业标准,CPU 如IBM Power、Sun SPARC以及Intel Itanium都已经支持64位, 包括AMD Opteron chips 和 Intel Xeon EM64T chips。除了硬件,操作系统厂商如Microsoft、HP、IBM、Sun 以及 Linux都已经有了64位的产品。只有64位的JVM发展相对落后。今天,64位的JVM仍然很少见,并且只使用在特别领域中,比如那些需要超大Heap空间的应用。
2. 64位架构如图所示,底层是64位的,上层可以选择32位的;反过来则不行。可以看出,要想安装64位的JVM,操作系统和硬件必须都是64位的才行。
3. 使用64位架构的好处
理论上,32位最多支持4G内存(2的32次方),64位最多支持内存(2的64次方)。
下图展示Heap大小与GC的关系:
可以看出,Heap=1.5G时的GC所占用的时间几乎是Heap=13.5G的5倍。也就是说,Heap=13.5G时的吞吐率是Heap=1.5G时的5倍。
但是,评估GC好坏的另一个指标是GC暂停时间,而GC暂停时间与两个因素有关:Heap大小和“活对象“的数量。一方面,Heap越大,GC暂停时间越长;另一方面,Heap越大,“活对象“的数量越小,数据访问集中在内存中,访问硬盘的几率很小,因此GC暂停时间越小。
4. 使用64位架构的成功案例
目前已经证实的是,64位架构已经在金融领域(如银行、保险、投资)、科学领域(如地理信息系统)中大显身手。这里有两个成功案例:
(1)野外勘探测量:海量数据处理。
(2)金融产品风险评估:海量数据计算。
5. 64位不是万能的
6. 移植到64位
从32位移植到64位的主要问题是JNI native code:
如果使用了32位的JNI native code,则移植到64位上会有问题,因为32位的JNI native code无法运行在64位的操作系统中。
你需要在64位平台上重新编译32位的JNI native code,如果你有源代码。如果没有源代码,则无法移植。
7. 64位适合你吗?
当你有如下需求时,可以考虑使用64位:
(1)需要Heap空间超过4 GB。
(2)GC时间过长(由于Heap空间过小的缘故)。
(3)海量数据计算。
当你有如下需求时,可以考虑使用32位:
(1)4 GB的Heap空间足够用。
(2)希望GC暂停时间越小越好。
下图展示了在32位和64位架构下,Server端Java应用的性能比较(前提:1G的Heap足够用):
下图展示了在32位和64位架构下,Client端Java应用的性能比较(前提:1G的Heap足够用):
下图展示了在32位和64位架构下,Java应用的性能比较(前提:1G的Heap不够用):
2008年9月26日星期五
2008年9月25日星期四
JVM_025:JIT 是啥东东?
【JIT】:Just In Time,即时编译。
JIT能够加速Java程序的执行速度。大家知道,javac将程序源代码编译成java字节码,即class文件,JVM通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译。
很显然,经过解释执行,其执行速度必然会比可执行的二进制字节码程序慢。为了提高执行速度,引入了JIT。
JIT 编译过程如下:JVM读入字节码文件后,将其发给JIT编译器。JIT编译器将字节码编译成本机机器代码,下图展示了该过程。
JIT能够加速Java程序的执行速度。大家知道,javac将程序源代码编译成java字节码,即class文件,JVM通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译。
很显然,经过解释执行,其执行速度必然会比可执行的二进制字节码程序慢。为了提高执行速度,引入了JIT。
JIT 编译过程如下:JVM读入字节码文件后,将其发给JIT编译器。JIT编译器将字节码编译成本机机器代码,下图展示了该过程。
2008年9月22日星期一
JVM_023:JVM GC 调优举例(摘录+整理)
1. 前后两次GC的间隔是否太短?
运行 java -verbose:gc -Xloggc:gc.log BigObject,文件内容如下:
69.713: [GC 11536K->11044K(12016K), 0.0032621 secs]
69.717: [Full GC 11044K->5143K(12016K), 0.1429698 secs]
首列是JVM 开始后的秒数和毫秒数的时间戳,方括号中含义的依次为:收集的类型 GC之前活动对象所占大小 GC之后活动对象所占大小 Heap总大小(不包括永久代) 本次GC持续的时间。
可以看出第一次GC为一个minor收集, 在GC开始之前,JVM运行了69.713 秒,使用了11536 Kb 的Heap空间。在完成时,使用了11044 Kb,Heap总空间为12016 Kb,而整个收集用了0.0032621 秒。
第二次GC为一个完全收集,在 69.717 秒时发生,即在上一次GC之后0.004 秒开始。注意,如果将上一次minor GC的持续时间加到其开始时间上,就会看到在上次minor收集是在本次完全收集之前不到1毫秒结束。可以得出结论:minor收集没有筹集到足够的空间,并因此触发了完全的 GC。对应用程序来说,就好像一直持续了0.1462319 秒的GC收集。
优化提示:提高年轻代的大小。建议:年轻代的大小为整个Heap空间的3/8。
2. 年轻代的回收率是否低于70%?
运行java -Xmn4m -Xms32m -Xmx32m -XX:+PrintGCDetails BigObject,输出结果如下:
[GC [DefNew: 3968K->64K(4032K), 0.0923407 secs] 3968K->2025K(32704K), 0.0931870 secs]
可以看出,Minor收集在年轻代中中找回3904K(3968K->64K),整个Heap空间找回1943K (3968K->2025)。Minor收集了大约50%(1943/3904)的对象,而另外的50%的对象则被移到了年老代。
优化提示:适当提高年轻代的大小,观察年轻代回收率是否提高。年轻代的minor收集率应在70%以上。
3. 应用暂停时间是否过长?
运行 java -XX:+PrintGCDetails -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime BigObject,输出结果如下:
Application time: 0.5114944 seconds
[GC [DefNew: 3968K->64K(4032K), 0.0823952 secs] 3968K->2023K(32704K), 0.0827626 secs]
Total time for which application threads were stopped: 0.0839428 seconds
年轻代的Minor收集占用的时间比率计算如下:
应用线程被中断的总时长/(应用执行总时长 + 应用线程被中断的总时长)。
那么在本例中垃圾收集占用的时间比率为:0.0839428 /(0.5114944 + 0.0839428 ) = 14%;
垃圾收集占用的时间的比率越大,系统的响应越慢。
优化提示:适当提高年轻代的大小;改变收集方式。
4. 如何缩短minor收集暂停时间?运行 java -XX:+PrintGCDetails -XX:+UseParNewGC BigObject。
5. 如何缩短major收集暂停时间?运行 java -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC BigObject
6. 如何缩短收集暂停时间?运行 java -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC -XX:+UseParNewGC BigObject
7. 如何提高GC的吞吐量?
机器配置:4G的内存,32个CPU。
java -Xmx3200m -Xms3200m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20,说明如下:
(1)-Xmx3200m -Xms3200m 配置了最大Heap来充分利用系统内存。
(2)-Xmn2g 创建足够大的年轻代,防止将短期对象复制到年老代。
(3)-Xss128 减少默认最大的线程栈大小,提供更多的处理虚拟内存地址空间被进程使用。
(4)-XX:+UseParallelGC 采用并行垃圾收集器对年青代的内存进行收集,提高效率。
(5)-XX:ParallelGCThreads=20 减少垃圾收集线程,默认是和CPU个数相同,往往不需要配置到最大值。
8. 采用对年老代并行收集,JDK1.6支持。机器配置:4G的内存,32个CPU。
java -Xmx3000m -Xms3000m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC,说明如下:
(1)-Xmx3000m -Xms3000m 内存分配被减小,因为ParallelOldGC会增加对于Native Heap的需求,因此需要减小Java Heap来满足需求。
(2)-XX:+UseParallelOldGC 采用对于老年代并发收集的策略,提高收集效率。
9. 既提高吞吐量,又减少应用暂停时间
机器配置:4G的内存,32个CPU。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31
(1)-XX:+UseConcMarkSweepGC -XX:+UseParNewGC 选择了并发标记交换收集器,它可以并发执行收集操作,降低应用暂停时间,同时它也是并行处理模式,有效地利用多CPU的处理能力。
(2)-XX:SurvivorRatio=8 年轻代中Eden和Survivor比例,两个Survivor区与一个Eden区的比值为2:8。Survivor越大,短期对象在年轻代被回收的可能性越高。
(3)-XX:MaxTenuringThreshold=31 熬过年轻代31次收集后才进入年老代,提高短期对象在年轻代被回收的可能性。
(4)-XX:TargetSurvivorRatio=90 允许使用90%的Survivor区空间,超过默认的50%,提高Survivor区的使用率。
10. 当系统负载与GC“共振”时,如何解决?在《优化Java 垃圾收集器改进系统性能》这篇文章中,任务数与GC发生“共振”:任务数比较高时,JVM开始GC,导致更多的任务无法被处理。通过减少Heap的最大值,降低每次GC所花的时间,提高GC的次数,使其与任务数的频率不一致。从而使CPU有时间可以处理更多的任务。
参考文献:
1. 《JVM调优》。
运行 java -verbose:gc -Xloggc:gc.log BigObject,文件内容如下:
69.713: [GC 11536K->11044K(12016K), 0.0032621 secs]
69.717: [Full GC 11044K->5143K(12016K), 0.1429698 secs]
首列是JVM 开始后的秒数和毫秒数的时间戳,方括号中含义的依次为:收集的类型 GC之前活动对象所占大小 GC之后活动对象所占大小 Heap总大小(不包括永久代) 本次GC持续的时间。
可以看出第一次GC为一个minor收集, 在GC开始之前,JVM运行了69.713 秒,使用了11536 Kb 的Heap空间。在完成时,使用了11044 Kb,Heap总空间为12016 Kb,而整个收集用了0.0032621 秒。
第二次GC为一个完全收集,在 69.717 秒时发生,即在上一次GC之后0.004 秒开始。注意,如果将上一次minor GC的持续时间加到其开始时间上,就会看到在上次minor收集是在本次完全收集之前不到1毫秒结束。可以得出结论:minor收集没有筹集到足够的空间,并因此触发了完全的 GC。对应用程序来说,就好像一直持续了0.1462319 秒的GC收集。
优化提示:提高年轻代的大小。建议:年轻代的大小为整个Heap空间的3/8。
2. 年轻代的回收率是否低于70%?
运行java -Xmn4m -Xms32m -Xmx32m -XX:+PrintGCDetails BigObject,输出结果如下:
[GC [DefNew: 3968K->64K(4032K), 0.0923407 secs] 3968K->2025K(32704K), 0.0931870 secs]
可以看出,Minor收集在年轻代中中找回3904K(3968K->64K),整个Heap空间找回1943K (3968K->2025)。Minor收集了大约50%(1943/3904)的对象,而另外的50%的对象则被移到了年老代。
优化提示:适当提高年轻代的大小,观察年轻代回收率是否提高。年轻代的minor收集率应在70%以上。
3. 应用暂停时间是否过长?
运行 java -XX:+PrintGCDetails -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime BigObject,输出结果如下:
Application time: 0.5114944 seconds
[GC [DefNew: 3968K->64K(4032K), 0.0823952 secs] 3968K->2023K(32704K), 0.0827626 secs]
Total time for which application threads were stopped: 0.0839428 seconds
年轻代的Minor收集占用的时间比率计算如下:
应用线程被中断的总时长/(应用执行总时长 + 应用线程被中断的总时长)。
那么在本例中垃圾收集占用的时间比率为:0.0839428 /(0.5114944 + 0.0839428 ) = 14%;
垃圾收集占用的时间的比率越大,系统的响应越慢。
优化提示:适当提高年轻代的大小;改变收集方式。
4. 如何缩短minor收集暂停时间?运行 java -XX:+PrintGCDetails -XX:+UseParNewGC BigObject。
5. 如何缩短major收集暂停时间?运行 java -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC BigObject
6. 如何缩短收集暂停时间?运行 java -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC -XX:+UseParNewGC BigObject
7. 如何提高GC的吞吐量?
机器配置:4G的内存,32个CPU。
java -Xmx3200m -Xms3200m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20,说明如下:
(1)-Xmx3200m -Xms3200m 配置了最大Heap来充分利用系统内存。
(2)-Xmn2g 创建足够大的年轻代,防止将短期对象复制到年老代。
(3)-Xss128 减少默认最大的线程栈大小,提供更多的处理虚拟内存地址空间被进程使用。
(4)-XX:+UseParallelGC 采用并行垃圾收集器对年青代的内存进行收集,提高效率。
(5)-XX:ParallelGCThreads=20 减少垃圾收集线程,默认是和CPU个数相同,往往不需要配置到最大值。
8. 采用对年老代并行收集,JDK1.6支持。机器配置:4G的内存,32个CPU。
java -Xmx3000m -Xms3000m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC,说明如下:
(1)-Xmx3000m -Xms3000m 内存分配被减小,因为ParallelOldGC会增加对于Native Heap的需求,因此需要减小Java Heap来满足需求。
(2)-XX:+UseParallelOldGC 采用对于老年代并发收集的策略,提高收集效率。
9. 既提高吞吐量,又减少应用暂停时间
机器配置:4G的内存,32个CPU。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31
(1)-XX:+UseConcMarkSweepGC -XX:+UseParNewGC 选择了并发标记交换收集器,它可以并发执行收集操作,降低应用暂停时间,同时它也是并行处理模式,有效地利用多CPU的处理能力。
(2)-XX:SurvivorRatio=8 年轻代中Eden和Survivor比例,两个Survivor区与一个Eden区的比值为2:8。Survivor越大,短期对象在年轻代被回收的可能性越高。
(3)-XX:MaxTenuringThreshold=31 熬过年轻代31次收集后才进入年老代,提高短期对象在年轻代被回收的可能性。
(4)-XX:TargetSurvivorRatio=90 允许使用90%的Survivor区空间,超过默认的50%,提高Survivor区的使用率。
10. 当系统负载与GC“共振”时,如何解决?在《优化Java 垃圾收集器改进系统性能》这篇文章中,任务数与GC发生“共振”:任务数比较高时,JVM开始GC,导致更多的任务无法被处理。通过减少Heap的最大值,降低每次GC所花的时间,提高GC的次数,使其与任务数的频率不一致。从而使CPU有时间可以处理更多的任务。
参考文献:
1. 《JVM调优》。
2008年9月21日星期日
JVM_022:Heap 大小调优(摘录+整理)
1. Heap 空间的最大值可以是多少?
这个需要测试,可以运行命令 java -Xms<nnnn>M -Xmx<nnnn>M -version 来测试,如果执行正常就表示指定的内存大小可用,否则会打印错误信息。
32位操作系统下,一般限制在1.5G~2G;64位操作系统则无限制。
2. 把-Xms和-Xmx设置成相等,避免每次GC后,JVM调整Heap的大小。
这个需要测试,可以运行命令 java -Xms<nnnn>M -Xmx<nnnn>M -version 来测试,如果执行正常就表示指定的内存大小可用,否则会打印错误信息。
32位操作系统下,一般限制在1.5G~2G;64位操作系统则无限制。
2. 把-Xms和-Xmx设置成相等,避免每次GC后,JVM调整Heap的大小。
2008年9月20日星期六
JVM_021:JVM 调优
JVM 调优主要包括以下几个方面:
1. 堆大小(heap sizing)
2. 垃圾回收(Garbage collection)
3. 栈大小(Stack size)
4. 类装载(Class loading)
5. 编译器/解释器(Compilation/interpretation)
1. 堆大小(heap sizing)
2. 垃圾回收(Garbage collection)
3. 栈大小(Stack size)
4. 类装载(Class loading)
5. 编译器/解释器(Compilation/interpretation)
2008年9月16日星期二
JVM_020:在Eclipse中使用JRockit Mission Control
1. 配置JRockit JVM 作为Eclipse的Java VM
(1)启动脚本
eclipse.exe -clean -vm [jrockit_home]\bin\java.exe -vmargs -Xms256M -Xmx512M -XgcPrio:deterministic -XpauseTarget:20 -XXcompactratio:1
(2)增加JRockit JRE,并将其设为默认的JRE。
2. 为Eclipse安装JRockit Mission Control Plugin
(1)启动脚本
eclipse.exe -clean -vm [jrockit_home]\bin\java.exe -vmargs -Xms256M -Xmx512M -XgcPrio:deterministic -XpauseTarget:20 -XXcompactratio:1
(2)增加JRockit JRE,并将其设为默认的JRE。
2. 为Eclipse安装JRockit Mission Control Plugin
Help > Software Updates > Find and Install
Search for new features to install
New Remote Site
New Update Site,输入 http://www.oracle.com/technology/software/products/jrockit/missioncontrol/updates/base/3.1.0/eclipse/
JVM_019:JRockit Memory Leak Detector 使用说明
JRockit Memory Leak Detector 的趋势分析器可以发现非常慢的泄漏,它显示详细的Heap统计信息(包括泄漏对象的引用类型和实例,分配点),并可快速追溯泄漏的原因。
内存泄漏检测程序使用先进的图形表示技术,简化了复杂信息的浏览和理解。
那么,如何使用JRockit Memory Leak Detector 呢?
说明:本实验使用的JRockit Mission Control的版本为4.0.0。
1. 使用前的环境准备
(1)设置JAVA_HOME为JRockit所在目录。
(2)设置PATH=%JAVA_HOME%\bin;%PATH%
(3)运行 java -version 确认使用的JVM的确是 JRockit,以及具体版本。
(4)运行 java -Xpausetarget=30ms -Xverbose:gc DemoLeak。
(5)运行 启动 JAVA_HOME\bin\jrmc.exe。
(6)找到本地连接的Java程序DemoLeak,右键选择 Start Memleak。
2. 观察
JRockit Memory Leak Detector 有6个tab,分别是:
趋势分析显示了Heap中最常见的对象类型、 对象类型的增长率、对象类型的实例数、所占内存大小 。趋势分析的运行时间越长,趋势就越可靠。
各个类型是按照增长速率的顺序排列的,我们最关注的当然是增长率最高的类型——这些类型很可能就是引起内存泄漏的类型。
可以看出,上图中增长率排在前三位的是:DemoLeak$DemoObject 、Hashtable$Entry 、Hashtable$Entry[] 。
到底是哪个对象类型引起了内存泄漏呢?仔细观察,我们发现DemoLeak$DemoObject 与Hashtable$Entry 实例数大致相当,因此我们可以推测Hashtable$Entry 中存放的就是DemoObject。
为了证实推测,右键点击DemoLeak$DemoObject ,选择“添加到类型图形”,看看有哪些类型指向了DemoObject。
在类型图形Tab中,我们展开指向DemoObject的所有节点,发现只有Hashtable$Entry——我们的猜测是对的!
继续展开指向Hashtable$Entry 的所有节点,发现只有Hashtable$Entry[]。右键Hashtable$Entry[],选择“列出最大数组”——因为最大的数组最可能引起内存泄露!
右键最大数组,选择“添加到实例图形”,发现有一个HashTable指向该数组。继续展开全部节点——有两个DemoThread 对象引用了该HashTable。
说明:本实验过程中断了一次,故最大数组新显示为12287,就是前面所指的393215。
右键其中一个DemoThread,选择“检查实例”。发现第一个参数是table,且指向该有问题的数组。
分析至此,我们基本找到问题的源头了——就是DemoThread.java中的table属性。剩下的工作就是分析该table属性,在哪个地方被不正确地使用了,最终导致了这个可疑的最大数组的产生。
3. DemoLeak.java
import java.util.Hashtable;
import java.util.List;
import java.util.ArrayList;
/*
* Copyright (c) 2007 by BEA Systems, Inc. All Rights Reserved.
*
* Created on 2003-dec-04
*/
/**
* Simple MemLeak demo
*/
public class DemoLeak {
private static class DemoObject {
private long position;
long myField1 = 1;
long myField2 = 2;
public DemoObject(int pos) {
position = pos;
}
public int hashCode() {
return (int)position;
}
public boolean equals(Object o) {
return (o instanceof DemoObject) && (o.hashCode() == position);
}
}
private static class AllocThread extends Thread {
public void run() {
while (true) {
List junkList = new ArrayList();
for (int i = 0; i < 1000; i++) {
junkList.add(new Object());
for (int j = 0; j < 10; j++)
Thread.yield(); // Keep busy yielding for a little while...
}
}
}
}
private static class DemoThread extends Thread {
private Hashtable table;
DemoThread(Hashtable h) {
table = h;
}
public void run() {
int total = 0;
while (true) {
for (int i = 0; i <= 100; i++)
put(total + i);
for (int i = 0; i < 100; i++)
remove(total + i);
total += 101;
for (int i = 0; i < 10; i++)
Thread.yield(); // Keep busy yielding for a little while...
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
}
private void put(int n) {
table.put(new DemoObject(n), "foo");
}
private String remove(int n) {
return (String)table.remove(new DemoObject(n));
}
}
public static void main(String[] args) {
Hashtable h = new Hashtable();
Thread[] threads;
if (args.length != 1) {
threads = new Thread[2];
} else {
threads = new Thread[Integer.parseInt(args[0])];
}
for (int i = 0; i < threads.length; i++) {
threads[i] = new DemoThread(h);
threads[i].start();
}
new AllocThread().start();
}
}
内存泄漏检测程序使用先进的图形表示技术,简化了复杂信息的浏览和理解。
那么,如何使用JRockit Memory Leak Detector 呢?
说明:本实验使用的JRockit Mission Control的版本为4.0.0。
1. 使用前的环境准备
(1)设置JAVA_HOME为JRockit所在目录。
(2)设置PATH=%JAVA_HOME%\bin;%PATH%
(3)运行 java -version 确认使用的JVM的确是 JRockit,以及具体版本。
(4)运行 java -Xpausetarget=30ms -Xverbose:gc DemoLeak。
(5)运行 启动 JAVA_HOME\bin\jrmc.exe。
(6)找到本地连接的Java程序DemoLeak,右键选择 Start Memleak。
2. 观察
JRockit Memory Leak Detector 有6个tab,分别是:
- 趋势
- 类型图形
- 类型树
- 实例图形
- 实例树
- 分配
趋势分析显示了Heap中最常见的对象类型、 对象类型的增长率、对象类型的实例数、所占内存大小 。趋势分析的运行时间越长,趋势就越可靠。
各个类型是按照增长速率的顺序排列的,我们最关注的当然是增长率最高的类型——这些类型很可能就是引起内存泄漏的类型。
可以看出,上图中增长率排在前三位的是:DemoLeak$DemoObject 、Hashtable$Entry 、Hashtable$Entry[] 。
到底是哪个对象类型引起了内存泄漏呢?仔细观察,我们发现DemoLeak$DemoObject 与Hashtable$Entry 实例数大致相当,因此我们可以推测Hashtable$Entry 中存放的就是DemoObject。
为了证实推测,右键点击DemoLeak$DemoObject ,选择“添加到类型图形”,看看有哪些类型指向了DemoObject。
在类型图形Tab中,我们展开指向DemoObject的所有节点,发现只有Hashtable$Entry——我们的猜测是对的!
继续展开指向Hashtable$Entry 的所有节点,发现只有Hashtable$Entry[]。右键Hashtable$Entry[],选择“列出最大数组”——因为最大的数组最可能引起内存泄露!
右键最大数组,选择“添加到实例图形”,发现有一个HashTable指向该数组。继续展开全部节点——有两个DemoThread 对象引用了该HashTable。
说明:本实验过程中断了一次,故最大数组新显示为12287,就是前面所指的393215。
右键其中一个DemoThread,选择“检查实例”。发现第一个参数是table,且指向该有问题的数组。
分析至此,我们基本找到问题的源头了——就是DemoThread.java中的table属性。剩下的工作就是分析该table属性,在哪个地方被不正确地使用了,最终导致了这个可疑的最大数组的产生。
3. DemoLeak.java
import java.util.Hashtable;
import java.util.List;
import java.util.ArrayList;
/*
* Copyright (c) 2007 by BEA Systems, Inc. All Rights Reserved.
*
* Created on 2003-dec-04
*/
/**
* Simple MemLeak demo
*/
public class DemoLeak {
private static class DemoObject {
private long position;
long myField1 = 1;
long myField2 = 2;
public DemoObject(int pos) {
position = pos;
}
public int hashCode() {
return (int)position;
}
public boolean equals(Object o) {
return (o instanceof DemoObject) && (o.hashCode() == position);
}
}
private static class AllocThread extends Thread {
public void run() {
while (true) {
List junkList = new ArrayList();
for (int i = 0; i < 1000; i++) {
junkList.add(new Object());
for (int j = 0; j < 10; j++)
Thread.yield(); // Keep busy yielding for a little while...
}
}
}
}
private static class DemoThread extends Thread {
private Hashtable table;
DemoThread(Hashtable h) {
table = h;
}
public void run() {
int total = 0;
while (true) {
for (int i = 0; i <= 100; i++)
put(total + i);
for (int i = 0; i < 100; i++)
remove(total + i);
total += 101;
for (int i = 0; i < 10; i++)
Thread.yield(); // Keep busy yielding for a little while...
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
}
private void put(int n) {
table.put(new DemoObject(n), "foo");
}
private String remove(int n) {
return (String)table.remove(new DemoObject(n));
}
}
public static void main(String[] args) {
Hashtable h = new Hashtable();
Thread[] threads;
if (args.length != 1) {
threads = new Thread[2];
} else {
threads = new Thread[Integer.parseInt(args[0])];
}
for (int i = 0; i < threads.length; i++) {
threads[i] = new DemoThread(h);
threads[i].start();
}
new AllocThread().start();
}
}
JVM_018:JRockit Runtime Analyzer 使用说明
JRockit Runtime Analyzer 类似于航空中飞行记录器,它可以按照你的需要定制,生成关于JVM及其上运行的应用程序的详细记录。
记录的档案可供以后脱机分析。记录的数据包括方法和锁定的分析,以及垃圾回收统计、优化决策、对象统计和延迟事件。
那么,如何使用JRockit Runtime Analyzer呢?
说明:本实验使用的JRockit Mission Control的版本为3.0.3。
1. 使用前的环境准备
(1)设置JAVA_HOME为JRockit所在目录。
(2)设置PATH=%JAVA_HOME%\bin;%PATH%
(3)运行 java -version 确认使用的JVM的确是 JRockit,以及具体版本。
(4)运行 java -Xpausetarget=30ms DemoLeak。
(5)运行 启动 JAVA_HOME\bin\jrmc.exe。
(6)找到本地连接的Java程序DemoLeak,右键选择 Start JPA Recording。做一些必要的设置后,点击Start就可以了。
2. 观察
JRockit Runtime Analyzer有8个tab,分别是:
2.2 Method
按顺序排列占用CPU时间最多的方法,以及调用该方法之前和之后的方法。 2.3 GC general
可以查看哪些方法引起了GC。 2.4 GC
可以查看最长的暂停时间是多少?2.5 Heap
“暗物质”指被浪费的堆内存,它使堆成为许多碎片。应当尽量减少“暗物质”。
2.6 Object Statistics
比较记录前和记录后各个对象的数量和大小变化。
2.7 Optimizations
看看哪些方法被JRockit JVM优化了。一般优化后的代码比优化前的代码要大,这是因为
2.8 Lock Profiling
列出应用和JVM中有哪些锁。
记录的档案可供以后脱机分析。记录的数据包括方法和锁定的分析,以及垃圾回收统计、优化决策、对象统计和延迟事件。
那么,如何使用JRockit Runtime Analyzer呢?
说明:本实验使用的JRockit Mission Control的版本为3.0.3。
1. 使用前的环境准备
(1)设置JAVA_HOME为JRockit所在目录。
(2)设置PATH=%JAVA_HOME%\bin;%PATH%
(3)运行 java -version 确认使用的JVM的确是 JRockit,以及具体版本。
(4)运行 java -Xpausetarget=30ms DemoLeak。
(5)运行 启动 JAVA_HOME\bin\jrmc.exe。
(6)找到本地连接的Java程序DemoLeak,右键选择 Start JPA Recording。做一些必要的设置后,点击Start就可以了。
2. 观察
JRockit Runtime Analyzer有8个tab,分别是:
- General
- Method
- GC general
- GC
- Heap
- Optimizations
- Lock Profiling
2.2 Method
按顺序排列占用CPU时间最多的方法,以及调用该方法之前和之后的方法。 2.3 GC general
可以查看哪些方法引起了GC。 2.4 GC
可以查看最长的暂停时间是多少?2.5 Heap
“暗物质”指被浪费的堆内存,它使堆成为许多碎片。应当尽量减少“暗物质”。
2.6 Object Statistics
比较记录前和记录后各个对象的数量和大小变化。
2.7 Optimizations
看看哪些方法被JRockit JVM优化了。一般优化后的代码比优化前的代码要大,这是因为
2.8 Lock Profiling
列出应用和JVM中有哪些锁。
JVM_017:JRockit Management Console 使用说明
JRockit Management Console 管理控制台是用于监控和管理多个 JRockit 实例的工具。
它可以捕获并显示关于 GC 暂停、内存和 CPU 使用情况的实时数据,以及来自JVM 内部 MBean 服务器上部署的任何 JMX MBean 的信息。
JVM 管理包括动态控制 CPU 亲和性、垃圾回收策略、内存池大小等。
那么,如何使用JRockit Management Console呢?
说明:本实验使用的JRockit Mission Control的版本为3.0.3。
1. 使用前的环境准备
(1)设置JAVA_HOME为JRockit所在目录。
(2)设置PATH=%JAVA_HOME%\bin;%PATH%
(3)运行 java -version 确认使用的JVM的确是 JRockit,以及具体版本。
(4)运行 java -Xpausetarget=10ms DemoLeak。
(5)运行 启动 JAVA_HOME\bin\jrmc.exe。
(6)找到本地连接的Java程序DemoLeak,右键选择 Start Console。
如果希望远程监控JRockit JVM:java -Xmanagement:port=7091,ssl=false,authenticate=false DemoLeak。
如果希望远程自动发现JRockit JVM:java -Xmanagement:port=7091,ssl=false,authenticate=false,autodiscovery=true DemoLeak。
2. 观察
JRockit Management Console有8个tab,分别是:
2.2 MBean Browser tab
2.3 Memory tab可以观察Heap、内存、GC的使用情况。
参数说明:
(1)Available physical memory:还有多少物理内存可供使用。
(2)Allocated heap size target:Heap总大小(可以动态更改)。
(3)Used physical memory:已使用的物理内存。
(4)Used Java heap:已使用的Heap大小。
(5)Available Java heap:还有多少Heap可供使用。
2.4 Threads tab
2.5 Runtime tab
2.6 Triggers tab可以设置暂停时间触发器,超过设置时间将会触发警告窗口。
特别适合GC反复调优时,长时间监测暂停时间,直到满意为止。
2.7 Exception Count tab
2.8 Method Profiler tab用于监测指定类的指定方法在一定时间内调用了多少次,花费了多少时间。
它可以捕获并显示关于 GC 暂停、内存和 CPU 使用情况的实时数据,以及来自JVM 内部 MBean 服务器上部署的任何 JMX MBean 的信息。
JVM 管理包括动态控制 CPU 亲和性、垃圾回收策略、内存池大小等。
那么,如何使用JRockit Management Console呢?
说明:本实验使用的JRockit Mission Control的版本为3.0.3。
1. 使用前的环境准备
(1)设置JAVA_HOME为JRockit所在目录。
(2)设置PATH=%JAVA_HOME%\bin;%PATH%
(3)运行 java -version 确认使用的JVM的确是 JRockit,以及具体版本。
(4)运行 java -Xpausetarget=10ms DemoLeak。
(5)运行 启动 JAVA_HOME\bin\jrmc.exe。
(6)找到本地连接的Java程序DemoLeak,右键选择 Start Console。
如果希望远程监控JRockit JVM:java -Xmanagement:port=7091,ssl=false,authenticate=false DemoLeak。
如果希望远程自动发现JRockit JVM:java -Xmanagement:port=7091,ssl=false,authenticate=false,autodiscovery=true DemoLeak。
2. 观察
JRockit Management Console有8个tab,分别是:
- Overview
- MBean Browser
- Memory
- Threads
- Runtime
- Triggers
- Exception Count
- Method Profiler
2.2 MBean Browser tab
2.3 Memory tab可以观察Heap、内存、GC的使用情况。
参数说明:
(1)Available physical memory:还有多少物理内存可供使用。
(2)Allocated heap size target:Heap总大小(可以动态更改)。
(3)Used physical memory:已使用的物理内存。
(4)Used Java heap:已使用的Heap大小。
(5)Available Java heap:还有多少Heap可供使用。
2.4 Threads tab
2.5 Runtime tab
2.6 Triggers tab可以设置暂停时间触发器,超过设置时间将会触发警告窗口。
特别适合GC反复调优时,长时间监测暂停时间,直到满意为止。
2.7 Exception Count tab
2.8 Method Profiler tab用于监测指定类的指定方法在一定时间内调用了多少次,花费了多少时间。
2008年9月15日星期一
JVM_016:JRockit JVM 参数介绍
1. 标准参数
(1)-agentlib:设置native library。例如:-agentlib:hprof、-agentlib:jdwp=help、 -agentlib:hprof=help
(2)-agentpath:设置 native library path。
(3)-client:设置JVM 为“client” 方式。
(4)-javaagent:设置Java 语言的agent,参考 java.lang.instrument。
(5) -jrockit:设置JVM 为 “jrockit” 方式,即“server”方式。JVM 默认值。
(6)-version:显示版本信息。
(7)-showversion: 打印版本号并显示参数说明。
(8)-verbose:<area>[,<area>]:参考 -Xverbose。
(9)-cp: 设置类路径。
(10)-classpath: 设置类路径。
(11)-ea (-enableassertions):
(12)-da (-disableassertions):
(13)-esa (-enablesystemassertions):
(14)-dsa (-disablesystemassertions):
(15) -D[name]=[value]:设置一个系统属性。
2. -X 参数
(1)-XgcPrio:[throughput pausetime deterministic] :设置收集策略。
(3)-Xmanagement[:<argument1>=<value1>[,<argument2>=<value2>]]:启动JVM的同时,启动management server。参数可以为如下选项:
3. -XX 参数
参考文献:
1. http://download.oracle.com/docs/cd/E13188_01/jrockit/jrdocs/refman1/intro.html#wp1002680
2. http://download.oracle.com/docs/cd/E13188_01/jrockit/jrdocs/refman/optionX.html
3. http://download.oracle.com/docs/cd/E13188_01/jrockit/jrdocs/refman/optionXX.html
(2)
(3)
(4)
(5)
(7)-showversion:
(9)-cp:
(11)-ea (-enableassertions):
(12)-da (-disableassertions):
(13)-esa (-enablesystemassertions):
(14)-dsa (-disablesystemassertions):
(15)
(1)-XgcPrio:[throughput pausetime deterministic] :设置收集策略。
- throughput:GC效率最高的并发GC。-server情况下默认收集策略。
多个GC线程同时进行“清扫”工作,其它工作线程暂停;因此GC效率最高。但工作线程暂停时间可能过长。 - pausetime:工作线程暂停时间较短的并行GC。配合-XpauseTarget一起使用。默认200ms。
一个GC线程进行“清扫”工作,与其它工作线程同时工作,工作线程暂停时间较短。与throughput相比,消耗资源较多(CPU、内存)。 - deterministic:工作线程暂停时间最短的并行GC。配合-XpauseTarget一起使用。默认30ms。
工作线程暂停时间可以控制在非常短的时间范围内,30ms上下。当然这跟硬件和软件环境有关。
举例说明:如下的硬件环境,1 G的 Heap,每次收集约 30%的Heap空间,暂停时间可以控制在30ms以内。
2 x Intel Xeon 3.6 GHz, 2 MB level 2 cache, 4 GB RAM
4 x Intel Xeon 2.0 GHz, 0.5 MB level 2 cache, 8 GB RAM
(3)-Xmanagement[:<argument1>=<value1>[,<argument2>=<value2>]]:启动JVM的同时,启动management server。参数可以为如下选项:
- autodiscovery=<truefalse>:是否允许JRockit Mission Control自动发现JRockit JVM。
- ssl=<truefalse>:是否允许 SSL encryption。
- authenticate=<truefalse>:是否允许验证 JRockit JVM。
- port=<portNumber>:设置management server port,以便远程连接。
- class=:设置JVM启动时要装载的类。
3. -XX 参数
参考文献:
1. http://download.oracle.com/docs/cd/E13188_01/jrockit/jrdocs/refman1/intro.html#wp1002680
2. http://download.oracle.com/docs/cd/E13188_01/jrockit/jrdocs/refman/optionX.html
3. http://download.oracle.com/docs/cd/E13188_01/jrockit/jrdocs/refman/optionXX.html
JVM_015:JRockit JVM 介绍
1. 完全实现了Java SE
JRockit JVM 专门为Intel和Solaris (SPARC) 硬件作了优化,特别适合服务器运行(使用了JIT编译)。它完全实现了Java SE,由以下部分组成:
(1)The Java Development Kit (JDK)(2)The Java Runtime Environment (JRE)(3)Code Generation and Optimization
(4)Memory Management (Garbage Collection)(5)JRockit Mission Control (Management and Monitoring Tools)
其中,JRockit Mission Control 包括如下功能组件:
(1)JRockit Management Console:管理控制台。
(2)JRockit Runtime Analyzer:记录和分析JVM以及其上运行的应用程序。
(3)JRockit Memory Leak Detector:检测内存泄漏。JRockit Mission Control 在收集数据时,对JRockit JVM影响甚小,因此可以用于生产环境。2. JRockit 架构(1)I/O:负责处理与文件、数据库、网络的通信。
(2)Memory management:负责垃圾回收、为应用调整Heap大小(优化)。
(3)Threads management:负责线程调度、处理同步和锁。
(4)Java model:负责Java reflection 和 class loading。
(5)Code generation:负责把 java 代码编译成字节码,并做相应的优化(JIT) 。
(6)External interfaces 和 monitoring/management:获取JVM信息,监控和管理JVM。
JRockit JVM 专门为Intel和Solaris (SPARC) 硬件作了优化,特别适合服务器运行(使用了JIT编译)。它完全实现了Java SE,由以下部分组成:
(1)The Java Development Kit (JDK)(2)The Java Runtime Environment (JRE)(3)Code Generation and Optimization
(4)Memory Management (Garbage Collection)(5)JRockit Mission Control (Management and Monitoring Tools)
其中,JRockit Mission Control 包括如下功能组件:
(1)JRockit Management Console:管理控制台。
(2)JRockit Runtime Analyzer:记录和分析JVM以及其上运行的应用程序。
(3)JRockit Memory Leak Detector:检测内存泄漏。JRockit Mission Control 在收集数据时,对JRockit JVM影响甚小,因此可以用于生产环境。2. JRockit 架构(1)I/O:负责处理与文件、数据库、网络的通信。
(2)Memory management:负责垃圾回收、为应用调整Heap大小(优化)。
(3)Threads management:负责线程调度、处理同步和锁。
(4)Java model:负责Java reflection 和 class loading。
(5)Code generation:负责把 java 代码编译成字节码,并做相应的优化(JIT) 。
(6)External interfaces 和 monitoring/management:获取JVM信息,监控和管理JVM。
2008年9月11日星期四
JVM_011:HotSpot JVM 参数介绍
参数相当的多,特别是-XX的参数,有近千个,都列出来有些疯狂,因此只列出了可能会用到的参数(有中文说明的),那些不常用的估计一辈子也用不到。这就不少了,这么多的参数,我的建议是:见一个,学一个,消灭一个。
1. 标准参数(运行java,可以看详细说明)
(1)-client:代码编译快速,JVM启动迅速,占用内存范围小,运行时间短。
-hotspot 即 -client,已经不推荐使用。JVM 默认值。
(2)-server:优化代码编译(启动JIT编译,关于JIT,参见《JIT 是啥东东?》),JVM启动较慢,占用内存范围大,运行时间长。
(3)-verbose[:class|gc|jni]:打印详细的输出(class、gc、jni)
(4)-cp:类路径
(5)-classpath:类路径
(6)-version:打印版本号
(7)-showversion:打印版本号并显示参数说明
(8)-fullversion:打印详细的版本号
(9)-agentlib:[=]
(10)-agentpath:[=]
(11)-javaagent:[=]
(12)-d32
(13)-d64
(14)-ea[:packagename...|:classname]
(15)-enableassertions[:packagename...|:classname]
(16)-da[:packagename...|:classname]
(17)-disableassertions[:packagename...|:classname]
(18)-esa
(19)-enablesystemassertions
(20)-dsa
(21)-disablesystemassertions
(22) -D[name]=[value]:设置一个系统属性
(23) -jre-restrict-search
(24) -jre-no-restrict-search
2. -X 参数 (运行java -X,可以看详细说明)
(1)-Xtm
(2)-Xbootclasspath[/a|/p]:<path>
(3)-Xdebug
(4)-Xfuture
(5)-Xcheck:jni
(6)-Xshare:off
(7)-Xshare:auto
(8)-Xshare:on
(9)-Xnoclassgc
(10) -Xss<size> : 设置每个线程的堆栈大小。默认值:1M(JDK5.0),256k(JDK5.0之前)。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
(11) -Xoss<size>
(12) -Xms<size> :Heap的最小值。默认是物理内存的1/64。
(13) -Xmx<size>: Heap的最大值。默认是物理内存的1/4。
(14) -Xmn<size> :直接指定年轻代的大小。此值对系统性能影响甚大,Sun官方推荐为Heap的3/8。
(15) -Xrs
(16) -Xrunhprof[:help]|[:option=value, ...]
(17)- Xmaxjitcodesize<size>
(18) -Xsqnopause
(19) -Xoptimize
(20) -Xmixed
(21) -Xint
(22) -Xincgc
(23) -Xconcgc
(24) -Xprof
(25) -Xaprof
(26) -Xloggc:<file>:指定垃圾收集日志文件。
(27) -Xcomp
(28) -Xbatch
(29) -Xconcurrentio
(30) -Xgenconfig
(31) -Xverifyheap
3. -XX 参数
(1)XX:MinHeapFreeRatio=:设定Heap剩余空间最小值百分比,如果小于此值,JVM会增大Heap空间直到-Xmx。默认值:40%。如果把-Xms和-Xmx设置成相等后,则不起作用。
(2)XX:MaxHeapFreeRatio=:设定Heap剩余空间最大值百分比,如果大于此值,JVM会减小Heap空间直到-Xms。默认值:70%。如果把-Xms和-Xmx设置成相等后,则不起作用。
(3)-XX:+PrintGCDetails:显示GC详细信息。
(4)-XX:+PrintGCTimeStamps:显示每次GC开始的时间戳。
(5)-XX:+PrintGCApplicationConcurrentTime:显示应用执行的时间。
(6)-XX:+PrintGCApplicationStoppedTime:显示应用被暂停的时间。
(7)-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
(8)-XX:NewRatio=:设定年老代与年轻代(包括Eden和两个Survivor区)的比值。比如设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个Heap的1/5。
(9)-XX:SurvivorRatio=:设定年轻代中Eden区与Survivor区的比值。默认值32。比如设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个 Survivor区占整个年轻代的1/6。Survivio的空间过大会造成浪费,过小会使一些年轻对象潜逃到年老代。
(10)-XX:NewSize=:设置年轻代最小值。
(11)-XX:MaxNewSize=:设置年轻代最大值。
(12)-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。
(13)-XX:TargetSurvivorRatio:设置Survivor区使用的百分比。默认值是50%。
(14)-XX:MaxTenuringThreshold=:设置熬过年轻代多少次收集后移入年老代,CMS中默 认为0,熬过第一次GC就转入,不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概率。
(15)-XX:+PrintTenuringDistribution:查看年老代收集信息。
(16)-XX:+UseConcMarkSweepGC:设置年老代为并发收集。
(17)-XX:PermSize=:设定永久代最小值。
(18)-XX:MaxPermSize=:设定永久代最大值。
参考文献:
1. 《A Collection of JVM Options 》
1. 标准参数(运行java,可以看详细说明)
(1)-client:代码编译快速,JVM启动迅速,占用内存范围小,运行时间短。
-hotspot 即 -client,已经不推荐使用。JVM 默认值。
(2)-server:优化代码编译(启动JIT编译,关于JIT,参见《JIT 是啥东东?》),JVM启动较慢,占用内存范围大,运行时间长。
(3)-verbose[:class|gc|jni]:打印详细的输出(class、gc、jni)
(4)-cp:类路径
(5)-classpath:类路径
(6)-version:打印版本号
(7)-showversion:打印版本号并显示参数说明
(8)-fullversion:打印详细的版本号
(9)-agentlib:
(10)-agentpath:
(11)-javaagent:
(12)-d32
(13)-d64
(14)-ea[:packagename...|:classname]
(15)-enableassertions[:packagename...|:classname]
(16)-da[:packagename...|:classname]
(17)-disableassertions[:packagename...|:classname]
(18)-esa
(19)-enablesystemassertions
(20)-dsa
(21)-disablesystemassertions
(22)
2. -X 参数
(2)-Xbootclasspath[/a|/p]:<path>
(3)-Xdebug
(4)-Xfuture
(5)-Xcheck:jni
(6)-Xshare:off
(7)-Xshare:auto
(8)-Xshare:on
(9)-Xnoclassgc
(10)
(18)
(1)XX:MinHeapFreeRatio=:设定Heap剩余空间最小值百分比,如果小于此值,JVM会增大Heap空间直到-Xmx。默认值:40%。如果把-Xms和-Xmx设置成相等后,则不起作用。
(2)XX:MaxHeapFreeRatio=:设定Heap剩余空间最大值百分比,如果大于此值,JVM会减小Heap空间直到-Xms。默认值:70%。如果把-Xms和-Xmx设置成相等后,则不起作用。
(3)-XX:+PrintGCDetails:显示GC详细信息。
(4)-XX:+PrintGCTimeStamps:显示每次GC开始的时间戳。
(5)-XX:+PrintGCApplicationConcurrentTime:显示应用执行的时间。
(6)-XX:+PrintGCApplicationStoppedTime:显示应用被暂停的时间。
(7)-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
(8)-XX:NewRatio=:设定年老代与年轻代(包括Eden和两个Survivor区)的比值。比如设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个Heap的1/5。
(9)-XX:SurvivorRatio=:设定年轻代中Eden区与Survivor区的比值。默认值32。比如设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个 Survivor区占整个年轻代的1/6。Survivio的空间过大会造成浪费,过小会使一些年轻对象潜逃到年老代。
(10)-XX:NewSize=:设置年轻代最小值。
(11)-XX:MaxNewSize=:设置年轻代最大值。
(12)-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。
(13)-XX:TargetSurvivorRatio:设置Survivor区使用的百分比。默认值是50%。
(14)-XX:MaxTenuringThreshold=:设置熬过年轻代多少次收集后移入年老代,CMS中默 认为0,熬过第一次GC就转入,不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概率。
(15)-XX:+PrintTenuringDistribution:查看年老代收集信息。
(16)-XX:+UseConcMarkSweepGC:设置年老代为并发收集。
(17)-XX:PermSize=:设定永久代最小值。
(18)-XX:MaxPermSize=:设定永久代最大值。
参考文献:
1. 《A Collection of JVM Options 》
2008年9月10日星期三
JVM_010:HotSpot JVM 介绍
1. HotSpot JVM 架构2. 重要工具
(1)JConsole:JMX-complaint JVM Monitoring andManagement Console。
(2)jps:JVM process status tool。
(3)jstat:JVM performance and GC statistics monitoring tool。
(4)jinfo:Tool that prints JVM configuration information。
(5)jmap:Tool that prints heap and shared object memory map。
(6)jstack:Thread stack trace printing tool。
参考文献:
1. http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html。
2. http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jps.html。
3. http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jstat.html。
4. http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jinfo.html。
5. http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jmap.html。
6. http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jstack.html。
(1)JConsole:JMX-complaint JVM Monitoring andManagement Console。
(2)jps:JVM process status tool。
(3)jstat:JVM performance and GC statistics monitoring tool。
(4)jinfo:Tool that prints JVM configuration information。
(5)jmap:Tool that prints heap and shared object memory map。
(6)jstack:Thread stack trace printing tool。
参考文献:
1. http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html。
2. http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jps.html。
3. http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jstat.html。
4. http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jinfo.html。
5. http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jmap.html。
6. http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jstack.html。
2008年9月7日星期日
JVM_007:哪些代码会影响 GC?(摘录+整理)
1. 不要使用如下方法:
(1)Object.finalize():Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. 该方法用来回收内存以外的系统资源,比如文件句柄和网络连接。
(2)System.gc():Runs the garbage collector. 该方法建议JVM执行GC(major collection),可以使用-XX:+DisableExplicitGC禁止它,或设为CMS并发-XX:+ExplicitGCInvokesConcurrent。
(3)System.runFinalization():Runs the finalization methods of any objects pending finalization.
2. 分配给应用程序的内存越多越好?错,应该按需分配,绝不多给。
你应该考虑的问题是你的应用程序需要多少内存?这个话题扯远了,留待以后再说。
3. 不用的变量显式置为null
将不用的变量显式地设为Null,有利于GC收集该变量所引用的对象。注意:
(1)对于局部变量不用设置为null,因为这些变量的引用将随着方法的退出而自动清除。
(2)如果该对象与事件监听器有连接,那它还是不可以被收集。因此在设置一个引用变量为null之前,应注意该引用变量指向的对象是否被监听,若有,要首先除去监听器,然后才可以设置为null。
(3)不要滥用这一条建议。滥用的结果可能适得其反。例如,迭代地或者递归地赋空集合内的元素使得这些集合中的对象能够满足垃圾收集的条件,实际上是增加了系统的开销而不是帮助垃圾收集。
正确的做法是正确地设置变量的作用域,而不要显式地赋空它们。
4. 分散对象创建或删除的时间
集 中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存。JVM在面临这种情况时,只能进行Full GC,以回收内存或整合内存碎片。集中删除对象的道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时 强制Full GC 的机率。
5. 如果可以,使用WeakReference或SoftReference
WeakReference是真正意义上的C++指针,只是单纯的指向一个对象,而不会影响对象的引用计数。而SoftReference更特别,在内存足够时,对象会因为SoftReference的存在而不被收集,但内存不足时,对象就还是会被收集。代码如下:
Foo foo = new Foo();
SoftReference sr= new SoftReference(foo);
Foo bar = sr.get();
如果foo已被垃圾收集,sr.get()会返回null;
另外还有一个ReferenceQueue的机制,使得对象被回收时能获得通知,比finalize()完全不知道GC何时会执行要聪明的多。
ReferenceQueue rq = new ReferenceQueue();
ref = new WeakReference(foo, rq);
WeakReference cleaned = rq.pool();
cleaned就是刚刚被GC掉的WeakReference。
(1)Object.finalize():Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. 该方法用来回收内存以外的系统资源,比如文件句柄和网络连接。
(2)System.gc():Runs the garbage collector. 该方法建议JVM执行GC(major collection),可以使用-XX:+DisableExplicitGC禁止它,或设为CMS并发-XX:+ExplicitGCInvokesConcurrent。
(3)System.runFinalization():Runs the finalization methods of any objects pending finalization.
2. 分配给应用程序的内存越多越好?错,应该按需分配,绝不多给。
你应该考虑的问题是你的应用程序需要多少内存?这个话题扯远了,留待以后再说。
3. 不用的变量显式置为null
将不用的变量显式地设为Null,有利于GC收集该变量所引用的对象。注意:
(1)对于局部变量不用设置为null,因为这些变量的引用将随着方法的退出而自动清除。
(2)如果该对象与事件监听器有连接,那它还是不可以被收集。因此在设置一个引用变量为null之前,应注意该引用变量指向的对象是否被监听,若有,要首先除去监听器,然后才可以设置为null。
(3)不要滥用这一条建议。滥用的结果可能适得其反。例如,迭代地或者递归地赋空集合内的元素使得这些集合中的对象能够满足垃圾收集的条件,实际上是增加了系统的开销而不是帮助垃圾收集。
正确的做法是正确地设置变量的作用域,而不要显式地赋空它们。
4. 分散对象创建或删除的时间
集 中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存。JVM在面临这种情况时,只能进行Full GC,以回收内存或整合内存碎片。集中删除对象的道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时 强制Full GC 的机率。
5. 如果可以,使用WeakReference或SoftReference
WeakReference是真正意义上的C++指针,只是单纯的指向一个对象,而不会影响对象的引用计数。而SoftReference更特别,在内存足够时,对象会因为SoftReference的存在而不被收集,但内存不足时,对象就还是会被收集。代码如下:
Foo foo = new Foo();
SoftReference sr= new SoftReference(foo);
Foo bar = sr.get();
如果foo已被垃圾收集,sr.get()会返回null;
另外还有一个ReferenceQueue的机制,使得对象被回收时能获得通知,比finalize()完全不知道GC何时会执行要聪明的多。
ReferenceQueue rq = new ReferenceQueue();
ref = new WeakReference(foo, rq);
WeakReference cleaned = rq.pool();
cleaned就是刚刚被GC掉的WeakReference。
2008年9月6日星期六
JVM_006:分代垃圾收集算法(摘录+整理)
1. 年轻代、年老代、永久代
分代思想很类似于环境保护中的垃圾分类处理,只不过它区分的原则是对象的生命周期长短,分为3个代:年轻代、年老代、永久代,不同的代采用不同的收集算法。永久代一般固定大小为64M,而我们常说的Heap = 年轻代 + 年老代。Heap大小一定情况下,增大年轻代将会减小年老代。1.1 年轻代 Young
这里的对象生命周期非常短,因此采用了复制算法收集垃圾。
复制算法优点是只访问活跃对象,缺点是复制成本高。
但因为年轻代只有少量的对象能熬到垃圾收集,因此只需少量的复制。
优化建议:将垃圾收集进行的时间间隔尽量控制在大部分对象死亡之后,最大限度的减少复制工作。
年轻代中又分为3个区域,1个Eden区,所有新建对象都创建在该区;2个Survivor区,用来实施复制算法。
每次复制就是将Eden和第1块Survior中的活的对象复制到第2块Survior中,然后清空Eden与第一块Survior。
与年轻代有关的参数如下:
(1)-XX:NewRatio=
(2)-XX:SurvivorRatio=
(3)-XX:NewSize=
(4)-XX:MaxNewSize=
(5)-XX:+UseParNewGC
(6)-XX:TargetSurvivorRatio
(7) -Xmn<size>
1.2 年老代 Tenured
年轻代的对象如果能够挺过数次收集,就会进入年老代。
年老代使用标记整理算法,每次遍历区域内所有对象。
因为年老代中的对象都没那么容易死的,采用复制算法就要反复的复制活的对象,很不合算。与年老代有关的参数如下:
(1)-XX:MaxTenuringThreshold=
(2)-XX:+PrintTenuringDistribution
(3)-XX:+UseConcMarkSweepGC
1.3 永久代
装载Class信息等基础数据,服务器Weblogic Server以及Spring,Hibernate这类喜欢AOP动态生成类的框架需要更多的永久代内存。默认64M。
该值如果满了之后会引起Full GC或Out of Memory。与永久代有关的参数如下:
(1)-XX:PermSize=
(2)-XX:MaxPermSize=
2. 不同的代选择不同的垃圾收集算法
2.1 串行收集器(Serial Collector)
(1)-XX:+UseSerialGC:年轻代串行复制,年老代串行标记整理。
分代思想很类似于环境保护中的垃圾分类处理,只不过它区分的原则是对象的生命周期长短,分为3个代:年轻代、年老代、永久代,不同的代采用不同的收集算法。永久代一般固定大小为64M,而我们常说的Heap = 年轻代 + 年老代。Heap大小一定情况下,增大年轻代将会减小年老代。1.1 年轻代 Young
这里的对象生命周期非常短,因此采用了复制算法收集垃圾。
复制算法优点是只访问活跃对象,缺点是复制成本高。
但因为年轻代只有少量的对象能熬到垃圾收集,因此只需少量的复制。
优化建议:将垃圾收集进行的时间间隔尽量控制在大部分对象死亡之后,最大限度的减少复制工作。
年轻代中又分为3个区域,1个Eden区,所有新建对象都创建在该区;2个Survivor区,用来实施复制算法。
每次复制就是将Eden和第1块Survior中的活的对象复制到第2块Survior中,然后清空Eden与第一块Survior。
与年轻代有关的参数如下:
(1)-XX:NewRatio=
(2)-XX:SurvivorRatio=
(3)-XX:NewSize=
(4)-XX:MaxNewSize=
(5)-XX:+UseParNewGC
(6)-XX:TargetSurvivorRatio
(7)
1.2 年老代 Tenured
年轻代的对象如果能够挺过数次收集,就会进入年老代。
年老代使用标记整理算法,每次遍历区域内所有对象。
因为年老代中的对象都没那么容易死的,采用复制算法就要反复的复制活的对象,很不合算。与年老代有关的参数如下:
(1)-XX:MaxTenuringThreshold=
(2)-XX:+PrintTenuringDistribution
(3)-XX:+UseConcMarkSweepGC
1.3 永久代
装载Class信息等基础数据,服务器Weblogic Server以及Spring,Hibernate这类喜欢AOP动态生成类的框架需要更多的永久代内存。默认64M。
该值如果满了之后会引起Full GC或Out of Memory。与永久代有关的参数如下:
(1)-XX:PermSize=
(2)-XX:MaxPermSize=
2. 不同的代选择不同的垃圾收集算法
2.1 串行收集器(Serial Collector)
(1)-XX:+UseSerialGC:年轻代串行复制,年老代串行标记整理。
2.2 并行收集器(Throughput Collector),吞吐量优先。
(1)-XX:+UseParallelGC :设置-server时默认选用的收集器。收集策略如下:
年轻代暂停应用程序,多个垃圾收集线程并行的复制收集,线程数默认为CPU个数。
年老代暂停应用程序,与串行收集器一样,单垃圾收集线程标记整理。
(2)-XX:ParallelGCThreads=:CPU很多时,可以设置实际需要的线程数。
(3)-XX:MaxGCPauseMillis=:设置并行收集年轻代垃圾的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
(4)-XX:GCTimeRatio=:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)。可以看出,在单个CPU时,并行收集器并没有体现出优势(多个线程分享一个CPU <= 一个线程独占一个CPU),只有在2个以上的CPU时,并行收集器才会优于串行收集器。
【吞吐率】:未用于垃圾收集上的时间占运行总时间的百分比。
吞吐率越高,应用程序运行得越快。通常服务器程序都要求有较高的吞吐率。并行收集器侧重于吞吐量,对暂停时间要求较为宽松,适用于后台处理,科学计算。
(1)-XX:+UseParallelGC :设置-server时默认选用的收集器。收集策略如下:
年轻代暂停应用程序,多个垃圾收集线程并行的复制收集,线程数默认为CPU个数。
年老代暂停应用程序,与串行收集器一样,单垃圾收集线程标记整理。
(2)-XX:ParallelGCThreads=:CPU很多时,可以设置实际需要的线程数。
(3)-XX:MaxGCPauseMillis=:设置并行收集年轻代垃圾的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
(4)-XX:GCTimeRatio=:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)。可以看出,在单个CPU时,并行收集器并没有体现出优势(多个线程分享一个CPU <= 一个线程独占一个CPU),只有在2个以上的CPU时,并行收集器才会优于串行收集器。
【吞吐率】:未用于垃圾收集上的时间占运行总时间的百分比。
吞吐率越高,应用程序运行得越快。通常服务器程序都要求有较高的吞吐率。并行收集器侧重于吞吐量,对暂停时间要求较为宽松,适用于后台处理,科学计算。
2.3 并发收集器(Concurrent Low Pause Collector-CMS),暂停时间优先。
(1)-XX:+UseConcMarkSweepGC:收集策略如下:
年轻代暂停应用程序,多个垃圾收集线程并行的复制收集,线程数默认为CPU个数。
年老代只有两次短暂停,其他时间收集线程(占用一个CPU)与应用程序并发运行。
并发收集一开始会很短暂的停止一次所有线程来开始初始标记根对象,然后标记线程与应用线程一起并发运行,最后又很短的暂停一次,多线程并行的重新标记之前可能因为并发而漏掉的对象,然后就开始与应用程序并发的清除过程。
可见,最长的两个遍历过程都是与应用程序并发执行的,比以前的串行算法改进太多太多了!
(2)-XX:CMSInitiatingOccupancyFraction=:串行标记清除是等年老代满了再开始收集的,而并发收集因为要与应用程序一起运行,如果满了才收集,应用程序就无内存可用,所以系统默认68%满的时候就开始收集。内存已设得较大,吃内存又没有这么快的时候,可以用恰当增大该比率。【暂停时间】:指垃圾收集器运行时,应用程序停止响应的时间。
通常桌面GUI 程序都要求程序快速作出响应。适用于对暂停时间要求较高的应用,如Web服务器/应用服务器。
(1)-XX:+UseConcMarkSweepGC:收集策略如下:
年轻代暂停应用程序,多个垃圾收集线程并行的复制收集,线程数默认为CPU个数。
年老代只有两次短暂停,其他时间收集线程(占用一个CPU)与应用程序并发运行。
并发收集一开始会很短暂的停止一次所有线程来开始初始标记根对象,然后标记线程与应用线程一起并发运行,最后又很短的暂停一次,多线程并行的重新标记之前可能因为并发而漏掉的对象,然后就开始与应用程序并发的清除过程。
可见,最长的两个遍历过程都是与应用程序并发执行的,比以前的串行算法改进太多太多了!
(2)-XX:CMSInitiatingOccupancyFraction=:串行标记清除是等年老代满了再开始收集的,而并发收集因为要与应用程序一起运行,如果满了才收集,应用程序就无内存可用,所以系统默认68%满的时候就开始收集。内存已设得较大,吃内存又没有这么快的时候,可以用恰当增大该比率。【暂停时间】:指垃圾收集器运行时,应用程序停止响应的时间。
通常桌面GUI 程序都要求程序快速作出响应。适用于对暂停时间要求较高的应用,如Web服务器/应用服务器。
3. minor 收集和major 收集
每个代满了之后都会触发收集(并发收集器默认在年老代68%的时候触发收集),GC对年轻代扫描和回收的频率较高,对年老代的扫描和回收的频率则要低很多。
只对年轻代的回收称为minor 收集;同时对年轻代和年老代的回收称为major 收集。
每个代满了之后都会触发收集(并发收集器默认在年老代68%的时候触发收集),GC对年轻代扫描和回收的频率较高,对年老代的扫描和回收的频率则要低很多。
只对年轻代的回收称为minor 收集;同时对年轻代和年老代的回收称为major 收集。
2008年9月5日星期五
JVM_005:GC 算法介绍(摘录+整理)
既然GC负责垃圾回收,那自然有一套回收的办法。所有的算法无外乎都做两件事:
(1)找到所有不再使用的对象。
(2)回收这些对象的空间。
目前已经有很多GC算法,除了Sun之外,还有Oracle、IBM、Symantec、Microsoft、HP以及各种开源组织都投入到这项研究。目前,有如下几种垃圾收集的基本策略:
1. 引用计数器
引用计数器保存在对象里,当一个对象被创建时,计数器为1。当其他对象引用这个对象时,该对象的引用计数器加1。当对这个对象的引用结束时,计数器减1。引用计数器为0时,则被看作是垃圾。该算法的缺点是没有遍历功能。该算法已经过时,不再过多介绍。
2. 遍历搜索器
搜索器从树结构的根节点开始遍历,寻找对象引用,并把被引用的对象做标记(Mark)。遍历之后,未作标记的对象被视为垃圾,可被GC回收(Sweep)。
3. 碎片整理器
Heap中的碎片是由于不断分配和释放造成的。如果不合并这些碎片,即使有足够的空间,但由于没有足够大的连续空间,也无法分配空间给对象。这时只好扩大Heap空间,造成空间利用率低,降低运行性能。
Heap 碎片整理(Compacting collector)有两个步骤,整理和拷贝。这两步用来移动Heap中的对象从而减少碎片。既然好移动对象的位置,当要也要更新对象的引用,但是更新对象引用是会降低VM的性能,因此建立一个对象句柄表,对象引用指向对象句柄,当对象移动时,更新对象的句柄,而不用改变对象引用的值。拷贝步骤就是将 Heap的对象移动到新的位置。为了提高拷贝的性能,VM被分为两个区,VM在内只能使用两个中的一个。当使用中的区没有空间后,将所有的对象从该空间拷贝到另一个空间,并继续在另一个空间工作,直到另一个空间满了为止,再把所有对象拷贝回原来的空间。该方法的代价就是需要花费2倍的空间。
4. 分代收集算法
Java 1.5 后,主要使用分代收集算法,详见《分代垃圾收集算法》。
有这么多的算法,该使用哪一种GC算法呢?
回答是取决于你的应用。不同的垃圾收集实现使用不同的策略来识别和收回不可到达的对象,它们与用户程序和调度器以不同的方式互动。不同类型的应用程序对于垃圾收集有不同的要求:实时应用程序会将要求收集暂停的持续时间短并且有限制;而企业应用程序可能允许更长时间和可预测性更低的暂停以获得更高的吞吐能力。
(1)找到所有不再使用的对象。
(2)回收这些对象的空间。
目前已经有很多GC算法,除了Sun之外,还有Oracle、IBM、Symantec、Microsoft、HP以及各种开源组织都投入到这项研究。目前,有如下几种垃圾收集的基本策略:
1. 引用计数器
引用计数器保存在对象里,当一个对象被创建时,计数器为1。当其他对象引用这个对象时,该对象的引用计数器加1。当对这个对象的引用结束时,计数器减1。引用计数器为0时,则被看作是垃圾。该算法的缺点是没有遍历功能。该算法已经过时,不再过多介绍。
2. 遍历搜索器
搜索器从树结构的根节点开始遍历,寻找对象引用,并把被引用的对象做标记(Mark)。遍历之后,未作标记的对象被视为垃圾,可被GC回收(Sweep)。
3. 碎片整理器
Heap中的碎片是由于不断分配和释放造成的。如果不合并这些碎片,即使有足够的空间,但由于没有足够大的连续空间,也无法分配空间给对象。这时只好扩大Heap空间,造成空间利用率低,降低运行性能。
Heap 碎片整理(Compacting collector)有两个步骤,整理和拷贝。这两步用来移动Heap中的对象从而减少碎片。既然好移动对象的位置,当要也要更新对象的引用,但是更新对象引用是会降低VM的性能,因此建立一个对象句柄表,对象引用指向对象句柄,当对象移动时,更新对象的句柄,而不用改变对象引用的值。拷贝步骤就是将 Heap的对象移动到新的位置。为了提高拷贝的性能,VM被分为两个区,VM在内只能使用两个中的一个。当使用中的区没有空间后,将所有的对象从该空间拷贝到另一个空间,并继续在另一个空间工作,直到另一个空间满了为止,再把所有对象拷贝回原来的空间。该方法的代价就是需要花费2倍的空间。
4. 分代收集算法
Java 1.5 后,主要使用分代收集算法,详见《分代垃圾收集算法》。
有这么多的算法,该使用哪一种GC算法呢?
回答是取决于你的应用。不同的垃圾收集实现使用不同的策略来识别和收回不可到达的对象,它们与用户程序和调度器以不同的方式互动。不同类型的应用程序对于垃圾收集有不同的要求:实时应用程序会将要求收集暂停的持续时间短并且有限制;而企业应用程序可能允许更长时间和可预测性更低的暂停以获得更高的吞吐能力。
2008年9月3日星期三
JVM_004:Java 垃圾回收机制(摘录+整理)
机 器的内存是非常宝贵的资源,当不需要对象时,我们应该把对象的空间释放出来,留给其它对象使用。
与C/C++语言不同,Java语言不需要程序代码来显式的释放,而是由Java的垃圾收集器(GC)来完成的。由于Stack能够自动释放空间,因此垃圾回收GC是针对Heap的。垃圾回收的好处是开发人员再也不用自己申请和释放内存了,避免了内存管理不当而导致的JVM Crash。但凡事有利就有弊,垃圾回收的潜在问题是使用一个额外进程跟踪所有对象的Reference,会导致一定的系统开销,使用的不好还会影响程序的性能。
目前大多数开发人员很少知道GC,更遑论设置GC了。“我真的需要关心GC吗?”——这个问题很具有普遍性,在汶川地震之前我觉得地震和我一点都扯不上。很多事情都是这样,惨剧发生了,人们才开始反思,总结经验教训。为何不未雨绸缪呢?没感觉到GC的原因很简单:在正常情况下,GC的占用的时间极短。
触发GC的条件有两个:
(1)应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用。
(2)Heap内存不足时,JVM会强制GC。 需要强调的是,大多数JVM的GC线程与应用线程是互斥的,即GC线程运行时(stop-the-world),其他线程都要暂停下来。
与C/C++语言不同,Java语言不需要程序代码来显式的释放,而是由Java的垃圾收集器(GC)来完成的。由于Stack能够自动释放空间,因此垃圾回收GC是针对Heap的。垃圾回收的好处是开发人员再也不用自己申请和释放内存了,避免了内存管理不当而导致的JVM Crash。但凡事有利就有弊,垃圾回收的潜在问题是使用一个额外进程跟踪所有对象的Reference,会导致一定的系统开销,使用的不好还会影响程序的性能。
目前大多数开发人员很少知道GC,更遑论设置GC了。“我真的需要关心GC吗?”——这个问题很具有普遍性,在汶川地震之前我觉得地震和我一点都扯不上。很多事情都是这样,惨剧发生了,人们才开始反思,总结经验教训。为何不未雨绸缪呢?没感觉到GC的原因很简单:在正常情况下,GC的占用的时间极短。
触发GC的条件有两个:
(1)应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用。
(2)Heap内存不足时,JVM会强制GC。 需要强调的是,大多数JVM的GC线程与应用线程是互斥的,即GC线程运行时(stop-the-world),其他线程都要暂停下来。
JVM_003:Java 内存泄漏(摘录+整理)
1. 首先要回答的是:有Java内存泄漏这回事儿吗?
答案是有,但这样回答并不准确。因为Java的内存泄漏和C++ 的内存泄漏是两回事:C++程序中的内存泄漏是没有任何引用指向该对象,导致无法访问这些对象,也就无法释放这些对象使用的内存。而Java程序中的内存泄漏是有引用指向该对象,(如果没有的话,将被GC回收),但已经不需要使用该对象了(但GC并不知道,GC只看对象是否有引用),久而久之,这样的对象多了,就会造成内存泄漏。说内存泄漏也许不准确,应该称之为“无意识的对象保留(unintentional object retention)”。
如果GC仍然忠实地为我们工作,那么究竟是谁造成了Java内存泄漏?答案是你,程序的开发者。 为了深刻理解“无意识的对象保留”,请看下面这个例子:
1.object aobj = new object ( ) ;
2.object bobj = new object ( ) ;
3.object cobj = new object ( ) ;
4.aobj = bobj;
5.aobj = cobj;
6.cobj = null;
7.aobj = null;
问:这段代码中,第几行的内存空间符合垃圾收集器的收集标准?
答:第7行。
行1-3分别创建了object类的三个对象:aobj,bobj,cobj
行4:此时对象aobj的句柄指向bobj,所以该行的执行不能使aobj符合垃圾收集器的收集标准。
行5:此时对象aobj的句柄指向cobj,所以该行的执行不能使aobj符合垃圾收集器的收集标准。
行6:此时仍没有任何一个对象符合垃圾收集器的收集标准。
行7:对象cobj符合了垃圾收集器的收集标准,因为cobj的句柄指向单一的地址空间。在第6行的时候,cobj已经被赋值为null,但由cobj同时还指向了aobj(第5行),所以此时cobj并不符合垃圾收集器的收集标准。而在第7行,aobj所指向的地址空间也被赋予了空值null,这就说明了,由cobj所指向的地址空间已经被完全地赋予了空值。所以此时cobj最终符合了垃圾收集器的收集标准。 但对于aobj和bobj,仍然无法判断其是否符合收集标准。
2. 哪些情况可能会导致Java的内存泄漏?
(1)多线程:在多线程程序中,线程里保存的只是对象的引用,而对象的实例都保存在Heap中。当一个线程结束,线程里对象引用将被删除,但在其他线程中可能还有指向该对象的引用,那么这些对象就有可能成为Heap中的垃圾。
(2)全局集合:这种集合生命周期极长,向集合中加入对象后,忘了删除不用的对象。例子参见《Effective Java》第5条:消除过期的对象引用。
(3)缓存:缓存用于快速查找执行过的操作结果。通常算法如下:
第一步,检查结果是否在缓存中,如果在,就返回结果。
第二步,如果结果不在缓存中,就进行计算。
第三步,将计算出来的结果添加到缓存中。以便以后对该操作的调用可以使用。
问题就出现在第三步,能否保证没有其它不该加入缓存的对象被加入缓存了。为了预防这种具有潜在破坏性的设计,必须对缓存所使用的内存容量有一个上限。因此,更好的算法是:
第一步,检查结果是否在缓存中,如果在,就返回结果。
第二步,如果结果不在缓存中,就进行计算。
第三步,如果缓存所占的空间过大,按照LRU算法移除缓存对象。
第四步,将计算出来的结果添加到缓存中。
(4)ClassLoader:Java ClassLoader结构的使用为内存泄漏提供了许多可乘之机。正是该结构本身的复杂性使ClassLoader在内存泄漏方面存在如此多的问题。ClassLoader的特别之处在于它不仅涉及“常规”的对象引用,还涉及元对象引用,比如:字段、方法和类。这意味着只要有对字段、方法、类或 ClassLoader的对象的引用,ClassLoader就会驻留在JVM中。因为ClassLoader本身可以关联许多类及其静态字段,所以就有许多内存被泄漏了。
(5)忘记释放某些资源,比如异常时没有加finally{}来释放数据库连接。
(6)给某对象加了listener,对象已经没用了,但listener还在。
3. 如何判断发生了Java的内存泄漏
(1)最不幸的信号:出现了OutOfMemoryError。
这通常发生在您最不愿意它发生的生产环境中,此时几乎不能进行调试。一般情况下,都是代码的问题。所以建议重要的应用一定要做内存泄漏测试。如果是因为测试环境运行应用程序的方式与生产系统不完全相同,因而导致泄漏只出现在生产环境中,在这种情况下,需要使用一些开销较低的工具来监控和查找内存泄漏。还需要能够无需重启系统或修改代码就可以将这些工具连接到正在运行的系统上。
如果没有任何工具,最土的办法就是使用 -verbose:gc参数,不间断地监控GC的活动,确定内存使用量是否随着时间增加。如果确实如此,就发生了内存泄漏。
注意,出现OutOfMemoryError,也有可能应用程序确实正在使用这么多的内存,此时必须增加Heap的大小。
(2)如果发生了内存泄漏,应该如何找到泄漏点呢?
Heap快照可以帮助我们找到泄漏点。操作步骤如下:
第一步,启动并运行系统,直到系统达到一个稳定的状态。
第二步,强制进行一次垃圾收集,并且对此时的Heap做一次快照。
第三步,进行任何可能产生内存泄漏的操作。
第四步,再强制进行一次垃圾收集,并且对此时的Heap做一次快照。
比较两次快照,看看哪些对象的被引用数量比第一次快照时增加了。因为在快照之前强制进行了垃圾收集,那么剩下的对象都应该是被应用程序所引用的对象,并且通过比较两次快照我们可以准确地找出那些被程序保留的、新产生的对象。然后再结合你对程序代码的了解,判断出哪些对象是被无意识保留的,跟踪这些对象的引用链,直到找出源头。
答案是有,但这样回答并不准确。因为Java的内存泄漏和C++ 的内存泄漏是两回事:C++程序中的内存泄漏是没有任何引用指向该对象,导致无法访问这些对象,也就无法释放这些对象使用的内存。而Java程序中的内存泄漏是有引用指向该对象,(如果没有的话,将被GC回收),但已经不需要使用该对象了(但GC并不知道,GC只看对象是否有引用),久而久之,这样的对象多了,就会造成内存泄漏。说内存泄漏也许不准确,应该称之为“无意识的对象保留(unintentional object retention)”。
如果GC仍然忠实地为我们工作,那么究竟是谁造成了Java内存泄漏?答案是你,程序的开发者。 为了深刻理解“无意识的对象保留”,请看下面这个例子:
1.object aobj = new object ( ) ;
2.object bobj = new object ( ) ;
3.object cobj = new object ( ) ;
4.aobj = bobj;
5.aobj = cobj;
6.cobj = null;
7.aobj = null;
问:这段代码中,第几行的内存空间符合垃圾收集器的收集标准?
答:第7行。
行1-3分别创建了object类的三个对象:aobj,bobj,cobj
行4:此时对象aobj的句柄指向bobj,所以该行的执行不能使aobj符合垃圾收集器的收集标准。
行5:此时对象aobj的句柄指向cobj,所以该行的执行不能使aobj符合垃圾收集器的收集标准。
行6:此时仍没有任何一个对象符合垃圾收集器的收集标准。
行7:对象cobj符合了垃圾收集器的收集标准,因为cobj的句柄指向单一的地址空间。在第6行的时候,cobj已经被赋值为null,但由cobj同时还指向了aobj(第5行),所以此时cobj并不符合垃圾收集器的收集标准。而在第7行,aobj所指向的地址空间也被赋予了空值null,这就说明了,由cobj所指向的地址空间已经被完全地赋予了空值。所以此时cobj最终符合了垃圾收集器的收集标准。 但对于aobj和bobj,仍然无法判断其是否符合收集标准。
2. 哪些情况可能会导致Java的内存泄漏?
(1)多线程:在多线程程序中,线程里保存的只是对象的引用,而对象的实例都保存在Heap中。当一个线程结束,线程里对象引用将被删除,但在其他线程中可能还有指向该对象的引用,那么这些对象就有可能成为Heap中的垃圾。
(2)全局集合:这种集合生命周期极长,向集合中加入对象后,忘了删除不用的对象。例子参见《Effective Java》第5条:消除过期的对象引用。
(3)缓存:缓存用于快速查找执行过的操作结果。通常算法如下:
第一步,检查结果是否在缓存中,如果在,就返回结果。
第二步,如果结果不在缓存中,就进行计算。
第三步,将计算出来的结果添加到缓存中。以便以后对该操作的调用可以使用。
问题就出现在第三步,能否保证没有其它不该加入缓存的对象被加入缓存了。为了预防这种具有潜在破坏性的设计,必须对缓存所使用的内存容量有一个上限。因此,更好的算法是:
第一步,检查结果是否在缓存中,如果在,就返回结果。
第二步,如果结果不在缓存中,就进行计算。
第三步,如果缓存所占的空间过大,按照LRU算法移除缓存对象。
第四步,将计算出来的结果添加到缓存中。
(4)ClassLoader:Java ClassLoader结构的使用为内存泄漏提供了许多可乘之机。正是该结构本身的复杂性使ClassLoader在内存泄漏方面存在如此多的问题。ClassLoader的特别之处在于它不仅涉及“常规”的对象引用,还涉及元对象引用,比如:字段、方法和类。这意味着只要有对字段、方法、类或 ClassLoader的对象的引用,ClassLoader就会驻留在JVM中。因为ClassLoader本身可以关联许多类及其静态字段,所以就有许多内存被泄漏了。
(5)忘记释放某些资源,比如异常时没有加finally{}来释放数据库连接。
(6)给某对象加了listener,对象已经没用了,但listener还在。
3. 如何判断发生了Java的内存泄漏
(1)最不幸的信号:出现了OutOfMemoryError。
这通常发生在您最不愿意它发生的生产环境中,此时几乎不能进行调试。一般情况下,都是代码的问题。所以建议重要的应用一定要做内存泄漏测试。如果是因为测试环境运行应用程序的方式与生产系统不完全相同,因而导致泄漏只出现在生产环境中,在这种情况下,需要使用一些开销较低的工具来监控和查找内存泄漏。还需要能够无需重启系统或修改代码就可以将这些工具连接到正在运行的系统上。
如果没有任何工具,最土的办法就是使用 -verbose:gc参数,不间断地监控GC的活动,确定内存使用量是否随着时间增加。如果确实如此,就发生了内存泄漏。
注意,出现OutOfMemoryError,也有可能应用程序确实正在使用这么多的内存,此时必须增加Heap的大小。
(2)如果发生了内存泄漏,应该如何找到泄漏点呢?
Heap快照可以帮助我们找到泄漏点。操作步骤如下:
第一步,启动并运行系统,直到系统达到一个稳定的状态。
第二步,强制进行一次垃圾收集,并且对此时的Heap做一次快照。
第三步,进行任何可能产生内存泄漏的操作。
第四步,再强制进行一次垃圾收集,并且对此时的Heap做一次快照。
比较两次快照,看看哪些对象的被引用数量比第一次快照时增加了。因为在快照之前强制进行了垃圾收集,那么剩下的对象都应该是被应用程序所引用的对象,并且通过比较两次快照我们可以准确地找出那些被程序保留的、新产生的对象。然后再结合你对程序代码的了解,判断出哪些对象是被无意识保留的,跟踪这些对象的引用链,直到找出源头。
2008年9月2日星期二
JVM_002:Java 内存管理(摘录+整理)
我们知道,每个应用程序都需要OS分配一定的内存才能运行。最大可分配的内存受OS的限制,不同的OS对进程所能访问虚拟内存地址区间直接影响对于应用内存的分配,32位的操作系统通常最大支持4G的内存寻址,而 Linux一般为3G,Windows为2G。
那么,Java是如何管理OS分配给的内存的呢?
这就要提到Java的内存管理中的两个东东:堆(Heap)和栈(Stack)。
1. 堆(Heap)
Heap是内存数据区,是存储对象实例的地方(即使用new创建的对象)。
(1)优点:可以动态地分配内存大小,生存期也不必事先告诉编译器。
(2)缺点:由于要在运行时动态分配内存,存取速度较慢。
(3)回收方法:因为是在运行时动态分配内存的,Java的垃圾收集器GC会自动收走这些不再使用的数据。
2. 栈(Stack)
Stack是内存指令区,是存储基本数据类型、指令代码、本地变量、常量、对象的引用地址的地方。方法属于指令,保存在stack中。对象实例在heap中分配好以后,需要在stack中保存一个4个字节的heap内存地址,用来定位该对象实例在Heap中的位置。
(1)优点:存取速度比Heap要快,仅次于寄存器,而且Stack中的数据可以共享,比如每个线程都有自己的Stack,线程之间可以共享数据。
(2)缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
(3)回收方法:Stack的数据结构是先进后出(FILO),能够自动释放空间,不需要Java的垃圾收集器GC。
3. Native Heap Memory
OS分配的内存并不会全部给JVM的Java Heap使用,它主要会分成三部分:Java Heap,Native Heap,载入资源和类库等所占用的内存。由此可见,Native Heap和 Java Heap大小配置是相互制约的,哪一部分分配多了都可能会影响到另外一部分的正常工作。
Native Heap Memory是JVM内部使用的Memory,这部分的Memory可以通过JDK提供的JNI的方式去访问,这部分Memory效率很高,但是管理需要自己去做,如果没有把握最好不要使用,以防出现内存泄露问题。可以在JVM启动时候增加-verbose:jni参数来观察Native Heap Memory。
那么,Java是如何管理OS分配给的内存的呢?
这就要提到Java的内存管理中的两个东东:堆(Heap)和栈(Stack)。
1. 堆(Heap)
Heap是内存数据区,是存储对象实例的地方(即使用new创建的对象)。
(1)优点:可以动态地分配内存大小,生存期也不必事先告诉编译器。
(2)缺点:由于要在运行时动态分配内存,存取速度较慢。
(3)回收方法:因为是在运行时动态分配内存的,Java的垃圾收集器GC会自动收走这些不再使用的数据。
2. 栈(Stack)
Stack是内存指令区,是存储基本数据类型、指令代码、本地变量、常量、对象的引用地址的地方。方法属于指令,保存在stack中。对象实例在heap中分配好以后,需要在stack中保存一个4个字节的heap内存地址,用来定位该对象实例在Heap中的位置。
(1)优点:存取速度比Heap要快,仅次于寄存器,而且Stack中的数据可以共享,比如每个线程都有自己的Stack,线程之间可以共享数据。
(2)缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
(3)回收方法:Stack的数据结构是先进后出(FILO),能够自动释放空间,不需要Java的垃圾收集器GC。
3. Native Heap Memory
OS分配的内存并不会全部给JVM的Java Heap使用,它主要会分成三部分:Java Heap,Native Heap,载入资源和类库等所占用的内存。由此可见,Native Heap和 Java Heap大小配置是相互制约的,哪一部分分配多了都可能会影响到另外一部分的正常工作。
Native Heap Memory是JVM内部使用的Memory,这部分的Memory可以通过JDK提供的JNI的方式去访问,这部分Memory效率很高,但是管理需要自己去做,如果没有把握最好不要使用,以防出现内存泄露问题。可以在JVM启动时候增加-verbose:jni参数来观察Native Heap Memory。
2008年9月1日星期一
JVM_001:JVM 是个啥东东?
【JVM】:Java Virtual Machine Java 虚拟机。
JVM是一台抽象的计算机,在其中可以运行编译后Java程序。说它“虚拟”,是因为它在“真实”的硬件平台和操作系统上建立了一个“虚拟”的软环境。
这样,所有的Java程序独立于底层的操作系统和硬件平台,从而保证了Java程序:"Write once, running anywhere"。
JVM 本身的实现与操作系统有关。目前已知的JVM有:
(1)Sun HotSpot JVM
(2)Oracle JRockit JVM
(3)IBM JVM
(4)HP JVM
JVM是一台抽象的计算机,在其中可以运行编译后Java程序。说它“虚拟”,是因为它在“真实”的硬件平台和操作系统上建立了一个“虚拟”的软环境。
这样,所有的Java程序独立于底层的操作系统和硬件平台,从而保证了Java程序:"Write once, running anywhere"。
JVM 本身的实现与操作系统有关。目前已知的JVM有:
(1)Sun HotSpot JVM
(2)Oracle JRockit JVM
(3)IBM JVM
(4)HP JVM
订阅:
博文 (Atom)