菜鸟笔记
提升您的技术认知

JavaGC机制详解

1 概述

在 Java中,对象实例都是在堆上创建。
方法区,又叫静态成员区,所有的 1 类(class),2 静态变量(static变量),3 静态方法,4 常量,5 成员方法都存储在方法区
方法区和栈区,被所有线程共享,是不安全的

  • GC机制jvm 提供,用来清理需要清除的对象,回收堆内存
  • GC由垃圾回收器守护线程执行
  • 从内存回收一个对象之前,会调用对象的finalize()方法
  • 开发者不能强制 JVM 执行 GC,GC的触发机制由 JVM 依据堆内存的大小来决定;但是可以通过不同的引用类来辅助垃圾回收器工作(弱引用或软引用)
  • System.gc() 和 Runtime.gc() 会向 JVM 发送执行 GC的请求,但是 JVM 不保证一定会执行 GC
2 总结
  • 为了分代垃圾回收,Java堆内存分为3代:新生代,老年代,永久代
  • 新的对象实例会优先分配在新生代,在经历几次 Minor GC后(默认15次),还存活的会被迁移至老年代(某些大对象会直接在老年代分配)
  • 永久代是否执行GC,取决于采用的 JVM
  • Minor GC 发生在新生代,当 Eden区没有足够空间时,会发起一次 Minor GC,将 Eden区中的存活对象迁移至 Survivor区,Major GC 发生在老年代,当 升到老年代的对象,大于老年代剩余空间时,会发生 Major GC
  • 发生 Major GC时,用户线程会暂停,会降低系统性能和吞吐量
  • JVM的参数-Xmx和-Xms用来设置Java堆内存的初始大小和最大值。依据个人经验这个值的比例最好是1:1或者1:1.5。比如,你可以将-Xmx和-Xms都设为1GB,或者-Xmx和-Xms设为1.2GB和1.8GB
3 堆内存的划分

Java中对象都在堆上创建。为了GC,堆内存分为三个部分,也可以说三代,分别称为新生代,老年代和永久代。其中新生代又进一步分为Eden区,Survivor 1区和Survivor 2区(如下图)。新创建的对象会分配在Eden区,在经历一次Minor GC后会被移到Survivor 1区,再经历一次Minor GC后会被移到Survivor 2区,直到升至老年代,需要注意的是,一些大对象(长字符串或数组)可能会直接存放到老年代
Eden与两个Survivor的内存大小比例大概是 8:1:1
永久代有一些特殊,它用来存储类的元信息。对于GC是否发生在永久代有许多不同的看法,在我看来这取决于采用的JVM。大家可以通过创建大量的字符串来观察是发生了GC还是抛出了OutOfMemoryError

4 GC算法
4.1 标记清除算法

分为标记清除两个阶段
首先标记出所有需要回收的对象,在标记完成后,统一回收所有被标记的对象
该算法的缺点是:效率不高,并且会产生不连续的碎片

4.2 复制算法

把内存空间划为两个区域,每次只使用其中一个区域
垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。
算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去后还能进行相应的内存整理,不会出现"碎片"问题
优点:实现简单,运行高效。 缺点:会浪费一定的内存
一般新生代才用这种算法

4.3 标记整理算法

标记阶段与标记清除算法一样,但后续并不是直接对可回收的对象进行清理,而是
让所有存活对象都向一端移动,然后清理
优点:不会造成内存碎片