JVM学习笔记——堆
一个 JVM 只有一个堆,堆也是 Java 内存管理的核心区域。在 JVM 启动时堆被创建,同时大小在启动时已设定好,堆是 JVM 管理最大的一块内存空间,其大小可以调节。
-
堆的内存空间物理上可以不连续,逻辑上连续
-
所有线程共享堆,甚至可以在堆中划分线程私有的缓冲区
-
所有的对象实例和数组都存放在堆中,保存所有引用类型的真实对象。在栈帧中保存引用,引用指向对象或数组在堆中的位置
-
方法结束后,堆中的对象不会被马上移除,只在垃圾回收时才被移除,因此堆是垃圾回收的重点区域
堆的内部结构
JVM 采用分代收集算法,因此从 GC 的角度把堆内存在逻辑上分为新生代,老年代,元空间(JDK 8 以后)。
对象分配过程
1. new 创建的对象放入 Eden 区,如果占用内存很大,直接分配到老年区
2. 当 Eden 已满又需要创建对象时, MinorGC 进行垃圾回收
3. MinorGC 回收时,把 Eden 和 ServivorFrom 中的幸存者复制到 ServivorTo,并将这些幸存者年龄+1,
4. 清空Eden 和 ServivorFrom
5. ServivorTo 和 ServivorFrom 互换
6. 当某个对象到达指定年龄后,被调往养老区
7. 在养老区内存不足时,触发 MajorGC
新生代
新生代用于存放新生对象,大约占据 1/3 的堆空间,新生代又分为 Eden 区,ServivorFrom,ServivorTO 三个区。在 HotSpot 中,Eden 和两个 Servivor 空间占比 8:1:1。
由于频繁创建对象,新生代会频繁触发 MinorGC 进行垃圾回收。MinorGC 采用复制算法。
绝大部分 Java 对象都在 Eden 区被创建,在新生代被销毁。
Eden 区
Java 新对象的出生地,几乎所有的 Java 对象都在这里被创建。如果新对象占用内存很大,会直接分配到老年代。当 Eden 区内存不够时就会触发 MinorGC 进行垃圾回收。
ServivorFrom
幸存0区,上一次GC的幸存者,这一次GC的被扫描者。
ServivorTo
幸存1区,保留了一次 MinorGC 过程中的幸存者。
两个幸存区是动态的概念,可以互相替换。
老年代
主要存放应用程序中生命周期长的内存对象。老年代对象相对稳定,只有当老年代空间不足或无法找到足够大的连续空间给新对象时才会触发 MajorGC。
MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记存活对象,回收没有标记的对象。MajorGC 由于要扫描,因此耗时较长,且会产生内存碎片,需要进行合并整理。
元空间
元空间与永久代最大的区别在于:元空间不在虚拟机中,直接使用本地内存。
元空间存放类的元数据。元数据是指用来描述数据的数据,更通俗一点,就是描述代码间关系,或者代码与其他资源(例如数据库表)之间内在联系的数据。
为什么要将永久代替换为元空间?
1. 永久代有一个 JVM 本身设置的固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,内存溢出的几率更小
2. 元空间里面存放的是类的元数据,而由系统的实际可用空间来控制,能加载更多的类
3. 便于合并 HotSpot 和 JRockit 的代码,JRockit 中没有永久代,合并之后就没有必要额外的设置永久代了
堆空间垃圾回收
堆空间的垃圾回收有三种机制,MinorGC,MajorGC,FullGC。每当开始垃圾回收时引发 STW(stop the world),会暂停其他用户线程,等回收结束,其他线程恢复。
MinorGC
MinorGC采用复制算法,MinorGC 的过程:复制->清空->互换。
-
触发条件:Eden 区满,仅 Survivor 满时不会触发 MinorGC
-
由于 Java 临时对象占比非常高,MinorGC 非常频繁,回收速度很快
MajorGC
MajorGC 采用标记清除算法,发生在老年代。
-
出现 MajorGC,通常伴随至少一次的 MinorGC,新生代进入老年代,老年代空间不足时,触发 MajorGC
-
MajorGC 由于要扫描,因此耗时较长,且会产生内存碎片,需要进行合并整理
-
MajorGC 后,内存依然不足,则触发 OMM 异常
FullGC
FullGC 收集整个堆的垃圾,当准备要触发 MinorGC 时,如果发现统计数据说之前 MinorGC 的平均晋升大小比目前 MajorGC 剩余的空间大,则触发 FullGC。
-
调用 System.gc() 时,系统建议执行 FullGC
-
FullGC 的暂停时间长,在调优中应当尽量避免
-

浙公网安备 33010602011771号