JVM基础:内存分配策略与垃圾收集

在编写文章前,有几个问题需要思考一下:

  • 内存如何划分?
  • 对象如何分配内存?
  • 哪些内存需要回收?
  • 在哪个节点回收?
  • 如何回收?

1. 内存如何划分?

当前商业虚拟机采用分代思想管理内存,接下来的部分已 HotSpot 虚拟机的内存分配为分析模型,在 HotSpot 里,Java 堆中可以细分为:新生代和老年代;再细致一点的有 Eden 空间、From Survivor 空间、To Survivor空间(8 : 1 : 1)等。在 JDK 1.7之前,使用永久代实现方法区,在1.7之后,逐步改为采用 Native Memory 来实现方法区的规划了。

2. 对象如何分配内存?

  • 对象优先在 Eden 分配:在大多数情况下,对象在新生代 Eden 区中分配空间,当 Eden 没有足够的内存空间可用时,虚拟机将发起一次 Minor GC。
  • 大对象直接进入老年代:需要大量连续内存空间的 Java 对象为了避免在 Eden 区以及两个 Survivor 区之间发生大量的内存复制,令大于系统设置值的对象直接在老年代分配空间。
  • 长期存活的对象将进入老年代:虚拟机采用分代的思想来管理内存,那么分配内存时必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。当对象年龄增长到系统设置进入老年代年龄时,就将会晋升到老年代中。如果在 Survivor 空间的相同年龄段所有对象大小大于 Survivo r空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,不需要等到对象的年龄大于系统设置值。当老年代没有足够的内存空间可用时,虚拟机将发起一次 Major GC。
  • 空间分配担保:新生代使用复制回收算法,把存活的对象复制到一个 Survivor 空间上,如果存活的对象空间大于 Survivor 可容纳的对象,就需要老年代进行担保,把 Survivor 无法容纳的对象存放到老年代上。

3. 哪些内存需要回收?

通过一系列称为 "GC Roots" 的对象作为起点,从这些节点开始向下搜索,当一个对象没有和任何引用链相连时,则说明该对象不可用。

可作为 GC Roots 对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)引用的对象。
  • 本地方法栈中(一般说的 Native 方法)引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。

引用类型:

  • 强引用:在程序代码中普遍存在的,类似 "Object obj = new Object()" 这类的引用,只要强引用还存在,垃圾收集器不会回收被引用的对象。
  • 软引用:用来描述一些有用但非必须对象。对于软引用关联着的对象,垃圾收集器运行时可能会(可能不会)回收软引用对象。对象是否被回收取决于垃圾收集器的算法以及垃圾收集器运行时可用的内存数量。JDK1.2 之后提供了 SoftReference 类来实现软引用。
  • 弱引用:用来描述非必须对象。被弱引用关联着的对象只能生存到下一次垃圾回收发生之前。JDK1.2 之后提供了 WeakReference 类来实现软引用。
  • 虚引用:最弱的引用关系。对象的虚引用完全不会对其生命周期构成影响,也无法通过虚引用来取得一个对象的实例。JDK1.2 之后提供了 PhantomReference 类来实现软引用。

4. 在哪个节点回收?

垃圾收集器回收的是到 GC Roots 的引用链不可达对象,在可达性分析时必须在确保一致性的快照中进行。不可以出现在分析过程中对象的引用还在不断变化的情况。这就会导致在 GC 运行时必须停顿所有 Java 执行线程(Stop the world)。所以在枚举根节点操作中必须要停顿的。

4.1 安全点

在触发 GC 回收时让线程在哪里停下来?如何让线程停下来?程序执行时并非在任何地方都可以停顿下来开始 GC,只有在到达安全点是才能停顿。安全点的选定基本上是以程序 "是否让程序具有长时间执行的特征" 为标准进行选定。例如方法调用、循环跳转、异常跳转等。

在 GC 发生时,可以通过两种选择让线程跑到安全点停顿下来:抢先式中断和主动式中断。抢先式中断先把所有线程全部中断,发现中断的地方不在安全点上,就恢复线程,让它继续运行到安全点上;主动式中断设置一个标志位,线程执行时让线程主动轮询这个标志位,发现中断标志位为真是就中断挂起。

4.2 安全区域

通过上面分析知道 GC 发生时执行线程会在安全点上中断,如果线程不执行(处于 sleep 或 block 状态),无法响应 JVM 中断请求,运行到安全点上中断挂起。对于这种情况就需要安全区域来解决。

安全区域指的是一段代码片段中,引用关系不会发生变化。在这个区域中的任何地方开始 GC 都是安全的。线程执行到安全区域中的代码时,标识自己已经入安全区域,当 GC 发生时,就不需要处理这些线程。当线程要离开安全区域时,首先要检查系统是否完成了根节点的枚举,如果完成,线程就继续执行,否则必须等到收到可以离开安全区域信号为止。

5. 如何回收?

当前商业虚拟机采用分代思想管理内存,根据不同代采用不同的分配策略和垃圾回收算法。不同代垃圾回收触发机制可能也会不一样。新生代采用复制策略,当需要分配的内存不足时就触发新生代 GC(Minor GC)。老年代会根据系统设置的空间使用比例参数来触发老年代 GC(Major GC)。

5.1 标记-清除算法

标记所有需要回收的对象,标记完成后统一回收被标记的对象。

5.2 标记-整理算法

标记所有需要回收的对象,让所有存活的对象都向一端移动,然后清理掉端边界以外的数据。

5.3 复制算法

将内存划分为大小相等的两块,每次只是用一块,当这一块内存用完时,就将还存活的对象复制到另一块内存上,然后再把已使用过的内存空间清理掉。将内存划分为一块较大的 Eden 区和两块较小的 Survivor 区(8:1:1),使用 Eden 和其中一块 Survivor 存放新对象,发生内存回收时把 Eden 和 Survivor 存活的对象复制到另一块 Survivor 空间上。如果出现存活对象大小大于 Survivor 空间大小的极端情况时,使用空间分配担保来把不能容纳的对象存放到 Tenured 空间。

5.4 分代收集算法

根据对象存活周期不同将内存划分为几块,再根据各个代的特点采用最适当的收集算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值