GC和内存分配策略
如何判断对象是否存活?
1:计数法:如果两个对象相互引用,则他们的计数都不为0,但是这时候触发回收器,这两个对象也是会被回收的,说明计数器并不能成为是否存活的标准
2:可达性分析算法:如果没有被GC roots引用的,就是无用的对象,就会被回收?
是会被回收,但是还没被回收,就是如果判断为没有引用链的情况,会被标注会无用的对象,但是不会 马上被回收
3:在被回收之前会经历两次标注:第一次是可达性分析算法,第二次是会先判断这个对象是否有必要执行finalize()方法,如果这个对象没有覆盖finalize()方法,或者是这个方法已经被虚拟机调用过了,那这个对象就是被判定死刑了,(如果覆盖了finalize(),并不能保证能将它运行完再结束,只能保证他会放入F-Queue队列中,等待触发这个方法,因为有可能在这里发生死锁或者执行缓慢的情况)如果在finalize()方法中,这个对象和引用链上其他对象发生关联即可,不然就die了
引用的四种强度:
1:强引用:只要强引用还在,这个对象便不会被回收
2软引用:还有用但是不是必须的对象,会再出现oom之前被回收,如果这些对象被回收完了,内存还不够,才会出现oom的情况
3:弱引用:被弱引用 的对象只能活到下一次回收前,下一次回收开始,无论内存是否足够,这个对象都会被护手
4:虚引用:完全没有关系,目的只是在被回收的时候,会给这个对象发一个系统通知
对方法区的回收
对方法区的回收,效率比较低,主要是回收废弃常量和无用的类
判断标准Wie:
废弃常量:在常量池中如果没有其他对象引用,也没有其他地方引用,此时发生内存回收,他就会回收
无用的类:该类的所有实例都被回收了,加载这个类的加载器也被回收了,class对象没有被引用,无法通过 任何方法访问到这个类了
回收算法:
标记-清除法:吧标记的清除掉,缺点:会造成不连续的内存碎片,容易造成下次的回收
复制法:内存两等分,只用一边,回收时,吧这边还能用的,复制到另外一边,吧这边的都回收,正常分为8:1:!
标记-整理法:针对老年代这种回收数量不多的,吧可用的聚集在一起,清除掉边界以外的
分带收集:吧堆分为新生代和老年代,新生代被收集时只有少量存活,所以用复制法,老年代存活率高,所以用标记-清除法或标记-整理法
hotspot的算法实现:
一致性:当执行分析gc时,整个容器会发生gc停顿,等于整个容器都停止(不然刚记录完对象有可能就发生变化)
1.枚举根节点:是一组称为OopMap的数据结构来存放对象引用,类加载完成时.hotspot吧对象的什么偏移量上是什么类型的数据计算出来,为了一致性,虚拟机只需要扫描这个节点就知道哪些对象有被引用,哪些需要标记,不需要扫描所有的引用和对象(这样太浪费性能和时间)
2.安全点:存放OopMap的位置就叫安全点,程序进行到安全点时,才会停顿下来进行gc,安全点的多少决定着gc的频率, (抢先式中断和主动式中断 抢先式:吧所有的都中断,发现线程不在安全点的,就恢复让其跑到安全点, 主动式:设置一个标志,线程执行时主动去查询这标志,如果为true就自己中断,这个标志放在了创建对象分配内存的位置,并和安全点重合)
3:安全区:如果线程sleep和block的时候是不会触发安全点的.所以把自己标注为安全区,gc不回收里面的东西,离开安全区时就会先检查系统是否已经完成枚举根节点,如果完成了那就继续执行,否则就要等待收到课可以离开安全区的信号为止才能离开安全区
垃圾收回器特点及运作原理:
1:Serial收集器:最古老的的收集器,在收集时,必须暂停其他所有的工作线程,直到收集结束,
简单而高效,主要应用于虚拟机运行在client模式下的默认新生代收集器,复制算法
2.ParNew收集器:Serial收集器的多线程版本, 应用于虚拟机在server模式下的默认新生代收集器,只有
serial和parnew能与CMS收集器配合, 复制算法,
3.Paraller Scavenge 收集器: 复制算法,优点,达到一个可控制的吞吐量,提供两个参数,控制最大垃圾堆
收集的停顿时间 -XX:MaxGCPauseMilis 当这个值变小时,新生代空间也随之变
小,那停顿时间变短,但是GC频率变高,吞吐量变小,设置吞吐量大小的-
XX:GCTimeRatio参数,吞吐量:运行代码时间/(运行代码时间+垃圾收集时间)
4:Serial-Old收集器:是Serial在老年代的版本,用的是标记-整理法,主要是在client模式下的老年代使用,
如果是server模式,则是jdk1.5之前和Paraller Scavenge收集器搭配,或者是CMS收
集器的后备方案,在并发收集发生Concurrent mode Failure时使用
5:Paraller - Old收集器:是Paraller Scavenge收集器在老年代的版本,应用,只能和Serial-Old配合,和
CMS没办法配合,在注重吞吐量和CPU资源敏感的场合,优先使用Paraller
Scavenge和 Paraller Old配合使用
6:CMS:目标是获取最短回收停顿的收集器,标记-清除算法,运行的过程分为1:初始标记,2:并发标记3:重
新标记,4:并发清除 在13过程还是需要stop the world 但是在24过程就可以和用户线程一起
工作, 缺点:1对cpu资源敏感,因为并发,客户代码运行的同时发生了GC所以,会比较慢
2:无法处理浮动垃圾:在gc的同时,客户代码也在运行,不断产生浮动垃圾,有需要预
留内存空间给用户线程使用,所以,在老年代内存还未到100%的时候就需要GC
3:使用标记-清除会产生空间碎片,碎片过多的情况就会对分配大内存的对象有影
响,导致经常触发FullGC.有个参数-XX:CMSFullGCsBeforeCompaction,执行多
少次不压缩的Full GC就带来一次压缩的Full GC
7:G1:标记-整理算法 特点:并行和并发,分代收集,空间整合,可预测的停顿,将java堆分成等大的独立区
域(Region),并把新生代和老年代都分配其中,之所以是可预测的停顿是因为他把每
个Region的回收价值记录在后台列表中,根据设置的停顿时间,从列表中挑选回收价
值最高的Region进行回收
执行过程:1:初始标记:是标记GC root能直接关联的对象,需要stop the world(短)
2:并发标记,进行可达性分析,找出存活对象并标记(长)
3:最终标记,标记在并发中新产生的对象
4:筛选回收,根据后台列表优先级来回收:
自动内存分配及回收的主要规则:
1:对象优先在Eden分配:新生代在Eden分配,则当Eden满了时,会触发Full GC 来清除对象
2:大对象直接进老年代:如果大对象还是放在新生代的话,大部分的对象都会分配在Eden,势必会导致
FullGC 的频率变高,还不如放入老年代,减小开销
3:长期存活的对象进老年代,这就关于年龄,每当在Eden中经历过一次Minor GC之后,允许的话会被放在
survivor区,之后每经历一次Minor GC 就会加1 ,当超过15(默认值,可以用-
XX:MaxTenuringThreshold设置)时就会进入老年代
4:动态对象年龄判断:并不是一定要超过MaxTenuringThreshold才能进老年区,当survivor中相同年龄
的对象占survivor内存的一半时,就会吧大于或等于这个年龄的对象放到老年区去
5:空间分配担保:在发生Minnor GC之前,会先检查老年最大的可连续空间是否>=新生代的空间,如果小
于就会判断HandlePromotionFailure的值,如果允许,会判断最大的可连续空间是否是
大于历次晋升到老年代的对象的平均值,如果大于,就会发起一次MinnorGC,如果小
于或者HandlePromotionFailure的值不允许,则会发起一次Full GC,
为什么要担保:新生代的对象用标记-复制来执行的,但是只有一个survivor空间,最坏的可能是,所有的新生代对象都存活,一次性都进入了老年代,如果老年代没有足够的空间,还不如先发起一次FullGC清理出足够的空间,HanlePromotionFailure的值是表示是否可以冒险进行Minnor GC