您的位置:首页 > 编程语言 > Java开发

深入理解Java虚拟机JVM高级特性与最佳实践阅读总结——第三章垃圾收集器与内存分配策略

2017-02-28 22:47 1061 查看
引用计数法:给对象添加引用计数器,每当有一个地方引用它时,计数器值加1,;引用失效则计数器值减一,任何计数值为0的对象就是不可能被使用的
特点:简单高效,但不能解决对象之间的循环引用

可达性分析算法:以GC Roots作为起点,从这些节点开始向下搜索,搜索路径称为引用链,当一个对象到GC Roots之间不存在引用链时,该对象就是不可用的
可以用作GC Roots的对象,一般是全局性引用(2、3)和执行上下文(1,4):
          1、虚拟机栈中引用的对象;
          2、方法区中静态属性引用的对象;
          3、方法区中常量引用的对象;
          4、本地方法区中JNI(即native方法)引用的对象

JDK对引用的定义:
1.2以前:reference中存储的数值是某个内存空间的起始地址,就称对该内存的引用;
定义的缺点:对象只存在被引用或不被引用两种状态,过于狭隘
1.2对引用的扩展:
          1、强引用Strong Reference:类似于new之类的引用,只要强引用还存在,GC就不会回收对象;
          2、软引用SoftReference:描述非必须引用,在将要发生内存溢出异常之前,会将该对象列入回收对象,如果回收后内存还是不足,抛出内存溢出异常;
          3、弱引用WeakReference:描述非必须引用,在GC工作时,无论内存是否充足,该对象都会被回收;
          4、虚引用PhantomReference:最弱的一种引用,无论一个对象是否存在虚引用,都不对其生存时间构成影响,也不能通过虚引用获得一个对对象的引用,设置虚引用的唯一目的就是在在回收时获得一个系统通知

可达性分析算法中不可达对象的处理方式:
1、对不可达对象,首先进行第一次标记并筛选,判断一个对象是否有必要执行finalize()方法,如果对象没有覆盖了或者虚拟机已经执行了finalize(),则判定不需要,否则为需要;
2、对于判定需要执行的对象,放入F-Queue 中,并由虚拟机自动建立并触发的、低优先级的Finalize线程执行,注意虚拟机不保证等待该线程执行完毕,以防止因Finalize线程执行缓慢或者死循环带来的GC崩溃;
3、然后,GC对F-Queue中对象再次进行标记,主要用于移除在finalize方法中再次被引用的对象;
4、最后,回收剩余对象

方法区(HotSpot的永久代)回收:主要回收无用的常量和类;对于永久代,是否回收取决于JVM参数设置:-verbose:class -XX:+TraceClassLoading
无用常量:和回收堆中的对象类似,没有引用则回收
无用类:判定条件是:
1、已经回收该类的所有对象;
2、该类的ClassLoader被回收;
3、类对象的class对象也不存在引用,即无法通过反射访问该类的方法

垃圾回收算法:
1、标记-清除算法,标记需要回收的对象,然后统一清除;缺点:标记和清理两个过程效率不高;空间碎片化,在分配大对象时会由于没有足够的连续空间触发另一次GC操作
2、复制算法:内存一分为二,每次用一半;当其中一半用满,则复制存活对象到另一个空间,然后回收整个半区;优点:不存在空间碎片化问题,实现简单高效,缺点是内存缩小为原来的一半;HosSpot的做法是,内存分为一个较大的Eden空间和两个较小的Survivor空间,分配比例是8:1  :  1;使用时用Eden和一个Survivor空间;回收时将存活对象复制到另一个Survivor空间;为了防止一个Survivor空间不够,需要老年代空间的担保(Handle Promotion)
3、标记整理算法:适用于老年代,先标记,后向一段移动对象,最后回收边界以外的空间
4、分代收集算法:将堆划分为新生代和老年代,新生代采用复制算法;老年代存活率高,采用标记-清理,或者标记-整理算法

HotSpot算法实现
1、在类加载的时候,将对象某个偏移量上的引用类型出来,通过使用一组OopMap数据结构保存;JIT编译也会在特定的位置记录栈和寄存器中哪些位置是引用;此时HotSpot通过OopMap实现对GC Roots的快速引用
2、HotSpot不会为每条指令生成OopMap,按照“是否具有让程序长时间执行”的特征选定安全点,如复用、跳转,在安全点处生成OopMap
3、为了让所有线程都跑到最近安全点:
          1、抢断式:GC开始时,中断所有线程,发现没有到安全点的,恢复该线程直至到最近安全点,优点是不需要执行代码配合;这也是使用最广泛的一种;
          2、主动式:开始GC时,设置标识,所有线程轮询该标志,当中断为真时将自己挂起,轮询标识与安全点重合+创建对象需要分配内存的地方
4、安全区域:为了解决线程因sleep、blocked而导致的无法响应JVM中断请求,将代码片段中引用关系不会发生变化的位置设定安全区域;当线程运行到安全区域时,标记自己进入安全区域,此时GC就不必关心处于该区域的线程;而线程即将离开安全区域时,线程检查系统是否完成根节点枚举或整个GC过程,如果是则继续运行,否则等待直到收到可以来开安全区域的信号为止

几种GC收集器
1、serial收集器:单线程,在GC时需要暂停其他所有线程;新生代采用复制算法,老年代采用标记整理算法;虚拟机在client模式下默认新生代收集器,简单高效,单CPU环境下没有线程交互开销
2、parNew收集器:serial收集器的多线程版本,在GC时需要暂停其他所有线程 ;新生代采用复制算法,老年代采用标记整理算法;虚拟机server模式下默认新生代收集器;
3、Parallel Scavenge收集器:新生代、并行收集器,吞吐量优先;设计的目标是到达可控的吞吐量(用户代码时间/(用户代码时间+GC时间));高吞吐量有利于提高CPU利用率,适用于后台计算而不需要大量交互的任务;吞吐量由最大GC停顿时间-XX:MaxGCPauseMillis和吞吐量大小-XX:GCTimeRation控制;注意停顿时间的减小以牺牲新生代大小和吞吐量为代价;该收集器也可以通过-XX:+UseAdaptiveSizePolicy实现GC自适应调节
4、serial old收集器:serial的老年代版本,采用标记整理算法,主要用在client模式下;主要两大用途
          1、1.5及以前,和Parallel Scavenge配合
          2、做为CMS收集器的备选方案,在并发收集发生Concurrent Mode Failure时使用
5、Parallel Old:Parallel Scavenge的老年代版本,使用标记整理算法;在吞吐量优先、CPU资源敏感的场合优先考虑Parallel Scavenge+Parallel Old
6、CMS收集器:以获取短暂停顿目标设计的收集器,多用于互联网及B/S系统服务端,采用标记清除算法,是一种并发低停顿收集器;运行过程如下:
          1、串行初始标记,标记直接与GC Roots直接关联的对象,需要停顿
          2、并发标记,GC Tracing的过程
          3、并行重新标记,修正并发标记期间因用户线程运作导致标记改变的那一部分对象的标记记录,需要停顿
          4、并发清除
CMS收集器缺点:
          1、对CPU资源敏感,占用部分线程,默认使用线程数=(CPU数量+3)/4
          2、无法处理浮动垃圾,可能出现Concurrent Mode Failure从而导致下一次GC,浮动垃圾由于在并发清理阶段用户线程运作造成的,因此CMS收集器不能等到老年代满时运作,默认占用92%时激活,可以通过-XX:CMSInitiatingOccupancyFraction设置阈值
          3、标记清除算法带来的空间碎片,可以通过-XX:+UseCMSCompactAtFullCollection或者-XX+CMSFullGCsBeforeCompaction设置是否用内存整理和内存整理与不整理之间的间隔
7、G1收集器,面向服务器的GC,主要工作过程如下(和CMS类似):
          1、初始标记,标记与GC Roots直接关联的对象,并修改TAMS(next top at mark start),使得阶段二中用户程序可以再正确的区域内创建对象
          2、并发标记,即可达性分析过程
          3、最终标记
          3、筛选回收
,主要特点如下:
          1、并行和并发
          2、分代收集
          3、空间整合,整体上标记-整理,局部为复制,即不存在空间碎片化问题
          3、可预测的停顿,G1收集器的时代,Java内存模型将整个Java堆规划为多个大小相等独立区域,老年代和新生代不再物理隔离,停顿可预测的原因在于,G1收集器有计划的避免在整个堆上进行全区域的GC,他会跟踪每个region中垃圾堆积的价值大小,维护一个优先列表,然后根据允许的搜集时间优先收集回收价值最大的区域;这里,为了处理Region之间的对象引用,通过Remembered Set避免完全堆扫描 

内存回收与分配策略
新生代GC(Minor GC):发生在新生代的GC,发生频繁且速度很快
老年代GC(Full GC/Major GC):老年代GC,一般出现Major GC,经常伴随至少一次Minor GC
对象内存分配,总体上讲就是在堆上分配,对象主要分配再新生代的Eden空间,如果启动了本地线程缓冲TLAB,则按线程优先在TLAB上分配,少数直接进入老年代,具体如下
1、首先在Eden上分配,Eden没有足够空间时,JVM发起一次Minor GC
2、大对象直接进入老年代(大对象,需要大量连续空间的对象,这种对象会提前触发GC),以避免在Eden和两个Surivor空间发生大量复制
3、长期存活的对象进入老年代,通过使用年龄计数器,如果对象在Eden出生并活过第一次Minor GC,并且Surivor空间可以容纳,则将其移动到Surivor空间,并年龄设为1;在Surivor空间内,每活过一次Minor GC,年龄加1,超过阈值(默认15)时进入老年代
4、对象年龄判定是动态的,当Surivor空间中相同年龄的对象的总和大于Surivor空间的一半时,大于等于该年龄的对象直接进入老年代,不必达到阈值
5、空间分配担保,发生Minor GC之前,JVM检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果成立则Minor GC是安全的;不成立,则检查HandlerPromotionFailure是否允许担保,如果允许,则检查老年代可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于则尝试进行Minor G(有风险),如果尝试失败,出现HandlerPromotionFailure失败,改为Full GC;如果小于或者不允许担保,则改为进行Full GC
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java jvm
相关文章推荐