`

JAVA垃圾回收分析&& java虚拟机垃圾回收机制

 
阅读更多

JAVA垃圾回收分析&&java虚拟机垃圾回收机制 全文转载自http://zackey.iteye.com/blog/1153490 && http://blog.csdn.net/fdimaof/article/details/5647763

JAVA垃圾回收简介 
java中的内存java虚拟机自己去管理的,java的内存分配分为两个部分,一个是数据堆,一个是栈。 
堆是给开发人员用的,是在JVM启动时创建,程序在运行的时候一般分配数据堆,把局部的临时的变量都放进去,生命周期和进程有关系,在堆中分配的内存由java虚拟机的自动垃圾回收器来管理,堆内存用来存放由new创建的对象和数组。 

栈是留给JVM自己用的,用来存放类的信息的,它和堆不同,运行期内GC不会释放空间,当超过变量的作用域后,java会自动释放掉为该变量所分配的内存空间: 
1、如果程序声明了static的变量,就直接在栈中运行的,进程销毁了,不一定会销毁static变量。 
2、在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配 

java中有垃圾回收机制:System.gc()即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。java语言并不要求jvm有gc,也没有规定gc如何工作。垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。 
如果你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问,该块已分配出来的内存也无法再使用,随着服务器内存的不断消耗,而无法使用的内存越来越多,系统也不能再次将它分配给需要的程序,产生泄露。一直下去,程序也逐渐无内存使用,就会溢出。 


堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。缺点就是要在运行时动态分配内存,存取速度较慢。 
栈的优势是存取速度比堆要快,缺点是存在栈中的数据大小与生存期必须是确定的无灵活性。 


JAVA垃圾回收原理 
  在Java虚拟机规范中,提及了如下几种类型的内存空间: 
      栈内存(Stack):每个线程私有的。 
      堆内存(Heap):所有线程公用的。 
      方法区(Method Area):有点像以前常说的“进程代码段”,这里面存放了每个加载类的反射信息、类函数的代码、编译时常量等信息。 
      原生方法栈(Native Method Stack):主要用于JNI中的原生代码,平时很少涉及。 
  而Java的使用的是堆内存,java堆是一个运行时数据区,类的实例(对象)从中分配空间。Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象,“垃圾回收”也是主要是和堆内存(Heap)有关。 
  垃圾回收的概念就是JAVA虚拟机(JVM)回收那些不再被引用的对象内存的过程。一般我们认为正在被引用的对象状态为“alive”,而没有被应用或者取不到引用属性的对象状态为“dead”。垃圾回收是一个释放处于”dead”状态的对象的内存的过程。而垃圾回收的规则和算法被动态的作用于应用运行当中,自动回收。 

JVM的垃圾回收器采用的是一种分代(generational )回收策略,共分为三个代: 
1.Young(年轻代) 
年 轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区 (两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一 个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关 系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。 
2.Tenured(年老代) 
年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。 
3.Perm(持久代) 
用 于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等, 在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。 
虚拟内存初始化的时候会把所有对象都分配到 Eden space,并且大部分对象也会在该区域被释放。 当进行  minor GC的时候,VM会把剩下的没有释放的对象从Eden space移动到其中一个survivor spaces当中。此外,VM也会把那些长期存活在survivor spaces 里的对象移动到 老生代的“tenured” space中。当 tenured generation 被填满后,就会产生Full GC,Full GC会相对比较慢因为回收的内容包括了所有的 live状态的对象,old generation的大小等于Xmx减去-Xmn(Xmx、-Xmn下面有介绍) 

GC类型 
GC有两种类型:Scavenge GC和Full GC。 
Scavenge GC 
一般情况下,当新对象生成,并且在Eden申请空间失败时,就好触发Scavenge GC,堆Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。 
Full GC 
对整个堆进行整理,包括Young、Tenured和Perm。Full GC比Scavenge GC要慢,因此应该尽可能减少Full GC。有如下原因可能导致Full GC: 
Tenured被写满 
Perm域被写满 
System.gc()被显示调用 
上一次GC之后Heap的各域分配策略动态变化 
用较高的频率对年轻的对象(young generation)进行扫描和回收,而对老对象(old generation)的检查回收频率要低很多。这样就不需要每次GC都将内存中所有对象都检查一遍,这种策略有利于实时观察和回收。 
一些对象被创建出来只是拥有短暂的生命周期,比如 iterators 和本地变量。 
另外一些对象被创建是拥有很长的生命周期,比如 高持久化对象等。 

各代内存回收规则 
  Eden Space (heap): 内存最初从这个线程池分配给大部分对象。 
  Survivor Space (heap):用于保存在eden space内存池中经过垃圾回收后没有被回收的对象。 
  Tenured Generation (heap):用于保持已经在 survivor space内存池中存在了一段时间的对象。 
  Permanent Generation (non-heap): 保存虚拟机自己的静态(refective)数据,例如类(class)和(method)对象。Java虚拟机共享这些类数据。这个区域被分割为只读的和只写的。 
  Code Cache (non-heap):HotSpot Java虚拟机包括一个用于编译和保存本地代码(native code)的内存,叫做“代码缓存区”(code cache) 

JVM如何设置虚拟内存 
     提示:在JVM中如果98%的时间是用于GC且可用的Heap size 不足2%的时候将抛出此异常信息。 
     提示:Heap Size 最大不要超过可用物理内存的80%,一般的要将-Xms和-Xmx选项设置为相同,而-Xmn为1/4的-Xmx值。 
     提示:JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。 
     默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC后调整堆的大小。 
     提示:假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。 
     简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制, 
     这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了 
     提示:注意:如果Xms超过了Xmx值,或者堆最大值和非堆最大值的总和超过了物理内存或者操作系统的最大限制都会引起服务器启动不起来。 
     提示:设置NewSize、MaxNewSize相等,"new"的大小最好不要大于"old"的一半,原因是old区如果不够大会频繁的触发 Full GC ,大大降低了性能 
     JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64; 
     由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。 
     解决方法:手动设置Heap size 
      set JAVA_OPTS=-Xms800m -Xmx800m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m 

内存溢出产生 
  1、java.lang.OutOfMemoryError: PermGen space 
    如果web app用了大量的第三方jar或者应用有太多的class文件而恰好MaxPermSize设置较小,超出了也会导致这块内存的占用过多造成溢出,或者服务器热部署时侯不会清理前面 

加载的环境,只会将context更改为新部署的,非堆存的内容就会越来越多。 
     PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中 ,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的应用中有很CLASS的话,就很可能出现PermGen space错误,这种错误常见在web服务器对JSP进行pre compile的时候。如果你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。 
     配置方式参见JVM如何设置虚拟内存 
 2、java.lang.OutOfMemoryError: Javaheap space 
     JVM调用GC的频度还是很高的,主要两种情况下进行垃圾回收: 
     当应用程序线程空闲;另一个是java内存堆不足时,会不断调用GC,若连续回收都解决不了内存堆不足的问题时,就会报out of memory错误。因为这个异常根据系统运行环境决定,所以无法预期它何时出现。根据GC的机制,程序的运行会引起系统运行环境的变化,增加GC的触发机会。 
     为了避免这些问题,程序的设计和编写就应避免垃圾对象的内存占用和GC的开销。显示调用System.GC()只能建议JVM需要在内存中对垃圾对象进行回收,但不是必须马上回收,  一个是并不能解决内存资源耗空的局面,另外也会增加GC的消耗。 
     配置方式参见JVM如何设置虚拟内存堆大小设置 
JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统 下,一般限制在1.5G~2G;64 位操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。 
设置内容: 
1.-Xms:初始堆大小 
2.-Xmx:最大堆大小 
3.-XX:NewSize=n:设置年轻代大小 
4.-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4 
5.-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5 
6.-XX:MaxPermSize=n:设置持久代大小 
典型设置: 
1.java -Xmx3550m -Xms3550m -Xmn2g -Xss128k 
-Xmx3550m:设置JVM最大可用内存为3550M。 
-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。 
-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。 
-Xss128k: 设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内 存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。 
2.java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0 
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6 
-XX:MaxPermSize=16m:设置持久代大小为16m。 
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。 
调优总结 
年轻代大小选择 
1.响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。 
2.吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。 
年老代大小选择 
1.响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得: 
1.并发垃圾收集信息 
2.持久代并发收集次数 
3.传统GC信息 
4.花在年轻代和年老代回收上的时间比例 
减少年轻代和年老代花费的时间,一般会提高应用的效率 
2.吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存 活对象。 
较小堆引起的碎片问题 
因 为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间 较小时,运行 

一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出 现“碎片”,可能需要进行如下配置: 
1.-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。 
2.-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩 
性能检测工具使用 
  利用JDK自带的JControl(图形化监测工具)、JMap(类似于win操作系统中的任务管理器的结果),可以看出哪些个对象实例化次数、以及内存使用情况来进行调优。 

通过代码来提高效率,不健壮代码的特征及解决办法 
     1、尽早释放无用对象的引用。好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。 
     对于仍然有指针指向的实例,jvm就不会回收该资源,因为垃圾回收会将值为null的对象作为垃圾,提高GC回收机制效率; 
     2、我们的程序里不可避免大量使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域; 
     String str = "aaa"; 
     String str2 = "bbb"; 
     String str3 = str + str2;//假如执行此次之后str ,str2以后再不被调用,那它就会被放在内存中等待Java的gc去回收,程序内过多的出现这样的情况就会报上面的那个错误,建 

议在使用字符串时能使用StringBuffer就不要用String,这样可以省不少开销; 
     3、尽量少用静态变量,因为静态变量是全局的,GC不会回收的; 
     4、避免集中创建对象尤其是大对象,JVM会突然需要大量内存,这时必然会触发GC优化系统内存环境;显示的声明数组空间,而且申请数量还极大。 
     5、尽量运用对象池技术以提高系统性能;生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大**对象拥有大数据量的业务对象的时候,可以考虑分块进行处理 ,然后解决一块释放一块的策略。 
     6、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次new之 后又丢弃 
     7、一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成 Out Of Memory Error 的状况,这时就大概要计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。

一、相关概念

 

基本回收算法

  • 引用计数( Reference Counting ) 
    比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃 圾回收时,只用收集计数为 0 的对象。 此算法最致命的是无法处理循环引用的问题。
  • 标记 - 清除( Mark-Sweep ) 
    此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整 个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。
  • 复制( Copying ) 
    此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把 正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内 存整理,不过出现 " 碎片 " 问题。当然,此算法的缺点也是很明显的,就是需要两 倍内存空间。
  • 标记 - 整理( Mark-Compact ) 
    此算法结合了 " 标 记 - 清除 " 和 " 复制 " 两个算法的优点。也是分两阶段,第一阶段从根节点开始 标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象 " 压缩 " 到堆 的其中一块,按顺序排放。此算法避免了 " 标 记 - 清除 " 的碎片问题,同时也避免了 " 复制 " 算法的空间问题。
  • 增量收集( Incremental Collecting ) 
    实施垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因 JDK5.0 中的收集器没有使用这种算法的。
  • 分代( Generational Collecting ) 
    基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对 不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从 J2SE1.2 开始)都是使用此算法的。

 

分代垃圾回收详述

 

如上图所示,为 Java 堆 中的各代分布。

  • Young (年轻代) 
    年轻代分三个区。一个 Eden 区,两个 Survivor 区。大部分对象在 Eden 区中生成。当 Eden 区满时,还存活的对象将被复制到 Survivor 区(两个中的一个),当这个 Survivor 区满时,此区的存活对象将被复制到另 外一个Survivor 区,当这个 Survivor 去也满了的时候,从第一个 Survivor 区复制过来的并且此时还存活的对象, 将被复制" 年老区 (Tenured)" 。需要注意, Survivor 的两个区是对称的,没先后关系,所以 同一个区中可能同时存在从Eden 复制 过来 对象,和从前一个 Survivor 复 制过来的对象,而复制到年老区的只有从第一个 Survivor 去 过来的对象。而且, Survivor 区 总有一个是空的。
  • Tenured (年 老代) 
    年老 代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。
  • Perm (持 久代) 
    用于 存放静态文件,如今 Java 类、方法 等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些 class ,例如 Hibernate 等,在这种时候需要设置一 个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过 -XX:MaxPermSize= 进行设置。

 

GC 类型

GC 有 两种类型: Scavenge GC 和 Full GC 

  • Scavenge GC
    一般情 况下,当新对象生成,并且在 Eden 申 请空间失败时,就好触发 Scavenge GC , 堆 Eden 区域进行 GC,清除非存活对象,并且把尚且存活的对象移动到 Survivor 区。然后整理 Survivor 的两个区。
  • Full GC
    对整个堆进行整 理,包括 Young 、 Tenured 和 Perm 。 Full GC 比 Scavenge GC 要慢,因此应该尽可能减少Full GC 。有如下原因可能导致 Full GC :
    • Tenured 被写满
    • Perm 域被写满
    • System.gc() 被显示调用
    • 上一次 GC 之 后 Heap 的各域分配策略动态变化

 

 

分代垃圾回收过程演示 

 1.


 2.


 3.


 4.

 

二、垃圾回收器

 

目前的收集器主要有三种:串行收集器、并行收集器、并发收集器 。

  • 串行收集器 

    使 用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以此收集器适合单处理器机 器。当然,此收集器也可以用在小数据量( 100M 左 右)情况下的多处理器机器上。可以使用 -XX:+UseSerialGC 打 开。
  • 并行收集器 
     
    • 对年轻代进行并行垃圾回收,因此可以减少垃圾回收时间。一般在多线程多处理 器机器上使用。使用 -XX:+UseParallelGC. 打 开。并行收集器在 J2SE5.0 第 六 6 更新上引入,在 Java SE6.0 中进行了增强 -- 可以堆年老代进行并行收集。如果年老代不使用并发收 集的话,是使用单线程进行垃圾回收,因此会制约扩展能力。使用 -XX:+UseParallelOldGC 打开。
    • 使用 -XX:ParallelGCThreads= 设置并行垃圾回收的线程数。此值可以设置与机器处理 器数量相等。
    • 此收集器可以进行如下配置:
      • 最大垃圾回收暂停 : 指定垃圾回收时的最长暂停时间,通过 -XX:MaxGCPauseMillis= 指定。为毫秒 . 如果指定了此值的话,堆大小和垃圾回收相关参数会进行 调整以达到指定值。设定此值可能会减少应用的吞吐量。
      • 吞吐量 : 吞吐量为垃圾回收时间与非垃圾回收时间的比值,通过 -XX:GCTimeRatio= 来设定,公式为 1/ ( 1+N )。例如, -XX:GCTimeRatio=19 时,表示 5% 的时间用于垃圾回收。默认情况为 99 ,即 1% 的时间用于垃圾回收。
  • 并发收集器 
    可以保证大部分工作都并发进行(应用不 停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。使用 -XX:+UseConcMarkSweepGC 打 开。 
     
    • 并 发收集器主要减少年老代的暂停时间,他在应用不停止的情况下使用独立的垃圾回收线程,跟踪可达对象。在每个年老代垃圾回收周期中,在 收集初期并发收集器会 对整个应用进行简短的暂停,在收集中还会再暂停一次。第二次暂停会比第一次稍长,在此过程中多个线程同时进行垃圾回收工作。
    • 并发收集器使用处理器换来短暂的停顿时间。在一个 N 个处理器的系统上,并发收集部分使用 K/N个可用处理器进行回收,一般情况下 1<=K<=N/4 。
    • 在只有一个处理器的主机上使用并发收集器,设置为 incremental mode 模式也可获得较短的停 顿时间。
    • 浮动垃圾 :由于在应用运行的同时进行垃圾回收,所以有些 垃圾可能在垃圾回收进行完成时产生,这样就造成了 "Floating Garbage" ,这些垃圾需要在下次垃圾回收周期时才能回收掉。所以,并发收集器一般需要20% 的预留空间用于这些浮 动垃圾。
    • Concurrent Mode Failure :并发收集器在应用运行时进行收集,所以需要保证堆在垃圾回收的这段时 间有足够的空间供程序使用,否则,垃圾回收还未完成,堆空间先满了。这种情况下将会发生 " 并发模式失败" ,此时整个应用将会暂停,进行垃圾回收。
    • 启动并发收集器 :因为并发收集在应用运行时进行收集,所 以必须保证收集完成之前有足够的内存空间供程序使用,否则会出现 "Concurrent Mode Failure" 。通过设置 -XX:CMSInitiatingOccupancyFraction=指 定还有多少剩余堆时开始执行并发收集
  • 小结
    • 串行处理器: 
      -- 适用情况:数据量比较小( 100M 左右);单处理器下并且对响应时间无要求的应用。 
      -- 缺点:只能用于小型应用
    • 并行处理器: 
      -- 适用情况: " 对 吞吐量有高要求 " ,多 CPU 、对应用响应时间无要求的中、大型应用。举例: 后台处理、科学计算。 
      -- 缺点:应用响应时间可能较长
    • 并发处理器: 
      -- 适用情况: " 对 响应时间有高要求 " ,多 CPU 、对应用响应时间有较高要求的中、大型应用。举 例: Web 服务器 / 应用服务器、电信交换、集成开发环境。

 

三、常见配置举例

  • 堆大小设置 
    JVM 中最大堆大小 有三方面限制:相关操作系统的数据模型( 32-bt 还 是 64-bit )限制;系统的可用虚拟 内存限制;系统的可用物理内存限制。 32 位 系统下,一般限制在 1.5G~2G ; 64 为操作系统对内存无限制。我在Windows Server 2003 系统, 3.5G 物理内存, JDK5.0 下测试,最大可设置为 1478m 。 
    典型设置:
    • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
      -Xmx3550m
       :设置 JVM 最 大可用内存为 3550M 。 
      -Xms3550m
       :设置 JVM 促 使内存为 3550m 。此 值可以设置与 -Xmx 相 同,以避免每次垃圾回收完成后 JVM 重新 分配内存。 
      -Xmn2g :设置年轻代大小为 2G 。 整 个堆大小 = 年轻代大小 年老代大小 持久代大小 。持久代一般固定大小为 64m ,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8 。 
      -Xss128k
       :设置每个线程的堆栈大小。 JDK5.0 以后每个 线程堆栈大小为 1M ,以前每个线程堆栈大小为 256K 。更 具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制 的,不能无限生成,经验值在3000~5000 左右。
    • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
      -XX:NewRatio=4:
       设置年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代)。设置为 4 ,则年轻代与年老代所占比值为 1 : 4 ,年轻代占整个堆栈的 1/5
      -XX:SurvivorRatio=4
       :设置年轻代中 Eden 区与 Survivor 区 的大小比值。设置为 4 ,则两个Survivor 区与一个 Eden 区的比值为 2:4 ,一个 Survivor 区占整个年轻代的 1/6
      -XX:MaxPermSize=16m : 设 置持久代大小为 16m 。 
      -XX:MaxTenuringThreshold=0 :设 置垃圾最大年龄。 
      如果设置为 0 的话,则年轻代对象不经过Survivor 区,直接进入年老代 。对于年老代比较多的应用,可以提高效率。 如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这 样可以增加对象再年轻代的存活时间,增加 在年轻代即被回收的概论。
  • 回收器选择 
    JVM 给了三种选择:串行收集器、并行收集器、并发收集器 ,但 是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下, JDK5.0 以前都是使用串行收集器,如果想 使用其他收集器需要在启动时加入相应参数。 JDK5.0 以后, JVM 会根据当前 
    系统配置 进行判断。
    • 吞吐量优先 的并行收集器 
      如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。 
      典型配置 :
      • java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
        -XX:+UseParallelGC
         :选择垃圾收集器为并行收集器。 
        此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收 集。 
        -XX:ParallelGCThreads=20 :配 置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
        -XX:+UseParallelOldGC :配置年老代垃圾收集方式为并行收集。 JDK6.0 支 持对年老代并行收集。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
        -XX:MaxGCPauseMillis=100: 设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM 会 自动调整年轻代大小,以满足此值。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
        -XX:+UseAdaptiveSizePolicy :设置此选项后,并行收集器会自动选择年轻代区大小和相应的 Survivor 区比例,以达到目标系统规 定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
    • 响应时间优先 的并发收集器 
      如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域 等。 
      典型配置 :
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
        -XX:+UseConcMarkSweepGC :设 置年老代为并发收集。测试中配置这个以后, -XX:NewRatio=4 的 配置失效了,原因不明。所以,此时年轻代大小最好用 -Xmn 设置。 
        -XX:+UseParNewGC:
         设置年轻代为并行收集。可与 CMS 收集同时使用。 JDK5.0 以上,JVM 会根据系统配置自行设置,所以无需再 设置此值。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
        -XX:CMSFullGCsBeforeCompaction : 由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生 " 碎片 " ,使得运行效率降低。此值设置运行多少次 GC 以后对内存空间进行压缩、整理。 
        -XX:+UseCMSCompactAtFullCollection :打 开对年老代的压缩。可能会影响性能,但是可以消除碎片
  • 辅助信息 
    JVM 提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:
    • -XX:+PrintGC
      输出形式: [GC 118250K->113543K(130112K), 0.0094143 secs]
                      [Full GC 121376K->10414K(130112K), 0.0650971 secs]
    • -XX:+PrintGCDetails
      输 出形式: [GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
                      [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
    • -XX:+PrintGCTimeStamps -XX:+PrintGC : PrintGCTimeStamps 可与上面两个混合 使用 
      输出形式: 11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
    • -XX:+PrintGCApplicationConcurrentTime: 打 印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用 
      输出形式: Application time: 0.5291524 seconds
    • -XX:+PrintGCApplicationStoppedTime :打 印垃圾回收期间程序暂停的时间。可与上面混合使用 
      输出形式: Total time for which application threads were stopped: 0.0468229 seconds
    • -XX:+PrintHeapAtGC: 打 印 GC 前后的详细堆栈信息 
      输出形式: 
      34.702: [GC {Heap before gc invocations=7:
      def new generation   total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
      eden space 49152K, 99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)
      from space 6144K, 55% used [0x221d0000, 0x22527e10, 0x227d0000)
      to   space 6144K,   0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
      tenured generation   total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
      the space 69632K,   3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
      compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
         the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
          ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
          rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
      34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:
      def new generation   total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
      eden space 49152K,   0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)
      from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
      to   space 6144K,   0% used [0x221d0000, 0x221d0000, 0x227d0000)
      tenured generation   total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
      the space 69632K,   4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
      compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
         the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
          ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
          rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
      }
      , 0.0757599 secs]
    • -Xloggc:filename: 与 上面几个配合使用,把相关日志信息记录到文件以便分析。
  • 常见配置汇总
    • 堆设置
      • -Xms: 初 始堆大小
      • -Xmx: 最 大堆大小
      • -XX:NewSize=n: 设 置年轻代大小
      • -XX:NewRatio=n: 设 置年轻代和年老代的比值。如 : 为 3 ,表示年轻代与年老代比值为 1 : 3,年轻代占整个年轻代年老代和的 1/4
      • -XX:SurvivorRatio=n: 年 轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。如: 3 ,表示 Eden : Survivor=3 : 2 ,一个 Survivor 区占整个年轻代的 1/5
      • -XX:MaxPermSize=n: 设 置持久代大小
    • 收集器设置
      • -XX:+UseSerialGC: 设 置串行收集器
      • -XX:+UseParallelGC: 设 置并行收集器
      • -XX:+UseParalledlOldGC: 设 置并行年老代收集器
      • -XX:+UseConcMarkSweepGC: 设 置并发收集器
    • 垃圾回收统计信息
      • -XX:+PrintGC
      • -XX:+PrintGCDetails
      • -XX:+PrintGCTimeStamps
      • -Xloggc:filename
    • 并行收集器设置
      • -XX:ParallelGCThreads=n: 设 置并行收集器收集时使用的 CPU 数。 并行收集线程数。
      • -XX:MaxGCPauseMillis=n: 设 置并行收集最大暂停时间
      • -XX:GCTimeRatio=n: 设 置垃圾回收时间占程序运行时间的百分比。公式为 1/(1+n)
    • 并发收集器设置
      • -XX:+CMSIncrementalMode: 设 置为增量模式。适用于单 CPU 情 况。
      • -XX:ParallelGCThreads=n: 设 置并发收集器年轻代收集方式为并行收集时,使用的 CPU 数。并 行收集线程数。

   

四、调优总结

  • 年轻代大小选择
    • 响应时间优先的应用: 尽可能设大,直到接近系统的最低响应时间限制 (根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减 少到达年老代的对象。
    • 吞吐量优先的应用:尽可能的设置大,可能到达 Gbit 的程度。因为对响应时间没有要求,垃圾收集可以 并行进行,一般适合 8CPU 以 上的应用。
  • 年老代大小选择
    • 响 应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果 堆设置小了,可以会造成内存碎 片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获 得:
      • 并发垃圾收集信息
      • 持久代并发收集次数
      • 传统 GC 信 息
      • 花在年轻代和年老代回收上的时间比例 
        减少年轻代和年老代花费的时间,一般会提高应用的效率
    • 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老 代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
  • 较小堆引起的碎片问题 
    因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他 会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现 " 碎片 " ,如果并发收集器找不到足够的空间,那么并发收集器将 会停止,然后使用传统的标记、清除方式进行回收。如果出现 " 碎 片 ",可能需要进行如下配 置:
    • -XX:+UseCMSCompactAtFullCollection :使 用并发收集器时,开启对年老代的压缩。
    • -XX:CMSFullGCsBeforeCompaction=0 :上 面配置开启的情况下,这里设置多少次 Full GC 后, 对年老代进行压缩

   

五、参考文献

分享到:
评论

相关推荐

    java垃圾回收机制简述

    虽然《Java虚拟机规范》并没有强制要求在方法区实现垃圾回收,但在实际应用中,许多JVM实现了这种方法区的垃圾回收机制。尽管如此,由于方法区中存储的是类的信息、常量、静态变量等数据,因此其回收效率通常较低。 ...

    Java垃圾回收机制总结

    Java垃圾回收机制是Java虚拟机(JVM)中的一种机制,用于防止内存泄露和有效地使用空闲的内存。垃圾回收机制的主要目的是为了回收无用的对象占用的内存空间,使该空间可被程序再次使用。 垃圾回收机制的算法有多种...

    java高级之垃圾回收机制

    其中,垃圾回收机制(Garbage Collection, GC)是Java虚拟机(JVM)的一项重要特性,它能够自动检测并回收不再使用的对象占用的内存空间,从而有效避免了内存泄漏问题。本文将详细介绍Java中的垃圾回收机制及其工作原理...

    java垃圾回收机制

    而在Java中,这种内存管理的工作由Java虚拟机(JVM)内置的垃圾回收机制自动完成,极大地简化了程序员的工作。 垃圾收集的基本原理在于识别并自动回收那些不再被任何引用指向的对象所占用的内存空间。当一个对象...

    细述 Java垃圾回收机制→Java Garbage Collection Introduction - Android 1

    Java垃圾回收机制简介 Java垃圾回收机制是Java语言中的一种自动内存管理机制,它可以帮助程序员更好地编写Java应用程序,而不需要手动编写垃圾回收相关的代码。这篇文章将会介绍Java垃圾回收机制的基本概念和原理,...

    java垃圾回收器代码举例

    在Java中,程序员无需手动释放内存,这一过程由JVM(Java虚拟机)自动完成。本篇文章将通过代码示例和个人笔记来深入探讨Java垃圾回收器的工作原理及其应用。 1. **Java内存模型** - Java内存分为堆内存(Heap)和...

    Java与C#的垃圾回收机制

    ### Java与C#的垃圾回收机制 #### 一、引言 在现代编程语言中,内存管理是一项重要的功能,能够显著提升程序的稳定性和效率。本文将深入对比Java与C#这两种广泛使用的编程语言中的垃圾回收机制,帮助开发者更好地...

    java虚拟机垃圾回收详解

    Java虚拟机(JVM)的垃圾回收(Garbage Collection,简称GC)机制是Java编程中的一个重要组成部分,它自动管理程序的内存,确保无用的对象能够被有效地释放,从而避免内存泄漏。本文将深入探讨Java垃圾回收的基本...

    java垃圾回收(gc)机制详解.pdf

    Java通过垃圾回收机制来帮助开发者自动管理内存,减轻了手动内存管理的负担。 一、为什么需要垃圾回收? 垃圾回收机制是为了解决内存资源的释放问题。在编程过程中,对象的创建和销毁是一个不断重复的过程。如果不...

    细述 Java垃圾回收机制→How Java Garbage Collection Works- - Android 1

    Java垃圾回收机制是Java虚拟机(JVM)中的一种自动管理内存的机制,它可以自动地将不再使用的对象从内存中回收,以释放更多的内存空间供其他对象使用。本文将详细介绍Java垃圾回收机制的工作原理、各个阶段的执行...

    Java垃圾回收机制的学习和使用

    Java虚拟机(JVM)的堆内存是存放对象的主要区域,当对象通过new等指令创建后,垃圾回收机制负责在适当的时候回收这些不再被引用的对象。 垃圾回收的意义在于提升程序的效率和安全性。在C++等语言中,程序员需要...

    JAVA垃圾回收面试个人总结.doc

    Java垃圾回收机制是Java编程中一个非常重要的概念,尤其在面试和实际开发中常常被讨论。垃圾回收(Garbage Collection, GC)是Java虚拟机自动管理内存的一种方式,旨在自动识别并释放不再使用的对象,从而避免内存...

    垃圾回收机制面试题·.docx

    ### 垃圾回收机制详解 #### 一、引言 在现代软件开发中,内存管理一直是程序设计中的一项重要任务。特别是在高级编程语言中,如何有效地管理和释放内存资源成为了衡量一个程序性能的关键因素之一。Java作为一种...

    java垃圾回收及内存泄漏.pptx

    1. **运行时数据区**:Java虚拟机管理的内存主要分为以下几个部分: - **方法区(Method Area)**:存储类的信息(如类名、字段、方法等)、常量、静态变量等。每个JVM实例只有一个方法区,被所有线程共享。方法区是...

    Java性能调优--关于垃圾回收机制的分析和指导

    Java性能调优,特别是关于垃圾回收机制的分析和指导,是优化Java应用程序的关键环节。Java的垃圾回收(Garbage Collection, GC)是自动管理内存的一种机制,它负责识别并清理那些不再使用的对象,以释放内存资源。...

    Java垃圾回收机制简述

    Java垃圾回收机制是Java虚拟机(JVM)中的一种机制,用于释放垃圾占用的空间,以便提高系统性能和避免内存泄露。在Java中,垃圾回收机制主要解决两个问题:如何确定某个对象是“垃圾”?采用什么样的策略来进行回收...

Global site tag (gtag.js) - Google Analytics