JVM 垃圾回收

垃圾回收

先抛出 3 个问题

  • 什么场景下该使用什么垃圾回收策略?
  • 垃圾回收发生在哪些区域?
  • 对象在什么时候能够被回收?

什么场景下该使用什么垃圾回收策略?
在对内存要求苛刻的场景,想办法提高对象的回收效率,多回收掉一些对象,腾出更多内存
在CPU使用率高的情况下,降低高并发时垃圾回收的频率,让出更多的 CPU去执行你的业务而不是垃圾回收

垃圾回收发生在哪些区域?

JVM 内存结构
虚拟机栈,本地方法栈,程序计数器,这三个区域都是线程独占的,随着线程的创建而创建,销毁而销毁,所以这三块区域是不需要考虑垃圾回收的

堆和方法区是线程共享的,这里才是需要考虑垃圾回收的区域。堆是垃圾回收的主要区域,主要回收的 是创建的对象,方法区主要回收废弃的常量和不需要使用的类

对象在什么时候能够被回收?

有多种判断方法

  • 引用计数法
    通过对象的引用计数器来判断该对象是否被引用

如果一个对象被另一个对象引用,计数器就加一,如果这个对象不再被其他对象引用,计数器就减一,直到计数器为 0,计数器为 0 的时候就可以被垃圾回收
但会出现一个问题:循环引用
比如说 A 引用 B,B 引用 C,C 引用 A,那么这个 3 个对象永远都不能回收,所以 java 并没有 用这种算法

  • 可达性分析
    以根对象( GC Roots )作为起点向下搜索,走过的路径被称为引用链( Reference Chain) ,如果某个对象到根对象没有引用链相连时,就认为这个对象是不可达的,可以回收

可达性分析
GC Roots包括哪些对象?

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

为什么这些对象可以当做 GC Roots???

引用

  • 强引用( Strong Reference)
    形如Object obj = new Object()的引用,只要强引用在,永远不会回收被引用的对象

  • 软引用( Soft Reference)
    形如SoftReference sr = new SoftReference< > (“hello”)
    是用来描述一些有用但非必需的对象
    软引用关联的对象,只有在内存不足的时候才会回收
    基于这个特性,可以用来实现缓存

  • 弱引用( Weak Reference)
    形如WeakReference sr = new WeakReference<> (“hello”)
    弱引用也是用来描述非必需对象的
    无论内存是否充足,都会回收被弱引用关联的对象

  • 虚引用( Phantom Reference)
    形如
    ReferenceQueue queue = new ReferenceQueue<>
    PhantomReference pr = new PhantomReference <>(“hello”, queue);
    不影响对象的生命周期,如果一个对象只有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动,必须和引用队列( ReferenceQueue)配合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存波回收之前采取必要的行动

可达性算法注意点
一个对象即使不可达,也不一定会被回收,对象不可达只是给这个对象判了死缓,并不会

GC Roots 垃圾回收
finalize()的建议

  • 避免使用finalize()方法,操作不当可能会导致问题
  • finalize()优先级低,何时会被调用无法确定,因为什么时间发生GC不确定;
  • 建议使用try…catch…finally来替代finalize()

垃圾回收算法

  • 标记-清除( Mark-Sweep )
    标记需要回收的对象(这个就是根据可达性分析标记需要回收的对象)
    清理掉要回收的对象

标记清除算法清理的时候可能会产生内存碎片,下次再来一个大对象的时候,可能就没法存了
所以就有了下面的垃圾回收算法

  • 标记-整理( Mark-Compact )
    标记需要回收的对象
    把所有的存活对象压缩到内存的一端
    清理掉边界外的所有空间

  • 复制( Copy )
    把内存分为两块,每次只使用一块
    将正在使用的内存中的存活对象复制到未使用的内存中去,然后清除掉正在使用的内存中的所有对象,交换两个内存的角色,等待下次回收

三种算法对比

回收算法优点缺点
标记-清除实现简单存在内存碎片、分配内存速度会受影响
标记-整理无碎片整理存在开销
复制性能好、无碎片内存利用率低
  • 分代收集算法
    把内存分成多个区域,不同区域使用不同的回收算法回收对象

如果需要回收的内存非常大,一次回收所有垃圾,耗费的时间会非常长,可能会造成系统长时间的停顿

  • 增量算法
    每次只收集一小片区域的内存空间的垃圾,这样可以减少系统的停顿

分代收集算法

各种商业虚拟机堆内存的垃圾收集基本上都采用了分代收集
根据对象的存活周期,把内存分成多个区域,不同区域使用不同的回收算法回收对象

在这里插入图片描述

回收类型

  • 新生代回收(Minor GC | Young GC)
  • 老年代回收( Major GC )
  • 清理整个堆( Full GC )
  • Major GC≈Full GC

对象创建的时候会先存放到 Eden,Eden满了就会触发垃圾回收,这个回收的过程是,把 eden 存活的对象拷贝到存活区的 From Survivor或to Survivor,比如这次拷贝到 From Survivor,下次回收的时候就会拷贝到 to Survivor,如此来回多次,默认是 15 次之后,会晋升到老年代,老年代一般使用标记清除,或标记整理回收,因为老年代的对象一般都存活的比较长,只有少部分对象需要清理

新建的对象不一定分配到伊甸园
对象大于XX:PretenureSizeThreshold , 就会直接分配到老年代
新生代空间不够

对象不一定要达到年龄才进入老年代
动态年龄:如果Survivor空间中所有相同年龄对象大小的总和大于Survivor空间的一半,那么年龄大于等于该年龄的对象就可以直接进老年代

触发垃圾回收的条件新生代( Minor GC )
伊甸园空间不足

触发垃圾回收的条件-老年代( Full GC )

  • 老年代空间不足(1.垃圾碎片引起的不足 2.本身空间不足 )
  • 元空间不足
  • 要晋升到老年代的的对象所 占用的空间大于老年代的剩余空间
  • 显式调用System.gc()
    建议垃圾回收器执行垃圾回收
    -XX:+DisableExplicitGC参数,忽略掉System.gc()的调用

分代的好处
更有效的清除不再需要的对象
提升了垃圾回收的效率

分代收集算法调优原则
合理设置Survivor区域的大小,避免内存浪费
让GC尽量发生在新生代,尽量减少Full GC的发生

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值