JVM 垃圾回收

Java 内存模型

Java 内存模型分为 5 个部分,程序计数器,虚拟机栈,本地方法栈,堆,方法区

  1. 程序计数器:线程私有,每个线程都有自己的程序计数器,指向正在执行的字节码地址。这个内存区也是 Java 内存模型中唯一一个不会发生内存溢出的区域。

  2. 虚拟机栈:线程私有,方法的执行都会涉及到虚拟机栈的入栈出栈操作。包含局部变量表、操作数栈、动态连接栈,局部变量表保存方法参数和方法内部变量,对于实例方法来说,局部变量表还保存着当前类的引用 this. 动态连接表,是用于指向常量池的。

  3. 本地方法栈:线程私有,保存 native 方法的信息。

  4. 堆:线程共享,保存 new 出的对象实例和数组,该区域也是垃圾回收的主要对象。

  5. 方法区:线程共享,保存的是类信息,常量,静态变量等数据,该区域也会进行垃圾回收,属于长久代的内存区域。运行时常量池就是方法区的一部分,保存编译期间的常量,不过并不只是编译器可以保存,在运行期也可以保存,比如 String 的 intern() 方法,如果常量池不存在该字符串常量,则将该字符串常量放入常量池。

四种引用

  1. 强引用,就是我们平时使用的最常见直接 new 处的对象。强引用的对象只有在不可达时才会被回收。

  2. 软引用,是在内存不足时才会被回收,内存够用的时候不会被回收

  3. 弱引用,是每次发生 GC 时就会被回收,不管内存是否够用

  4. 虚引用,随时都有可能会被回收,主要用于跟踪对象被垃圾回收时的活动

怎样判断垃圾需要回收

1. 引用计数

引用计数是给每个对象一个标记,每增加一个引用,标记就加 1, 在垃圾回收时标记是 0 则对其进行回收。
这样的算法有一个缺点就是假如有下面这样的代码

String a = new String("123");
String b = a + "ab";

这之后就没有再引用 a, b 的地方了,但是由于 a 的标记值为 1, 所以一直不能被回收。

2. 可达性分析

只要是没有被 GC Root 引用的对象就可以回收,可作为 GC Root 的引用为:虚拟机栈中引用的对象,方法区中常量池的对象,方法区中类静态属性对象,本地方法栈 JNI 引用的对象。

3. 方法区回收的判断

常量:没有再被引用
类:

  1. 该类的所有的实例都已经被回收
  2. 该类的类加载器已经被回收
  3. 没有任何对该类的引用,也没有对该类的反射

垃圾回收算法

1. 标记清除算法

标记清除分为标记阶段和清除阶段,标记可回收的对象,在清除阶段对其进行回收,这样的算法缺点就是会产生空间碎片较多,若给大对象分配内存空间时,就有可能提前触发 GC.

2. 复制算法

复制算法把内存空间分为一块 Eden 区域和两块 Suvivor 区域,其比例是 8:1:1, 有对象生成时优先分配到 Eden 区域,第一次发生 GC, 把 Eden 区域的存活的对象复制到 S1, 然后清除掉 Eden 区域所有的对象,再有新对象生成还是优先分配到 Eden 区域,第二次 GC 时,把 Eden 区域和 S1 区域存活的对象复制到 S2, 然后清除掉 Eden 区域和 S1 区域的对象。之后再发生 GC S1 和 S2 区域的就会来回倒腾。
复制算法适用于新生代的对象,因为新生代对象存活的时间都较短。

3. 标记整理算法

不直接对不可用对象进行回收,先把可用对象整理到一边,再清理边界外的不可用对象。

4. 分代收集

针对新生代和老年代使用不同的回收算法。

垃圾回收器

  1. Serial : 新生代(复制算法),单线程,需暂停用户线程

  2. Serial Old : 老年代(标记-整理),单线程,需暂停用户线程

  3. Parallel Savenge : 新生代(复制算法),多线程,需暂停用户线程。吞吐量优先,两个参数:MaxGCPauseMills(GC停顿时间),GCTimeRation(GC吞吐量倒数,自适应调节策略)

  4. Parallel Old : 老年代(标记-整理),多线程,需暂停用户线程,吞吐量优先。

  5. ParNew : 新生代(复制算法),多线程,需暂停用户线程。Serial 的多线程版本。

  6. CMS : 以获取最短回收时间为目标,老年代(标记-清除)
    过程:
    初始标记,需 stop the world, 标记直接与 GC Root 关联的对象;
    并发标记,可与用户线程并发执行,标记所有被 GC Root 引用的对象;
    重新标记,需 stop the world, 修改用户线程执行过程中已被标记的对象重新被引用的;
    并发清除,可与用户线程并发执行,清除被标记的对象。

由于回收算法使用的是标记-清除,所以就会导致有碎片产生,可通过设置 UseCMSCompactAtFullCollection(Full GC 时是否开启碎片整理,默认开启), CMSFullGCsBeforeCompaction(设置执行多少次 GC 不压缩后,执行一次碎片整理,默认是 0, 每次都整理碎片)
由于并发,无法处理浮动垃圾,也就是在 GC 进行过程中用户线程还在执行产生的垃圾,GC 运行中需要预留一部分空间供用户线程使用,如果预留空间不足,则会触发 “Concurrent Mode Failure”, 临时启动 Serial Old 来重新进行老年代垃圾收集。
由于并发,会导致用户线程变慢,于是出现了“增量式并发收集器”,GC 与用户线程交替执行,效果很一般,已不提倡使用。

  1. G1 收集器
    G1 收集器实现了分代收集,采用并行和并发,充分利用了多 CPU、多核,目标是低停顿,还建立了可预测的停顿时间模型,可指定在 M 时间内,GC 的时间小于 N.

JVM 内存分配策略

  1. 对象优先在新生代 Eden 区分配,当 Eden 区没有内存可分配时,触发一次 Minor GC.

  2. 大对象直接进入老年代,其中有一个参数 PretenureSizeThreShord, 大于改值的对象直接进入老年代。

  3. 长期存活对象进入老年代,Eden 区的对象,经过一个 Minor GC 后依然存活的进入 Survivor 区,且年龄设为 1, 此后每经过一次 Minor GC, 年龄加 1, 当年龄大于 MaxTenuringThreShold 时进入老年代。

  4. 动态对象年龄判定:当大于等于某一年龄的对象大小总和大于所有空间的一半时,大于等于某一年龄的对象进入老年代。

  5. 空间分配担保:当 Survivor 上没有足够的空间存 Eden 区经过 Minor GC 存活下来的对象时,这些对象将直接通过空间分配担保进入老年代。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值