对象内存的分配策略

目前主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代。
在新生代中为了防止内存碎片问题,因此垃圾收集器一般都选用“复制”算法。因此,堆
内存的新生代被进一步分为:Eden区+Survior1区+Survior2区。比例8:1:1
每次创建对象时,首先会在Eden区中分配。
若Eden区已满,则在Survior1区中分配。
若Eden区+Survior1区剩余内存太少,导致对象无法放入该区域时,就会启用“分配担
保”,将当前Eden区+Survior1区中的对象转移到老年代中,然后再将新对象存入Eden区。
大对象直接进入老年代
所谓“大对象”就是指一个占用大量连续存储空间的对象,如数组。
通过-XX:PretrnureSizeThreshold参数设置大对象
该参数用于设置大小超过该参数的对象被认为是“大对象”,直接进入老年代。
生命周期较长的对象进入老年代
老年代用于存储生命周期较长的对象,那么我们如何判断一个对象的年龄呢?
新生代中的每个对象都有一个年龄计数器,当新生代发生一次MinorGC后,存活下来的对象
的年龄就加一,当年龄超过一定值时,就将超过该值的所有对象转移到老年代中去。
使用-XXMaxTenuringThreshold设置新生代的最大年龄
设置该参数后,只要超过该参数的新生代对象都会被转移到老年代中去。
相同年龄的对象内存超过Survior内存一半的对象进入老年代
如果当前新生代的Survior中,年龄相同的对象的内存空间总和超过了Survior内存空间的一
半,那么所有年龄相同的对象和超过该年龄的对象都被转移到老年代中去。无需等到对象的
年龄超过MaxTenuringThreshold才被转移到老年代中去。

由于对年轻代的复制收集,依然必须停止所有应用程序线程,原理如此,只能靠多CPU,多收集线程并发来提高收集速度,但除非你的 Server独占整台服务器,否则如果服务器上本身还有很多其他线程时,切换起来速度就… 所以,搞到最后,暂停时间的瓶颈就落在了年轻代的复制算法上。
因此年轻代的大小设置挺重要的,大点就不用频繁GC,而且增大GC的间隔后,可以让多点对象自己死掉而不用复制了。但年轻代增大时,GC 造成的停顿时间攀升得非常恐怖,据某人的测试结果显示:默认8M的年轻代,只需要几毫秒的时间,64M就升到90毫秒,而升到256M时,就要到 300毫秒了,峰值还会攀到恐怖的800ms。谁叫复制算法,要等年轻代满了才开始收集,开始收集就要停止所有线程呢。

分配担保”策略详解
当垃圾收集器准备要在新生代发起一次MinorGC时,首先会检查“老年代中最大的连续空闲
区域的大小 是否大于 新生代中所有对象的大小?”,也就是老年代中目前能够将新生代中所
有对象全部装下?
若老年代能够装下新生代中所有的对象,那么此时进行MinorGC没有任何风险,然后就进行
MinorGC。
若老年代无法装下新生代中所有的对象,那么此时进行MinorGC是有风险的,垃圾收集器会
进行一次预测:根据以往MinorGC过后存活对象的平均数来预测这次MinorGC后存活对象的
平均数。
如果以往存活对象的平均数小于当前老年代最大的连续空闲空间,那么就进行MinorGC,虽
然此次MinorGC是有风险的。
如果以往存活对象的平均数大于当前老年代最大的连续空闲空间,那么就对老年代进行一次
Full GC,通过清除老年代中废弃数据来扩大老年代空闲空间,以便给新生代作担保。
这个过程就是分配担保。

Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:Minor GC、FullGC ( 或称
为 Major GC )。
Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。
新生代是 GC 收集垃圾的频繁区域。
当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次
Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设
为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中
存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即
to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即from 区域 ),并且将这些对
象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当
对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设
定),这些对象就会成为老年代。
但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接
进入到老年代。
Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。
现实的生活中,老年代的人通常会比新生代的人"早死"。堆内存中的老年代(Old)不同于这
个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就
“死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要
比进行一次 Minor GC 的时间更长。 另外,标记-清除算法收集垃圾的时候会产生许多的内
存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的
连续的内存空间,就会提前触发一次 GC 的收集动作。

gc策略

引用计数法
简单的想一下,如果一个对象没有被任何人引用,那么他就要被回收。这个实现起来很简单,但是会有一个问题。如果有两个对象互相引用,但是没有任何其他对象引用他们,那么他们就会造成资源泄漏。所以实际使用时还是有些问题的,只能适合一些简单引用的场景。
根搜索算法

概念:设立若干种根对象,当任何一个根对象(GC Root)到某一个对象均不可达时,则认为这个对象是可以被回收的。jvm中使用的垃圾回收算法

注意:这里提到,设立若干种根对象,当任何一个根对象到某一个对象均不可达时,则认为这个对象是可以被回收的。

java垃圾回收机制详解

如上图所示,ObjectD和ObjectE是互相关联的,但是由于GC root到这两个对象不可达,所以最终D和E还是会被当做GC的对象,上图若是采用引用计数法,则A-E五个对象都不会被回收。

根(GC roots)

说到GC roots(GC根),在Java语言中,可以当做GC root的对象有以下几种:

1.栈(栈帧的本地变量表)中引用的对象

2.方法区中的静态成员。

3.方法区中的常量引用的对象(全局变量)

4.本地方法栈中JNI(一般说是Native方法)引用的对象。
注意:第一和第四都是指的方法的本地变量表,第二种表达的意思比较清晰,第三种主要指的是生命为final的常量值。

常用垃圾回收算法

  • 复制算法。
    开辟另外一个内存空间,把扫描到可达的对象复制过去,然后把原内存空间全部清除即可。适用于存活对象较少的情况。

  • 标记清除 Mark-Sweep
    把扫描到可达的对象都标记下来,然后把所有未标记的对象清除。但这个方法会引起内存碎片。适用于存活对象较多的情况。

  • 标记整理Mark-Compact
    在Mark-Sweep基础上,在把内存空间整理一下,让存储连续以消除内存碎片。

一个面试GC部分简单的连环炮。
面试官可以先问你什么时候一个对象会被GC?
  接着继续问你为什么要在这种时候对象才会被GC?
  接着继续问你GC策略都有哪些分类?
  你如果说出来了,继续问你这些策略分别都有什么优劣势?都适用于什么场景?
  你继续说出来了以后,给你举个实际的场景,让你选择一个GC策略?
  你如果选出来了,继续问你,为什么要选择这个策略?

相关文章:

  • 2021-11-20
  • 2021-05-16
  • 2021-08-29
  • 2021-11-20
  • 2021-06-01
猜你喜欢
  • 2021-06-09
  • 2021-05-07
  • 2022-02-05
  • 2022-12-23
  • 2021-08-20
  • 2021-11-20
相关资源
相似解决方案