JVM学习笔记(四)垃圾回收

5 篇文章 0 订阅

尚硅谷JVM学习笔记

第八章 垃圾回收 [重要]

第一节 GC的基本问题

  • 什么是GC?

    GC 是 garbage collection 的缩写,意思是垃圾回收——把内存(特别是堆内存)中不再使用的空间释放掉;清理不再使用的对象。

  • 为什么要GC?

    堆内存是各个线程共享的空间,不能无节制的使用。服务器运行的时间通常都很长。累积的对象也会非常多。这些对象如果不做任何清理,任由它们数量不断累加,内存很快就会耗尽。所以GC就是要把不使用的对象都清理掉,把内存空间空出来,让项目可以持续运行下去。

  • 什么样的对象是垃圾对象?

    不再使用或获取不到的对象是垃圾对象。

  • 如何把垃圾对象找出来?

    办法1:引用计数法(不采用,不能解决循环引用问题)[了解]

    办法2:可达性分析(从GC Roots对象出发,不可达的对象就是要清理的对象)[理解]

  • 找到垃圾对象如何执行清理?

    具体的GC算法

第二节 标记垃圾对象

1、引用计数法(不采用)

①工作机制

引用计数法是在对象每一次被引用时,都给这个对象专属的『引用计数器』+1。

当前引用被取消时,就给这个『引用计数器』-1。

当前『引用计数器』为零时,表示这个对象不再被引用了,需要让GC回收。

可是当对象之间存在交叉引用的时候,对象即使处于应该被回收的状态,也没法让『引用计数器』归零。

        Member member01 = new Member();
        Member member02 = new Member();

images

        member01.setFriend(member02);
        member02.setFriend(member01);

images

        member01 = null;
        member02 = null;

images

引用计数法的关键问题:该清理的对象清理不掉

②循环引用举例
[1]一对多关联关系
public class Customer {

    private List<Order> orderList;

    public List<Order> getOrderList() {
        return orderList;
    }

    public void setOrderList(List<Order> orderList) {
        this.orderList = orderList;
    }
}
public class Order {

    private Customer customer;

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }
}
[2]SpringMVC 组件
  • IOC 容器对象的接口类型:WebApplicationContext
    • WebApplicationContext 对象初始化过程中:将它自己存入 ServletContext 域
    • WebApplicationContext 对象也会把 ServletContext 存入 IOC 容器
  • Servlet 上下文对象:ServletContext

images

2、GC Roots可达性分析

核心原理:判断一个对象,是否存在从**『堆外』『堆内』**的引用。

images

请看下面的例子:

①加载Employee类
class Employee {
    public static final String SUBJECT = new String("Java");
}

images

②创建Employee对象并赋值给变量
    public static void main(String[] args) {

        // 1.创建Employee对象,并赋值给employee变量
        Employee employee = new Employee();

    }

images

3、GC Root 对象

GC Root 对象:就是作为根节点出发,顺着引用路径一直查找到堆空间内,找到堆空间中的对象。

  • Java 栈中的局部变量
  • 本地方法栈中的局部变量
  • 方法区中的类变量、常量

第三节 垃圾回收算法

基本垃圾回收算法有四种:引用计数法、标记清除法、标记压缩法、复制算法。现代流行的垃圾收集算法一般是由这四种中的其中几种算法相互组合而成。例如:分代算法、分区算法。

这里又看到一个『引用计数法』,但是和前面提到的不一样:

前面『引用计数法』:是标记垃圾对象的一种方法

这里『引用计数法』:是执行垃圾回收的一种方法

1、基本算法:引用计数法(不采用)

引用计数算法很简单,它实际上是通过在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加一,如果删除对该对象的引用,那么它的引用计数就减一,当该对象的引用计数为0时,那么该对象就会被回收。

引用计数垃圾收集机制,它只是在引用计数变化为0时即刻发生,而且只针对某一个对象以及它所依赖的其它对象。所以,我们一般也称呼引用计数垃圾收集为直接的垃圾收集机制。垃圾收集的开销被分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的"Stop-The-World"的垃圾收集机制。

优点:

  • 实时性较高,不需要等到内存不够时才回收
  • 垃圾回收时不用挂起整个程序,不影响程序正常运行

缺点:

  • 回收时不移动对象, 所以会造成内存碎片问题
  • 不能解决对象间的循环引用问题

小结:

正是由于引用计数法不能解决对象间的循环引用问题,所以事实上并没有哪一款 JVM 产品采用这个机制。

TIP

Stop-The-World:字面意思让整个世界停止。在 GC 机制中,Stop-The-World 表示挂起整个 JVM 程序,等执行完垃圾回收之后,再继续执行 JVM 程序。Stop-The-World 通常也会简称为 STW。

2、基本算法:标记清除法

它的做法是当堆中的有效内存空间被耗尽的时候,就会暂停、挂起整个程序(也被称为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。

  • 标记:标记的过程其实就是,从根对象开始遍历所有的对象,然后将所有存活的对象标记为可达的对象。
  • 清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。

小结:

  • 优点:实现简单
  • 缺点:
    • 效率低,因为标记和清除两个动作都要遍历所有的对象
    • 垃圾收集后有可能会造成大量的内存碎片
    • 垃圾回收时会造成应用程序暂停

images

3、基本算法:标记压缩法

既然叫标记压缩算法,那么它也分为两个阶段,一个是标记(mark),一个是压缩(compact)。所谓压缩就是把存在碎片的空间连起来。

标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象移动到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题。

标记 : 标记的过程其实就是,从根对象开始遍历所有的对象,然后将所有存活的对象标记为可达的对象。

压缩 : 移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起,通过这样的方式来达到减少内存碎片的目的。

小结

优点:标记压缩算法是对标记清除算法的优化,解决了碎片化的问题

缺点:还是效率问题,在标记清除算法上又多加了一步,效率可想而知了

images

4、基本算法:复制算法

复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,并依次排列,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收。

小结

  • 优点1:在垃圾多的情况下(新生代),效率较高
  • 优点2:清理后,内存无碎片
  • 缺点:浪费了一半的内存空间,在存活对象较多的情况下(老年代),效率较差

执行GC前:

images

执行标记:

images

执行复制:

images

交换指针:

images

5、综合算法:分代算法

前面介绍了多种回收算法,每一种算法都有自己的优点也有缺点,谁都不能替代谁,所以根据垃圾回收对象的特点进行选择,才是明智的。

分代算法其实就是这样的,根据回收对象的特点进行选择。

  • 新生代适合使用复制算法
  • 老年代适合使用标记清除或标记压缩算法

#6、综合算法:分区算法

上面介绍的分代收集算法是将对象的生命周期按长短划分为两个部分,而分区算法则将整个堆空间划分为连续的不同小区间,每个小区间独立使用,独立回收。这样做的好处是可以控制一次回收多少个小区间。在相同条件下,堆空间越大。一次GC耗时就越长,从而产生的停顿也越长。为了更好地控制GC产生的停顿时间,将一块大的内存区域分割为多个小块,根据目标停顿时间每次合理地回收若干个小区间(而不是整个堆),从而减少一次GC所产生的停顿。

第四节 垃圾回收器 [了解]

1、串行垃圾回收器

串行:在一个线程内执行垃圾回收操作。

新生代串行回收器 SerialGC:采用复制算法实现,单线程垃圾回收,独占式垃圾回收器

老年代串行回收器 SerialOldGC:采用标记压缩算法,单线程独占式垃圾回收器

#2、并行垃圾回收器

并行:在多个线程中执行垃圾回收操作。

新生代 ParNew 回收器:采用复制算法实现,多线程回收器,独占式垃圾回收器。

新生代 ParallelScavengeGC 回收器:采用复制算法多线程独占式回收器

老年代 ParallelOldGC 回收器: 采用标记压缩算法,多线程独占式回收器

  • CMS回收器

    CMS全称 (Concurrent Mark Sweep),是一款并发的、使用标记-清除算法的垃圾回收器。对CPU资源非常敏感。

    启用CMS回收器参数 :-XX:+UseConcMarkSweepGC。

    使用场景:GC过程短暂停顿,适合对时延要求较高的服务,用户线程不允许长时间的停顿。

    优点:最短回收停顿时间为目标的收集器。并发收集,低停顿。

    缺点:服务长时间运行,造成严重的内存碎片化。算法实现比较复杂。

  • G1回收器

    G1(Garbage-First)是一款面向服务端应用的并发垃圾回收器, 主要目标用于配备多颗CPU的服务器,治理大内存。是JDK1.7提供的一个新收集器,是当今收集器技术发展的最前沿成果之一。

    G1计划是并发标记-清除收集器的长期替代品。

    启用G1收集器参数:-XX:+UseG1GC启用G1收集器。

    G1将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了, 它们都是一部分Region(不需要连续)的集合。

    images

    每块区域既有可能属于Old区、也有可能是Young区,因此不需要一次就对整个老年代/新生代回收。而是当线程并发寻找可回收的对象时,有些区块包含可回收的对象要比其他区块多很多。虽然在清理这些区块时G1仍然需要暂停应用线程,但可以用相对较少的时间优先回收垃圾较多的Region(这也是G1命名的来源)。这种方式保证了G1可以在有限的时间内获取尽可能高的收集效率。

    特点:

    • 一整块堆内存被分成多个独立的区域Regions
    • 存活对象被拷贝到新的Survivor区
    • 新生代内存由一组不连续的堆heap区组成,使得可以动态调整各个区域
    • 多线程并发GC
    • young GC会有STW(Stop the world)事件

3、垃圾回收器对比

①新生代回收器
名称串行/并行/并发回收算法适用场景可以与CMS配合
SerialGC串行复制单CPU
ParNewGC并行复制多CPU
ParallelScavengeGC并行复制多CPU且关注吞吐量
②老年代回收器
名称串行/并行/并发回收算法适用场景
SerialOldGC串行标记压缩单CPU
ParNewOldGC并行标记压缩多CPU
CMS并发,几乎不会暂停用户线程标记清除多CPU且与用户线程共存

第五节 finalize 机制

#1、总体机制介绍

java.lang.Object 类中有一个方法:

protected void finalize() throws Throwable { }

方法体内是空的,说明如果子类不重写这个方法,那么不执行任何逻辑。

images

  • 在执行 GC 操作前,调用 finalize() 方法的是 Finalizer 线程,这个线程优先级很低。
  • 在对象的整个生命周期过程中,finalize() 方法只会被调用一次。

2、代码验证

public class FinalizeTest {

    // 静态变量
    public static FinalizeTest testObj;

    @Override
    protected void finalize() throws Throwable {
        // 重写 finalize() 方法
        System.out.println(Thread.currentThread().getName() + " is working");

        // 给待回收的对象(this)重新建立引用
        testObj = this;
    }

    public static void main(String[] args) {

        // 1、创建 FinalizeTest 对象
        FinalizeTest testObj = new FinalizeTest();

        // 2、取消引用
        testObj = null;

        // 3、执行 GC 操作
        System.gc();

        // ※ 让主线程等待一会儿,以便调用 finalize() 的线程能够执行
        try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {}

        // 4、判断待回收的对象是否存在
        if (FinalizeTest.testObj == null) {
            System.out.println("待回收的对象没有获救,还是要被 GC 清理");
        } else {
            System.out.println("待回收的对象被成功解救");
        }

        // 5、再次取消引用
        FinalizeTest.testObj = null;

        // 6、再次执行 GC 操作
        System.gc();

        // 7、判断待回收的对象是否存在
        if (FinalizeTest.testObj == null) {
            System.out.println("待回收的对象没有获救,还是要被 GC 清理");
        } else {
            System.out.println("待回收的对象被成功解救");
        }
    }

}

执行效果:

Finalizer is working
待回收的对象被成功解救
待回收的对象没有获救,还是要被 GC 清理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
JVMJava虚拟机)内存模型和垃圾回收Java程序中重要的概念。JVM内存模型定义了Java程序在运行时所使用的内存结构,而垃圾回收是一种自动化的内存管理机制,用于回收不再使用的对象以释放内存空间。 JVM内存模型主要包括以下几个部分: 1. 堆(Heap):堆是JVM中最大的一块内存区域,用于存储对象实例。在堆中分配的内存由垃圾回收器自动管理。 2. 方法区(Method Area):方法区用于存储类的信息、常量、静态变量等数据。在JDK 8及之前的版本中,方法区被实现为永久代(Permanent Generation),而在JDK 8之后,被改为元空间(Metaspace)。 3. 虚拟机栈(VM Stack):每个线程在运行时都会创建一个虚拟机栈,用于存储局部变量、方法调用和返回信息等。每个方法在执行时都会创建一个栈帧(Stack Frame),栈帧包含了方法的局部变量表、操作数栈、动态链接、返回地址等信息。 4. 本地方法栈(Native Method Stack):本地方法栈与虚拟机栈类似,但用于执行本地方法(Native Method)。 垃圾回收JVM的一项重要功能,它负责自动回收不再使用的内存。JVM中的垃圾回收器会定期扫描堆中的对象,标记出不再被引用的对象,并将其回收释放。垃圾回收可以有效地避免内存泄漏和内存溢出的问题,提高程序的性能和稳定性。 JVM内存模型和垃圾回收Java程序员需要了解和理解的重要概念,它们直接影响到Java程序的性能和内存使用情况。合理地管理内存和优化垃圾回收对于编写高效、稳定的Java程序至关重要。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙龙龙呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值