jvm总结(2)——垃圾收集器与内存分配策略

2018-04-11  本文已影响0人  pluss

    有些内存区域因为方法结束或者线程结束时就自动回收了,所以并不需要考虑它们的回收。主要关注的是对java堆和方法区的回收。(方法区怎么回收?)


对象存活分析

    判断对象是否存活的算法有引用计数算法和可达性分析算法。java用的是可达性分析算法。

    引用计数算法原理是,给对象添加一个计数器,引用一次加一,引用失效减一。当计数器为0时对象就不可能被使用,所以可以回收该对象。
这个算法简单高效但是有一个缺点就是不能处理对象引用循环的情况,即对象A引用对象B,B引用A,除此之外没有任何其他对象引用A和B,这样子AB永远不可能被使用,因为计数器值为1也不会被回收。

    可达性分析算法,让一些自动回收(我还不确定选择的标准)的对象作为GCRoot,每个被引用的对象都会有一条到GCRoot的引用链。回收时通过GCRoot来搜索对象的引用,若找不到这个对象就说明这个对象没有被引用是可回收的(GCRoot到这个对象不可达)。

可作为GCRoot的对象有:


引用

引用分为强引用、软引用、弱引用、虚引用。


关于finalize()方法

    Object的protected方法,可在gc回收前调用这个方法,实现资源清理或者自救。
    若gc第一次标记时,判断到对象需要执行finalize方法,就将它加入一个队列中,另开线程执行这个方法,但不会等它执行完。
    如果对象在finalize方法中完成了自我拯救,在第二次标记时它会被移除出“即将回收”集合。


垃圾收集算法

    主要有 标记-清除算法、标记-整理算法、复制算法、分代收集算法。

标记-清除
    先标记出所有要清除的对象,然后再统一回收这些对象。
不足:
    效率低。
    空间碎片多,给大对象分配内存时找不到足够的连续内存,会提前触发另一次gc。

复制
    将内存分成两半,每次只使用其中一半,在用完时将存活对象复制到另一半上去,再将那用完的半部分空间都清理掉。
不足:可用内存减少了

    虚拟机将新生代分为了Eden区和两个Survivor区,大小 8:1:1。每次只用Eden和一块Survivor,回收时将存活对象复制到另一块Survivor中。
    可以这样分是因为新生代中大多是朝生夕死的对象。但存活对象也有可能会多余10%,所以需要分配担保机制,在Survivor空间不够时,让其他空间比如老年代提供分配担保,把存活对象复制到这些空间里。(是部分还是全部复制过去?咦担保是用的复制吗)

标记-整理
    标记所有可回收对象,然后将存活对象全部向一端移动,最后清除掉边界外的内存。

    老年代的选择。

分代收集算法
    根据对象存活时间来划分内存。
    一般将java堆分为新生代和老年代,然后可以根据不同年代的特点采用不同的收集算法。新生代朝生夕死存活对象少,所以可用复制算法。老年代顽强存活对象多,又没有其他空间给它担保所以只能选标记清除和标记整理。


枚举根结点、安全点和安全区域

    枚举根结点时为了防止引用关系变化,需要阻塞所有运行java code的线程(stop the world),VM操作相关的线程不会被阻塞,运行native code的线程如果不与javacode交互也不会被阻塞。
    另外不需要检查所有引用位置来找全GCRoot,数据结构OopMap记录了引用的位置。
    但是因为很多指令会导致引用关系变化,即OopMap内容改变,又不可能给每条指令后面都生成对应的OopMap。所以OopMap只在特定的位置生成,这些位置就是安全点。
    安全点是指一些特殊的位置,在这些位置线程状态可以被确定,使jvm安全的进行一些操作(比如gc,程序运行时并非在所有地方都能停顿下来进行gc,到达安全点才能停下来)。
    安全点的选定不能太少,让gc等待时间太长(自己的理解:假设只有一个安全点,每次gc都要等待线程运行到这个安全点才能开始,安全点多点的话,线程短时间内有很大几率碰到一个安全点,gc就不用等那么久才能开始),也不能过于频繁增加负荷。

所以,安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的——因为每条指令执行的时间都非常短暂,程序不太可能因为指令流长度太长这个原因而过长时间运行,“长时间执行”的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等。

(这一段看不懂)
这些特定的位置主要在:
1、循环的末尾
2、方法临返回前 / 调用方法的call指令后
3、可能抛异常的位置

    线程自己响应中断:在安全点设置一个标志,轮询这个标志,若为真就自己中断挂起。
    若线程阻塞,无法轮询,就需要安全区域了,安全区域可以看作是安全点的扩展。线程执行到安全区域时标示自己已经进了safe region,gc时不用管这些线程,当线程要出这个区域时查看gc是否已经完成,没完成需要等到gc完成才能出去。


垃圾收集器

Serial收集器
     单线程,需要stoptheworld,简单高效,适用于client模式的VM,新生代的选择,SerialOld可用于老年代。
     新生代复制算法,SerialOld老年代标记-整理算法

ParNew收集器
     多线程,Serial的多线程版,需要暂停用户线程,server模式VM中首选的新生代收集器。
除了Serial唯一能与CMS收集器配合工作的收集器。

Parallel Scavenge收集器
     多线程,新生代收集器,复制算法。吞吐量优先收集器。吞吐量是代码运行时间和cpu总运行时间的比值(即代码时间+gc时间)。
     要减少停顿时间就要牺牲新生代空间,新生代空间小了,停顿时间也就变小了,但是相应的gc次数变多了,吞吐量也降了下来。
-XX:GCTimeRatio gc时间占总时间的比值
-XX:MaxGCPauseMillis 最大停顿时间
-XX:+UseAdaptiveSizePolicy 自适应策略

Serial Old收集器
jdk1.5 前与parallel搭配使用
作为CMS的后备预案

Parallel Old 收集器
1.5后 与Parallel搭配使用。

image.png

CMS收集器
以最短停顿回收时间为目标,标记清除算法,并发低停顿收集器
只有初始标记和重新标记需要阻塞用户线程

缺点:

G1收集器
它java堆的内存分布与其他收集器不一样,它将java堆划分成了很多个相同大小的区域(region)。

老年代引用新生代的对象的情况:

老年代有一个CardTable,若老年代中有引用新生代的对象,就将cardtable对应的索引记录到新生代对象所属的RememberSet中,这样子MinorGC就不用搜索全堆,只需将RSet加入根结点枚举范围扫描特定的地方。


image.png

运行步骤:


(JIT编译后被拆散为标量类型栈上分配??)

对象的内存分配规则
上一篇 下一篇

猜你喜欢

热点阅读