JVM 运行时堆内存如何分代?

125 阅读4分钟

本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力

四大引用

首先我们需要对引用进行了解,才能更加理解如何分代.

JDK1.2 之后,Java对应用进行了扩充,将引用分为强引用,软引用,弱引用,虚引用,四种引用强度依次减弱

强引用

最传统的引用,指代码中普遍存在的引用赋值 Object o = new Object().

这种情况下,只要强引用关系还存在,永远不会回收!

软引用

用来描述一些还有用,但非必须的对象

这种关系下,当系统将要内存溢出,会将这些对象列入回收范围中进行第二次回收。如果回收后内存还是溢出,才会跑出内存溢出异常。

JDK1.2 提供SoftReference类实现软引用。

弱引用

也描述非必须对象,但是强度比软引用更弱一些。这种对象只能存活到下次垃圾回收发生为止。当垃圾回收开始工作,无论内存是否足够,都会回收

WeakReference类

虚引用

最弱的一种引用关系。对象是否用虚引用不会对其生存时间产生影响,也无法通过虚引用生成对象实例。只用于对象回收时受到系统通知

PhantomReference类

分代模型

基于分代价说

1.弱分代:绝大多数对象朝生夕灭;

2.强分代:熬过越多次垃圾回收过程的对象越难以消灭。

3.跨代引用:跨代引用相对与同代引用来说是极少数。即存在引用关系的两个对象倾向于同时生存或同时消亡。由于老年代的难以消亡,导致被引用的新生代难以被回收,进而容易晋升成老年代而使跨代引用消除。

以此分成两个区域:

新生代

弱分代假说,绝大多数对象都朝生夕灭,活不多垃圾收集过程,每次回收只关注少量存活对象而不是去标记要被回收的对象

存活的对象将逐步晋升到老年代

如果新生代被老年代引用怎么办

如果去老年代中遍历所有对象来确保可达性分析的正确,会为内存增加极大负担。

根据跨代引用假说:不要为了少量引用去扫描整个老年代。只需要在新生代上建立一个全局的数据结构(记忆集)。这个结构把老年代划分成若干个小块,标识出老年代哪一块内存会存在跨代引用。当发生GC时,才会去这一小块内存中加入GC Roots中进行扫描

老年代

强分代假说,大多数对象难以消亡,以较低的频率进行垃圾回收

永久代-元空间

类加载器将Class 加载到永久代(1.7)或者元空间(1.8)

1.永久代必须制定大小限制,可能会发生内存溢出

2.元数据可以不设置,无上限(上限为物理内存)

3.字符串常量 在 1.7中存放在永久代 ,1.8中存放在堆中

堆中的逻辑分区

image.png

Appel式回收将新生代分成三个区域

新生区百分之90多的对象都会被回收,因此使用Copying算法

  • 新生代存放区域
  • Eden

    • 这个空间较大Eden和Survivor 和 的比例 为 8:1
  • Survivor1

  • Survivor2

  • 流程

每次分配内存时 使用Eden和一块Survivor区另一个Survivor用于存放垃圾回收YGC时存活的对象

当垃圾回收时,将Eden和Survivor0的存活的对象全部复制到Survivor1区域中,然后将Eden和Survivor0的区域全部清空。

  • 放不下怎么办

即10%的空间存储 存活对象。 如果不够,那么依赖其他内存区域(老年代)进行分配担保

  • 晋升

当一个对象经历过多次垃圾回收还是没有被消灭(年龄足够),就会进入老年区

如果老年代也满了,就会触发一次Full Gc ,全部垃圾回收。

FGC 会对全部堆内存进行垃圾回收,十分消耗资源,并且会STW 停止所有用户活动,尽量减少FGC 。