jnjj

  • 垃级回收面对的三个问题
    • 哪些内存需要被回收
    • 什么时候回收
    • 如何回收
  • 程序计数器、虚拟机栈、本地方法栈随着线程而生,随首线程而亡。栈中的栈帧随着方法的进入和退出而有条不紊的执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的。因此这几个区域的内存分配和回收都具备确定性,这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而JAVA堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期内才会知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存。(即垃圾回收器主要关注堆的内存回收)


对象是否存活判断
1.引用计数法

  • 引用计数法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就另1;当引失效时,计数器值 就减1;任何时刻计数器为0的对象就是不可能再被使用的。
  • 引用计数算法实现简单,判定效率也很高,但是在JAVA虚拟机里面是没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题




/**
* 循环引用测试
* testGC()方法执行后,objA和objB会不会被GC呢?
* 会的
*

*
* @author zzm
*/
public class _9ReferenceCountingGC {
  public Object instance = null;
  public static void main(String[] args) {
      _9ReferenceCountingGC objA = new _9ReferenceCountingGC();
      _9ReferenceCountingGC objB = new _9ReferenceCountingGC();
      objA.instance = objB;
      objB.instance = objA;
      objA=null;
      objB=null;
      /**
      * 假设在这行发生GC,objA和objB是否能被回收?
      * 会被回收!循环引用会被回收!
      * java没有使用引用计数器
      * 如果是引用计数器那么不会被回收
      *
      */
      System.gc();
  }
}

 -XX:+PrintGCDetails 可以查看GC日志

2.可达性分析算法

  • 在主流语音java、c#等的主流实现中,都是通过可达性分析来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为GC roots的对象作为起始点,从这些点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(即GC Roots这个对象不可达时),则证时此对象是不可用的。
  • java语言中,可作为GC Roots的对象包括以下几种
    • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中JNI(即一般说的Native方法)引用的对象
  • java 中引用强度分为
    • 强引用  强引用就是程序代码之中普遍存在的如Object obj = new Object()这类引用,只要强引用存在,垃级收集器永远不会回收掉被引用的对象
    • 软引用 软引用是用来描述一些还有,但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。SoftReferenc
    • 弱引用 弱引用也是用来描述非必需对象的,但是它的强度软引用理弱一些,被弱引用关联的对象只能生存到下一次垃圾收收集发生之前。当垃级收集器工作时,无论当前内存是否足够,者会回收掉只被弱引用关联的对象WeakReference来实现
    • 虚引用 虚引用也称为幽灵或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用,目的就是能在这个对象被收集器回收时收到一个系统通知。 PhantomReference
    • 四种依次减弱  ReferenceQueue是用来监听的,相当于这个作用
  • 下面的代码会让str再次变为一个强引用:

    String  abc = abcWeakRef.get();


  • 即使在可达性分析算法中不可达的对象,也并非是“非死不可的”,这时候他们处于缓弄阶段,要真正宣告一个对象死亡,至少要经历两次标记过程
    • 一个对象有两个标记:一个是否可达,一个是是否已经执行finalize方法
    • 如果对象在进行可达性分析后,发现没有与GC Root相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize方法。当对象没有覆盖finalize方法,或者finalize方法已经被 虚拟机调用过,虚拟机将这两种情况都视为没有必要执行。如果这个对象被判定为有必要执行finalize方法,那么这个对象将会放置在一个叫做F-Queue的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的finalizer线程去执行它。这里所谓的“执行”是指虚拟机触发这个方法,但并不承诺会等待它运行结束,这样做的原因是,如果一个对象地finalize方法执行缓慢,或者发生了死循环,将很可能导致F-Queue队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。finalize方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记。如果对象要在finalize中成功拯救自己,只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this 关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”集合;如果对象在finalize方法里还没有逃脱,那基本上它就直的被回收了。
    • 注意:Object方法的finallize方法只能被jvm执行一次,在这仅有的一个执行中,可以进行一次自救,仅仅是一次!以后自救一定会失败,因为一个对象的finallize只能被jvm调用一次!!!
    • fianllize方法仅仅是为了C\C++而做的妥协,不建议使用!
  • 方法区回收
    • 很多人认为方法区(或者HotSpot虚拟机中的永久代)是没有垃级收集的,java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法区中进行垃圾收集的“性价比”一般比较低。在堆中尤其是在新生代中,常规应用进行一次垃圾收集一般可以顺收70%-95%的空间,而永久代的垃圾收集效率远低于此。
    • 永久代的垃圾收集主要回收两部分内容废弃常量,和无用类。回收废弃常量与回收JAVA堆中的对象非常类似。以常量池为例,似如一个字符串“abc”已经进入了常量池中,但是当前系统没有任何一个String对象是叫做“abc”,换句话说,就是没有任何String对象引用常量池中的“abc”常量,也没有其他地方引用了这个字面量,那就这个字符串常量就可能被回收。常量池中其他类、方法、字段的符号引用也与此类似。
    • 判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”很苛刻。要满路下面3个条件。
      1. 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例
      2. 加载该类的ClassLoader已经被回收
      3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

                              -Xnoclassgc 关闭CLASS的垃圾回收功能  -verbose:class 在程序运行的时候有多少类被加载!你可以用verbose:class来监视 -XX:+TraceClassLoading   XX:+TraceClassUnLoading –监控类的加载/卸载

  • 大量使用反射,动态代理/CGLIB等ByteCode框架、动态生成jsp以及OSGI这类频繁自定义ClassLoader的场影都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

垃圾收集算法
  • 1.标记-清除算法
  • 标记阶段,GC ROOTS根对象开始进行遍历,对从GC ROOTS根对象 可以访问到的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象
  • 而在清除阶段,垃圾收集器对堆内存(heap memory)从头到尾进行线性的遍历,如果发现某个对象没有标记为可达对象-通过读取对象的header信息,则就将其回收。
  • 即  标记:标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。 清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。
  • 垃圾收集器在进行标记和清除阶段时会将整个应用程序暂停(mutator),等待标记清除结束后才会恢复应用程序的运行,这也是Stop-The-World这个单词的来历。
  • 缺点
    1. 就是垃圾收集后有可能会造成大量的内存碎片
    2. 它的缺点就是效率比较低(递归与全堆对象遍历)而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲
  • 运行期间如果想进行垃圾回收,就必须让GC线程与程序当中的线程互相配合,才能在不影响程序运行的前提下,顺利的将垃圾进行回收。





2.复制算法
  • 为了解决效率问题,出现『复制』算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。
  • 现在的商业虚拟机都采用这种收集算法来回收新生代。新生代中对象98%『朝生夕死』,所以并不需要按照1:1的例来划分内存空间,而是将内存分为一块较大的Eden和2块Survivor空间。每次使用Eden和其中一块Survivor.当回收时,将Eden和Survivor中还存活的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor大小为8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80+10),只有10%内存会被『浪费』。当然98%对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依整其他内存(老年代)进行分配担保。内存的分配担保就好比我们去银行借款,如果我们信誉良好,在98%的情况下都能按时还,于是银行可能会默认我们下一次也能按时按量地偿还,只要要有一个担保人能保证如果我不能还款时,可以从他的账户扣钱,那银行就认为没有风险了,内存的分配担保也一样,如果另外一块Survivor空间没有足够空间存放上一次新生代收集下来且存活对象时,这些对象将直接通过分配担保机制进入老年代。
  • 注意:万一存活对象数量比较多,那么To域的内存可能不够存放,这个时候会借助老年代的空间。
  • 年老代主要采用压缩的方式来避免内存碎片(将存活对象移动到内存片的一边,也就是内存整理)。当然,有些垃圾回收器(譬如CMS垃圾回收器)出于效率的原因,可能会不进行压缩。
  • 优点:
    • 没有碎片
    • 如果存活对象的数量比较大,coping的性能会变得很差。
    • 浪费内存空间,如果对象存活率较高时要执行较多的复制操作,效率降低
  • 也就是新生代,采用的是复制算法实现垃圾回收。而老年代,使用标记-整理算法


3.标记-整理算法
  • 老年代就使用这个算法
  • 这个算法解决了如果对象存活率较高时复制算法,会进行更多的操作。
  • 与标记-清除算法差不多,标记步骤是一样的,后续不是直接对可回收对象进行删除





4.分代收集算法
  • 当代商业虚拟机的垃圾收集都采用『分代收集』。一般是把java堆分为新生代和老年代,这样就以根据各个年代的特点采用不同的算法。新生代-复制算法(每次垃圾收集时都发现有大批对象死去,只有少量存活)。老年代-使用标记清理或者标记整理算法(因为对象成活率很高)















HotSpot的算法实现

枚举根节点
         从可达性分析中从GC Roots节点找引用链这个操作为例, 可作为GC Roots的节点主要在全局性的引用(常量池或类列态属性)与执行上下文(栈帧中本地变量表),现在很多应用仅仅方法区就有数百兆,如果要逐个检查这里面的引用,那么必然会消耗很多时间。
        另外, 可达性分析对执行时间的敏感还休现在GC停顿上,因为这项分析工作必须在一个能确保一致性的快照中进行—这里『一致性』的快照中进行, 不可以出现分析过程中对象引用关系还在不断变化的情况这点是导致GC进行时必须停顿所有的JAVA执行线程(SUN 将这件事情称之为 Stop The World),即使是在号称(几乎)不会发生停顿的CMS收集器中,枚举根节点也是几须要停顿的
         目前主流JAVA虚拟机使用的都是 准确式GC,所以当执行系统停顿下来后,并不需要一个不漏的检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得知哪些地方存放着对象引用。在HotSpot中,使用一组称为OopMap的数据结构来达到这个目的的,在类加载完成的时候HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。这样GC在扫描时就可以直接得知这些信息了。



安全点
         在OopMap的协助下,HotSpot可以快速且准确地完成GC Roots枚举,但一个很现实的问题随这而来:可能导致引用关系变化,或者说OopMap内容变化指令非常多,如果为每个指令都生成对应的OopMap,将会消耗很多额外空间,这样GC成本会变高。
         HopSpot没有为每条指令都生成OopMap,只在安全点记录了这些信息,即程序
执行时并非在所有地方都能停顿下来开妈GC,只有在到达安全点时才能暂停。 安全点的选定基本是以『程序是否有让程序长时间执行的特征』为标准进行选定的
       如何上所有线程跑到最近的安全点上再停顿下来?有两种方案: 抢先式中段和主动式中断。
安全区域
         SafePoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的安全点中。但是程序不执行,如sleep或者block了,那么这时候线程无法响应JVM中断请求,走到安全点。此时需要安全区域
          安全区域指引用关系不会发生变化。这这个区域中的任意地方开始GC 都是安全的。
         在线程执行到安全区域的时候,首先标识自己进入了safe region那么,当在这段时间里JVM 要发起GC 时,就不用管标识自己把自己标识为SafeRegion状态的线程了。在线程要离开safeRegion时,它要检查系统是否已经完成了对根节点枚举,或者是整个GC过程,如果完成了,那线程继续执行,否则它就必须等待直到收到可以安全离开safe region的信号为止
































垃圾收集器





有连线的可以搭配使用
CMS — Concurrent Mark Sweep





分代No名称优点缺点开启参数算法 使用场景since version备注
新生代1Serial简单高效stop the world-XX:+UseSerialGC单线程、新生代-复制算法
老生代-标记整理
内存较小的client模式下首选的新生代垃圾收集器jdk1
新生代2ParNew多cpu环境下,比Serial效果好stop the world、cpu核数少时线程切换的开销使得没有serial性能好-XX:+UseParNewGC或
-XX:+UseConcMarkSweepGC
多线程并行、复制算法server首选、jdk6之前,老年代用CMS,新生代只能用Serial或ParNew除了serial只有它可以与CMS配合工作
新生代3Parallel Scavenge或称throughput收集器高吞吐量、带有GC Ergonomics(gc自适应调节策略,使用-XX:+UseAdaptiveSizePolicy开启),无需手工指定新生代大小,survivor比例,晋升老年代对象的年龄等参数。stop the world;不适合响应时间要求高/交互频繁的应用‘-XX:+UseParallelGC或-XX:+UseParallelOldGC多线程、复制算法关注高吞吐量并且响应性要求不高的应用可以自动优化UseParallelGC时老年代为SerialOldGC,UseParallelOldGC时老年代为ParallelOldGC
老年代4Serial Old简单高效stop the world‘-XX:+UseSerialGC或’-XX:+UseParNewGC单线程、标记整理client首选,搭配ParNew使用,或者在jdk6之前搭配Parallel Scavenge使用,也可在server模式下作为CMS的备胎(CMS可能会因为浮动垃圾而发生concurrent mode failure的错误,此时需要serial old上位)
老年代5Parallel Old高吞吐量stop the world,不适合响应时间要求高的应用-XX:+UseParallelOldGC多线程并行,标记整理server模式默认选项。搭配Parallel Scavenge,关注吞吐量及cpu资源敏感的场合jdk6该选项自动设置 -XX:+UseParallelGC选项
老年代6CMS停顿时间短,响应性高对cpu资源非常敏感;无法处理浮动垃圾,可能会出现Concurrent mode failure;标记清除算法容易产生垃圾碎片-XX:+UseConcMarkSweepGC多线程并发,标记清除要求高响应性的互联网站或BS服务端jdk5打开此开关参数后,使用ParNew+CMS+Serial Old收集器组
老年代7G1当内存很大时,停顿时间高,并且吞吐量高内存较小时性能没有CMS好-XX:+UseG1GC使用空间整合算法,堆被划分成多个连续的大小相等的region,新生代老年代物理上不再隔离,新生代老年代各自在内部也不再要求连续。内存大于6G的高并发低停顿应用jdk7可能会在java9时作为默认GC







Serial收集器

Serial垃圾收集器,通过这个单词的意思“连续”,那么必然不会采用“标记清除算法”来实现,所以这个垃圾收集器在 新生代使用的是“复制算法”,而 Serial Old作为Serail收集器的老年代版本,使用的就是“标记-整理算法”。
收集器是最基本、历史最悠久的收集器,在JDK1.3.1之前,是JVM新生代收集的唯一选择。
Serial收集器是一个单线程的收集器,这个“单线程”是指JVM在使用它进行GC的时候,必须暂停其他所有的工作线程(sun将这件事情称为“Stop the world”),直到GC完成,这是一件非常可怕的事情。看到这里,你可能想我一定要修改我的JVM的新生代收集器,不用Serial了,但是直至现在,Serial依然是JVM在运行Client模式下默认的新生代 收集器。与其他垃圾收集器的单线程相比,Serial简单而高效。 对于用户桌面应用场景来说,分配给JVM的内存一般不会太大,收集十几甚至一两百兆的内存,停顿时间可以控制在几十毫秒,最多一百多毫秒以内,只要不是特别频繁,这些停顿还是可以接受的。所以,对于 Client模式下的JVM来说,Serial是个很好的新生代收集器, 简单高效



ParNew收集器

  • 单环境下还不好Serial呢
  • 和serial基本一样,只是垃圾收集的时候用了多线程而已
  • 只有serial 和 parNew可以与CMS合作!
  • 可以使用 -XX:ParallelGCThreads参数来限制垃圾收集的线程数

ParNew收集器也是一个新生代收集器,其实就是Serial收集器的多线程版本,是一个并行”的垃圾收集器,除了多线程外,其他和Serial差不多。想想也就明白了,当JVM团队开发出来了Serial,可以满足Client模式下的JVM,但是对于Server模式下的JVM来说,运行很长时间,有很多的对象需要收集(可能几十个G),单线程导致的停顿时间太长了(比如每运行1小时需要停顿5分钟),用户无法接受业务线程停顿那么长的时间,我猜测这种情况下那些大牛能想到的最简单的办法就是让Serial变成多线程,这样开多个线程就可以有效的降低停顿时间,故而这个Serial的多线程版本也就诞生了。

    ParNew是许多运行在Server模式下的JVM中首选的垃圾收集器,其中一个重要原因就是除了Serial,它是唯一可以和CMS(Concurrent Mark Sweep)老年代垃圾收集器配合工作。

    ParNew在单CPU环境中收集效果不如Serial收集器,但是随着CPU的增加,它对于GC时系统资源的利用还是很有好处的,默认开启的线程数与CPU的数量一致 。

Parallel Scavenge收集器  平行扫描

  • 新生代收集器
  • 复制算法、多线程
  • 关注点不是缩短垃圾收集器收集时用户线程停顿的时间,而是关注达到一个可控制的吞吐量。吞吐量=运行用户代码的时间/(运行用户代码时间+垃圾收集时间),即尽可能的缩短垃圾收集的时间
  • 停顿时间越短,越适合需要与用户交互的程序,如web;高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务
  • 吞吐   该收集器提供了两个参数用于精确控制吞吐量
    • -XX:GCTimeRatio  设置吞吐量大小 0-100的值 如果此参数设为9 吞吐量为1/(1+19)=5% 即5%的时间用来垃圾回收,默认值为99,即允许量大1%的时间用于垃圾收集。
    • -XX:MaxGCPauseMillis  控制最大垃圾收集停顿时间,允许一个大于0的数,GC尽可能保证内存回收花费的时间不超过设定值。GC停顿时间是以牺牲吞吐量和新生代空间来换取的。系统把新生代调小一些,收集时间少了,但更频繁了。
    • -XX:+UseAdaptiveSizePolicy。这是一个开关参数,当这个参数打开之后,就不需要手工指定新生代大小-Xmn   eden和Survivor比例   -XX:SurvivorRatio 晋升老年代对象年龄  -XX:PretenureSizeThreshold等细节参数了。虚拟机会概据当前系统的运行情况收集性参监控信息,动态调整这些参数以提供最合便宜的停顿时间或最大吞吐量,这种调节方式称为GC自适应的调节策略。如果对收集器运作不太了解,手工优化存在困难,用 Parallel Scavenge配合自适应调节策略,把内存管理的调优任交给虚拟机去完成是一个不错的选择。也可以再指定-XX:MaxGCPauseMillis   -XX:GCTimeRatio来设定一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。
    • 自适应调节是 Parallel Scavenge与ParNew 最大区别
    • Parallel Scavenge收集器正是基于对“吞吐量”的追求而产生的,它的目标就是达到一个可控的吞吐量








Serial old收集器

  • serial old 是serial收集器的老年代版本,同样是单线程的收集器。使用标记整理算法
  • 这个收集器主要意义也是在于给Client单机模式下 的虚拟机使用
  • 在server模式下,它主要有两大用途
    • 在JDK以及的版本中与Parallel Scavenge收集器搭配使用
    • 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure 时使用。





Parallel old收集器

  • parallel old 是Parallel Scavenge收集器的老年代版本,使用多线程和“标记整理”算法。
  • 这个收集在JDK1.6中提供,在此之前Parallel Scavenge配套使用的老年代收集器只能是Serial old.由于serial old是单线程的拖累了Paralllel Scavenge。并不能达到高吞吐量。直到Paralle old收集器出现以后,“吞吐量优先”收集器终于有一比较名副共实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Paral Scavenge加parallel old




CMS (Concurrent Mark Sweep 同时标记清除)收集器

  • CMS是一种以获取最短回收停顿时间为目标的收集器
  • B/S系统的服务器注重服务的响应速度,所以CMS满足需求。
  • 使用标记-清除算法。
  • 过程
    1. 初始标记 CMS initial mark
    2. 并发标记 CMS concurrent mark
    3. 重新标记  CMS remark
    4. 并发清除  CMS concurrent sweep
 初始标记、重新标记仍然要Stop the world。初始标记仅仅是标记一下GC ROOTS 能直接关联到的对象,速度很快,并发标记除段就是进行GC ROOTS Tracing的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部份对象的标记记录,这个阶段的停顿会比初始标记阶段稍长一些,但远比并发标记时间短。
整个过程中耗时最长的并发标记和并发清除过程收集器线程都是可以与用户线程一起工作,所以,从总体上 来说CMS收集器的内存回收过程与用户线程一起并发执行

  • 特点:并发收集、低停顿收集器。
  • 缺点:
    • CMS对cpu资源敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部份线程或者说CPU而导致应用程序变慢,总吞吐量变低。CMS默认启动收线程数是(CPU数量+3)/4,也就是当CPU以4个以上时并发回收时垃圾收集线程不少于占用25%的CPU资源,并且随着CPU数量的增加而下降。但是当CPU不足4个比如2个,CMS对用户程序的影 响就可能变得更大,本来CPU就不够用,还分出一半给垃圾收集器去使用。所以虚拟机提供了一种称为“增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)的CMS收集器变种,所做的事情和单CPU年代PC机操作系统使用抢占式来模拟多任务机制的思想一样,就是并发标记、清理时让GC线程、用户线程效替运行,尽量减少GC线程的独占资源时间,这样整个垃圾收集的过程会更长,但对用户程序的影 响就会显得少一些,也就是垃圾下降没有那么明显。实践证明,增量式的CMS收集器效果很一般,I-CMS已被废弃,不提倡使用。
    • CMS无法处理浮动垃圾(Floating Garbage),可能出现Concurrent Mode Failer 失败而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行,伴随程序运行自然就还有新的垃圾不断产生,这一部份出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就叫“浮动垃圾”。因此CMS收集器不能像其他收集器那样等到老年代被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运行使用。在JDK1.5默认设置下,CMS收集器当老年代用了68%的空间后就被会激活,这是一个遍保守的设置,如果在就用中老年代增长不是太快,可以适当调高-XX:CMSInitiatingOccupancyFraction的值来提高百分比,以便降低内存回收次数从而获取更好的性能。在JKD1.6中默认值为92%。要是CMS运行期间预留内存无法满程序需要,就会出现一次“Concurrent Mode Failure”,这时虚拟机启动后备预案:临时启用SerialOld收集器来进行重新进行老年代的垃圾收集,这样停顿时间就长了,所以-XX:CMSInitiatingOccupancyFraction这个设置不能太大,反而性能降低
    • 因为使用的是标记清除算法,所以会产生大量碎片。当后续大对象分配空间的时候,会出现老年代还有很大空间剩余,但是无法找到足够大的空连续空间来分配当前对象,不得不提前触发一次FULL GC 。为了解决这个问题,CMS收集器提供了一个-XX:+UserCMSCompactAtFullCollection开关参数(默认开启),用于在CMS收集器顶不住要进行FullGC时开启内存碎片合并整理过程,内存整理过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。虚拟机还提供了另一个参数-XX:CMSFullGCBeforeCompaction,这个参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都要进行丈人片整理





G1(Garbage-First)收集器

  • G1收集器 JDK 7u4的时候才商用。
  • G1是一款面向服务端应用的垃圾收集器。
  • 它的使命是替换掉JDK1.5中发布的CMS收集器。
  • 特点
    • 并行与并发:G1充分利用多CPU、多核心的硬件优势,使用多CPU来缩短STOP-THE-WORLD停顿时间,部份其它收集器原本需要停顿JAVA线程执行GC动作,G1收集器仍然通过并发的方式让用户程序继续执行。
    • 分代收集:G1可以直接管理老年代和年轻代,不需要其它GC收集器便可独立工作,它能够采用不同的方式去处理新创建的对象和已经存活了一段时间熬过了多次GC的旧对象以获取更好的收集效果
    • 空间整合:看似是标记清理,但局部是标记复制算法。都不会有碎片的产生。所以收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分本大对象时不会因为会儿法找到连纪内存空间而提前触发下一次GC
    • 可预测的停顿:这是G1与CMS相比的优势,降低停顿是G1和CMS共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时JAVA 的垃圾收集器特征了。
实验看下来 G1 只有一个Survivor区?
  • 初始标记和最终标记阶段都需要暂停用户线程
在G1之前每种收集器只能收集新生代或老年代, 而G1可以收集老年代和新生成,G1会把JAVA堆划分为多个大小相等的独立区域,虽然还保留有新生代和老年代的概念,但新生代和老年不再是特理隔离,它们都是一部份区域的集合
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整 个JAVA堆中进行全区域的垃圾收集(不懂啥意思)。G1跟踪名个区域里面的垃圾堆积的价值大小(加收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,,优先回收价值最大的Region(也就是Garbage-First)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
以前的代分收集中,新生代的规模一般老年代要小许多,新生代收集比老年代也要频繁
          G1中Region之间的对象引用以及期他收集器中新生工与老年代之间的对象引用,虚拟机都是使用Remembered Set来买吧这名全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(在分代的例子中就是栓查是否老年代中的对象引用了新生代中的对象),如果是,便通过CardTable把相妆引用信息记录到被引用对象所属的Region的remembered set之中,当进行内存回收时,在GC根节点的枚举范围中另入 Remembered set 即可保证不对全堆扫描也会不会有贵漏(不理解。。。。。)。

  • 过程:
    • 初始标记
    • 并发标记
    • 最终标记
    • 筛选回收     其它略。。看不懂。。













































GC日志


[ GC [DefNew: 4928K->512K(4928K), 0.0038992 secs] 6592K->2884K(15872K), 0.0039332 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 4928K->286K(4928K), 0.0026634 secs] 7300K->3379K(15872K), 0.0026951 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 4702K->335K(4928K), 0.0030641 secs] 7795K->3714K(15872K), 0.0030949 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System) [Tenured: 3378K->3897K(10944K), 0.0177885 secs] 5632K->3897K(15872K), [Perm : 2308K->2308K(12288K)], 0.0178426 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[Full GC (System) [Tenured: 3897K->3858K(10944K), 0.0165858 secs] 4020K->3858K(15936K), [Perm : 2308K->2308K(12288K)], 0.0166245 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[Full GC (System) [Tenured: 3858K->3866K(10944K), 0.0169109 secs] 3962K->3866K(15936K), [Perm : 2308K->2308K(12288K)], 0.0169604 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]


  • GC/Full GC 表示了垃级收集停顿的类型。如果有Full说明是发生了stop-the-world的情况
  • System.gc()是Full GC.
  • [DefNew: 对应区域收集前已使用量->收集后已使用量(该区域的总容易), 0.0030641 secs] GC前java堆总容量->GC后java堆总容易(java堆总容量), 0.0030949 secs]






常用垃圾收集器参数
-XX:+UseSerialGC
Jvm运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old的收集器组合进行内存回收
-XX:+UseParNewGC打开此开关后,使用ParNew + Serial Old的收集器进行垃圾回收
-XX:+UseConcMarkSweepGC使用ParNew + CMS +  Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现“Concurrent Mode Failure”失败后的后备收集器使用。
-XX:+UseParallelGCJvm运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge +  Serial Old的收集器组合进行回收
-XX:+UseParallelOldGC使用Parallel Scavenge +  Parallel Old的收集器组合进行回收
-XX:SurvivorRatio新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Subrvivor = 8:1
-XX:PretenureSizeThreshold直接晋升到老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配
-XX:MaxTenuringThreshold晋升到老年代的对象年龄,每次Minor GC之后,年龄就加1,当超过这个参数的值时进入老年代
-XX:UseAdaptiveSizePolicy动态调整java堆中各个区域的大小以及进入老年代的年龄
-XX:+HandlePromotionFailure是否允许新生代收集担保,进行一次minor gc后, 另一块Survivor空间不足时,将直接会在老年代中保留
-XX:ParallelGCThreads设置并行GC进行内存回收的线程数
-XX:GCTimeRatioGC时间占总时间的比列,默认值为99,即允许1%的GC时间,仅在使用Parallel Scavenge 收集器时有效
-XX:MaxGCPauseMillis设置GC的最大停顿时间,在Parallel Scavenge 收集器下有效
-XX:CMSInitiatingOccupancyFraction设置CMS收集器在老年代空间被使用多少后出发垃圾收集,默认值为68%,仅在CMS收集器时有效,-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSCompactAtFullCollection
由于CMS收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在CMS收集器时有效
-XX:+CMSFullGCBeforeCompaction
设置CMS收集器在进行若干次垃圾收集后再进行一次内存碎片整理过程,通常与UseCMSCompactAtFullCollection参数一起使用
-XX:+UseFastAccessorMethods
原始类型优化
-XX:+DisableExplicitGC
是否关闭手动System.gc
-XX:+CMSParallelRemarkEnabled
降低标记停顿
-XX:LargePageSizeInBytes
内存页的大小不可设置过大,会影响Perm的大小,-XX:LargePageSizeInBytes=128m


-XX:SurvivorRatio=1










































内存分配与回复策略


  • java提倡的内存管理归结为2个问题:内存分配与内存回收
  • 对象内存分配,一般就是在堆上分配内存(JIT编译后可能间接往栈上分配),对象主要分配在eden区上,(如果启用了线程分配缓冲,将按照优先在TLAB上分配,少数情况直接分配在老年代中

TLAB的解释

     堆内的对象数据是各个线程所共享的,所以当在堆内创建新的对象时,就需要进行锁操作。锁操作是比较耗时,因此JVM为每个线在堆上分配了一块“自留地”——TLAB(全称是Thread Local Allocation Buffer),位于堆内存的新生代,也就是Eden区。每个线程在创建新的对象时,会首先尝试在自己的TLAB里进行分配,如果成功就返回,失败了再到共享的Eden区里去申请空间。在线程自己的TLAB区域创建对象失败一般有两个原因:一是对象太大,二是自己的TLAB区剩余空间不够。通常默认的TLAB区域大小是Eden区域的1%,当然也可以手工进行调整,对应的JVM参数是-XX:TLABWasteTargetPercent。



对象优先在Eden分配
  • 大多数情况下,对象在新生代的eden分区中分配。当eden没有足够的空间时,虚拟机将发起一次MinorGC
  • MinorGC 也就是新生代GC 发生在新生代的GC动作
  • Major GC/Full GC 发生在老年代的GC.


public class _11_TestAllocation {
    private static final int _1MB = 1024 * 1024;

    /**
    *  初始堆大小  -Xms20M
    *  最大堆大小  -Xmx20M
    *  新生代大小  -Xmn10M
    *  -XX:SurvivorRatio=8  eden  servival之比
    *  -XX:+PrintGCDetails  打印gc日志
    *
    *  所以
    *  old 10M
    *  Young 10m (eded 8  survival 1  survival 1)
    * VM参数:-verbose:gc -Xms20M -Xmx20M    -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
    */

/*

[GC [DefNew: 7514K->572K(9216K), 0.0052474 secs] 7514K->6716K(19456K), 0.0052834 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
def new generation  total 9216K, used 4998K [0x32690000, 0x33090000, 0x33090000)
eden space 8192K54% used [0x32690000, 0x32ae26e0, 0x32e90000)
from space 1024K55% used [0x32f90000, 0x3301f190, 0x33090000)
to  space 1024K,  0% used [0x32e90000, 0x32e90000, 0x32f90000)
tenured generation  total 10240K, used 6144K [0x33090000, 0x33a90000, 0x33a90000)
the space 10240K,  60% used [0x33090000, 0x33690030, 0x33690200, 0x33a90000)
compacting perm gen  total 12288K, used 209K [0x33a90000, 0x34690000, 0x37a90000)
the space 12288K,  1% used [0x33a90000, 0x33ac4530, 0x33ac4600, 0x34690000)
ro space 10240K,  45% used [0x37a90000, 0x37f17988, 0x37f17a00, 0x38490000)
rw space 12288K,  54% used [0x38490000, 0x38b1b5d8, 0x38b1b600, 0x39090000)

*/
    public static void testAllocation() throws InterruptedException {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB];  // 出现一次Minor GC

    }

    public static void main(String[] args) throws InterruptedException {
        testAllocation();
    }
}

大对象直接进入老年代
  • 大对象指,需要大量连续的内存空间,比如很长的字符串或者数组
  • 虚拟机讨厌大对象,更讨厌寿命短的大对象,代码中应当避免。不然会出现还有很多空间,但要进行垃圾回收动作。
  • -XX:PretenureSizeThreshold=104857 (1M)可以通过这个参数,设置大于多少的对象,直接放入old区。这个目的是避免也eden和两个survivor之间进行大量的内存复制。该参数只对serial  Parnew两个收集器起做用。parallel Scavenge不认识这个参数,如果一定要做用这个参数,可以使用parnew 和cms收集器的组合




/**
* 大对象直接放old区中
*
* 可以通过  -XX:PretenureSizeThreshold=1048567  (1M) 直接指定,超过多少的大对象直接放old区
* Created by liuquan on 2016/12/9 17:27
*
*
*  初始堆大小  -Xms40M
*  最大堆大小  -Xmx40M
*  新生代大小  -Xmn20M
*  -XX:SurvivorRatio=8  eden  servival之比
*  -XX:+PrintGCDetails  打印gc日志
*-XX:PretenureSizeThreshold  超过这个大小直接在老年代中分配
*  所以
*  old 20M
*  Young 20m (eded 16  survival 2  survival 12)
* VM参数:-verbose:gc -Xms40M -Xmx40M    -Xmn20M -XX:+PrintGCDetails -XX:SurvivorRatio=8    -XX:PretenureSizeThreshold=1048567
*
*
* 超过1M放到old区 1024*1024=1048576
*-verbose:gc -Xms40M -Xmx40M    -Xmn20M -XX:+PrintGCDetails -XX:SurvivorRatio=8    -XX:PretenureSizeThreshold=104857
* 放一个9m的文件,那么eden和survival都放不下
*/
public class BigObject_To_Old_Region {
    private static final int _1M = (1024*1024+5555);

    public static void main(String[] args) {
        byte b[] = new byte[_1M];

    }
}


-verbose:gc -Xms40M -Xmx40M    -Xmn20M -XX:+PrintGCDetails -XX:SurvivorRatio=8  -XX:PretenureSizeThreshold=104857

Heap
 def new generation   total 18432K, used 1990K [0x31290000, 0x32690000, 0x32690000)
  eden space 16384K,  12% used [0x31290000, 0x31481ae0, 0x32290000)
  from space 2048K,   0% used [0x32290000, 0x32290000, 0x32490000)
  to   space 2048K,   0% used [0x32490000, 0x32490000, 0x32690000)
  tenured generation   total 20480K, used 1029K [0x32690000, 0x33a90000, 0x33a90000)
   the space 20480K,   5% used [0x32690000, 0x327915c0, 0x32791600, 0x33a90000)
 compacting perm gen  total 12288K, used 188K [0x33a90000, 0x34690000, 0x37a90000)
   the space 12288K,   1% used [0x33a90000, 0x33abf2f0, 0x33abf400, 0x34690000)
    ro space 10240K,  45% used [0x37a90000, 0x37f17988, 0x37f17a00, 0x38490000)
    rw space 12288K,  54% used [0x38490000, 0x38b1b5d8, 0x38b1b600, 0x39090000)




-verbose:gc -Xms40M -Xmx40M    -Xmn20M -XX:+PrintGCDetails -XX:SurvivorRatio=8 

Heap
 def new generation   total 18432K, used 3020K [0x31290000, 0x32690000, 0x32690000)
  eden space 16384K,  18% used [0x31290000, 0x315830a0, 0x32290000)
  from space 2048K,   0% used [0x32290000, 0x32290000, 0x32490000)
  to   space 2048K,   0% used [0x32490000, 0x32490000, 0x32690000)
 tenured generation   total 20480K, used 0K [0x32690000, 0x33a90000, 0x33a90000)
   the space 20480K,   0% used [0x32690000, 0x32690000, 0x32690200, 0x33a90000)
 compacting perm gen  total 12288K, used 209K [0x33a90000, 0x34690000, 0x37a90000)
   the space 12288K,   1% used [0x33a90000, 0x33ac4430, 0x33ac4600, 0x34690000)
    ro space 10240K,  45% used [0x37a90000, 0x37f17988, 0x37f17a00, 0x38490000)
    rw space 12288K,  54% used [0x38490000, 0x38b1b5d8, 0x38b1b600, 0x39090000)



长期存活的对象将进入老年代
  • JVM采用了分代收集的思想管理内存。
  • 虚拟机给每个对象定义了一个对象年龄的计数器如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且能被Ssurvivor容纳的话,将被移动到survivor空间中,并且对象年龄设为1.对象在Survivor区中每“熬过”一个MinorGC(新生代gc),对象年龄就加1,当它的年龄增加到一定程度,“默认为15”,将就被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold来设置
  • -XX:MaxTenuringThreshold  设置对象年龄为多少的时候被放到old中


/**
* MaxTenuringThreshold来设置对象年龄为多少候进入老年代。默认为15
* Created by liuquan on 2016/12/12 10:49
*/
public class _12testTenuringThreshold {
    private static final int _10MB = 1024 * 1024*10;

    /**
    * VM参数:-verbose:gc -Xms200M -Xmx200M -Xmn100M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1
    * -XX:+PrintTenuringDistribution
    *
    *
    *
    *  -Xms200M  最小堆200M
    *  -Xmx200M  最大堆200M
    *  -Xmn100M  新生代100M
    *  -XX:+PrintGCDetails  打印gc日志
    *  -XX:SurvivorRatio=8  survival和eden之比 ( eden 为80M ,survival为10M)
    *  -XX:MaxTenuringThreshold=1    对象年龄为1的就候就去老年代了
    */
    @SuppressWarnings("unused")
    public static void testTenuringThreshold() throws InterruptedException {

      // Thread.sleep(10000);
        byte[] allocation1, allocation2, allocation3;
        allocation1 = new byte[_10MB / 4];  // 什么时候进入老年代决定于XX:MaxTenuringThreshold设置
        allocation2 = new byte[4 * _10MB];
        allocation3 = new byte[4 * _10MB];  //这里新生代垃圾回收一次
        allocation3 = null;
        allocation3 = new byte[4 * _10MB];

        //Thread.sleep(10000);
    }

    public static void main(String[] args) throws InterruptedException {
        new _12testTenuringThreshold().testTenuringThreshold();
    }
}




-verbose:gc -Xms200M -Xmx200M -Xmn100M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1


[GC [DefNew: 46796K->3133K(92160K), 0.0184738 secs] 46796K->44093K(194560K), 0.0185106 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
[GC [DefNew: 45731K->0K(92160K), 0.0021731 secs] 86691K->44043K(194560K), 0.0021917 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
 def new generation   total 92160K, used 42598K [0x27290000, 0x2d690000, 0x2d690000)
  eden space 81920K,  52% used [0x27290000, 0x29c29a10, 0x2c290000)
  from space 10240K,   0% used [0x2c290000, 0x2c290088, 0x2cc90000)
  to   space 10240K,   0% used [0x2cc90000, 0x2cc90000, 0x2d690000)
 tenured generation   total 102400K, used 44043K [0x2d690000, 0x33a90000, 0x33a90000)
   the space 102400K,  43% used [0x2d690000, 0x30192dd8, 0x30192e00, 0x33a90000)
 compacting perm gen  total 12288K, used 209K [0x33a90000, 0x34690000, 0x37a90000)
   the space 12288K,   1% used [0x33a90000, 0x33ac4540, 0x33ac4600, 0x34690000)
    ro space 10240K,  45% used [0x37a90000, 0x37f17988, 0x37f17a00, 0x38490000)
    rw space 12288K,  54% used [0x38490000, 0x38b1b5d8, 0x38b1b600, 0x39090000)


-verbose:gc -Xms200M -Xmx200M -Xmn100M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
[GC [DefNew: 46796K->3133K(92160K), 0.0213891 secs] 46796K->44093K(194560K), 0.0214255 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
[GC [DefNew: 45731K->3083K(92160K), 0.0029106 secs] 86691K->44043K(194560K), 0.0029382 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
 def new generation   total 92160K, used 46532K [0x27290000, 0x2d690000, 0x2d690000)
  eden space 81920K,  53% used [0x27290000, 0x29cfe4f8, 0x2c290000)
  from space 10240K,  30% used [0x2c290000, 0x2c592e50, 0x2cc90000)
  to   space 10240K,   0% used [0x2cc90000, 0x2cc90000, 0x2d690000)
 tenured generation   total 102400K, used 40960K [0x2d690000, 0x33a90000, 0x33a90000)
   the space 102400K,  40% used [0x2d690000, 0x2fe90010, 0x2fe90200, 0x33a90000)
 compacting perm gen  total 12288K, used 209K [0x33a90000, 0x34690000, 0x37a90000)
   the space 12288K,   1% used [0x33a90000, 0x33ac4560, 0x33ac4600, 0x34690000)
    ro space 10240K,  45% used [0x37a90000, 0x37f17988, 0x37f17a00, 0x38490000)
    rw space 12288K,  54% used [0x38490000, 0x38b1b5d8, 0x38b1b600, 0x39090000)

动态对象年龄判断

  • 为了更好的适应不同程序的内存状况,虚拟机并不是一定等到-XX:MaxTenuringThreshold才进入老年代。
  • 如果在Survivor 空间中相同年龄所有对象大小的总和大于Survivor空间一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。不是很理解。。。。。。。实验也没看懂



/**
* 动态对象年龄判断
*
* 为了更好的适应不同程序的内存状况,虚拟机并不是一定等到-XX:MaxTenuringThreshold才进入老年代。
* 如果在Survivor 空间中相同年龄所有对象大小的总和大于Survivor空间一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

* Created by liuquan on 2016/12/12 11:27
*/
public class _13testTenuringThreshold2 {
    private static final int _10MB = 1024 * 1024*10;

    /**
    * VM参数:-verbose:gc -Xms200M -Xmx200M -Xmn100M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
    *
    * -verbose:gc
    * -Xms200M
    * -Xmx200M
    * -Xmn100M
    * -XX:+PrintGCDetails
    * -XX:SurvivorRatio=8
    * -XX:MaxTenuringThreshold=15
    *
    *
    * ===========
    * 老年代 100
    * eden 80
    * survival  10
    * -XX:+PrintTenuringDistribution
    *
    * 在日志中,我们发现Survivor空间的使用率为0,也就是说allocation1 ,allocation2对象都进入到了老年代中,而没有等到15岁的年龄。
    */
    @SuppressWarnings("unused")
    public static void testTenuringThreshold2() throws Exception {
        byte[] allocation1, allocation2, allocation3, allocation4;
        //Thread.sleep(10000);
        allocation1 = new byte[_10MB / 4];  // allocation1+allocation2大于于survivo空间一半
        allocation2 = new byte[_10MB / 4];
        allocation3 = new byte[4 * _10MB];
        allocation4 = new byte[4 * _10MB];
        allocation4 = null;
        allocation4 = new byte[4 * _10MB];
    }

    public static void main(String[] args)  throws Exception{
        testTenuringThreshold2();
    }
}


[GC [DefNew: 49356K->5692K(92160K), 0.0213928 secs] 49356K->46652K(194560K), 0.0214236 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
[GC [DefNew: 48291K->0K(92160K), 0.0042351 secs] 89251K->46603K(194560K), 0.0042612 secs] [Times: user=0.00 sys=0.02, real=0.01 secs]
Heap
 def new generation   total 92160K, used 42598K [0x27290000, 0x2d690000, 0x2d690000)
  eden space 81920K,  52% used [0x27290000, 0x29c29a10, 0x2c290000)
  from space 10240K,   0% used [0x2c290000, 0x2c290088, 0x2cc90000)
  to   space 10240K,   0% used [0x2cc90000, 0x2cc90000, 0x2d690000)
 tenured generation   total 102400K, used 46603K [0x2d690000, 0x33a90000, 0x33a90000)
   the space 102400K,  45% used [0x2d690000, 0x30412d70, 0x30412e00, 0x33a90000)
 compacting perm gen  total 12288K, used 209K [0x33a90000, 0x34690000, 0x37a90000)
   the space 12288K,   1% used [0x33a90000, 0x33ac4558, 0x33ac4600, 0x34690000)
    ro space 10240K,  45% used [0x37a90000, 0x37f17988, 0x37f17a00, 0x38490000)
    rw space 12288K,  54% used [0x38490000, 0x38b1b5d8, 0x38b1b600, 0x39090000)



空间分配担保(做用,避免多次full GC)

  • 在发生Minor GC之前 (新生代GC),虚拟机会先检查老年代量大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么MinorGC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置是否允午但保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平无大小,如果大于,将尝试一次MinorGC,尽管这次MinorGC是有风险的;如果小于,或者HandlePromotionFailure设置为不允许冒险,那这时也要改为进行一次FullGC
  • HandlePromotionFailure通常设为打开。默认打开的
  • JDK6.24之后HandlePromotionFailure这个参数就没用了。
  • JDK6.24之后规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平无大小就会进行MinorGC,否则FullGC.




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值