当前位置: 首页 > article >正文

JVM垃圾回收机制垃圾回收相关算法垃圾收集器

JVM垃圾回收

请添加图片描述

什么是垃圾

在运行过程中,如果一个对象没有被任何引用所指向,这个对象就称为垃圾对象

不清理垃圾对象,后续的对象可能会没有空间进行存储,会导致内存溢出等问题

早期和现代的垃圾回收

早期垃圾回收

早期c/c++都是手动申请和释放内存(malloc和free)

好处:可以精确管理内存,用的时候申请,不用的时候立即释放

缺点;给程序员的操作带来了一定的负担,一旦忘记释放内存,这一块内存就会一直占用

现代垃圾回收

现代java.python,c#等语言都是采用自动垃圾回收

好处:程序员不用再关心何时释放垃圾对象

缺点:降低程序员对于内存管理的能力,不能对内存进行精确管理

垃圾回收的区域

运行时数据区中的堆和方法区是回收的区域

堆是回收的重点区域,频繁回收新生代,较少的回收老年代

基本不回收方法区(只有Full GC整堆收集的时候才会对方法区进行回收)

请添加图片描述

内存溢出和内存泄露

内存溢出(Out Of Memory)

是指应用系统中存在无法回收的内存或者应用的内存过多

程序运行要用到的内存大于能提供的最大内存,此时程序无法运行,提示内存溢出

内存泄露

内存泄露又称"存储渗漏"。

只有当对象不会再被程序用到了,但是GC又不能回收他们的情况,才能称之为内存泄露。

内存泄露不会引起程序崩溃,但是一旦发生了内存泄漏,程序中可用内存就会被逐步蚕食,直至耗尽所有内存,最终出现内存溢出异常,导致程序崩溃

常见的可能引发内存泄露的问题:
单例模式

单例的生命周期和应用程序是一样长的,所以在单例程序中,如果持有对外部对象的引用的话,那么这个外部对象不能被回收,可能导致内存泄露

提供close()的资源未主动关闭导致内存泄露

例如:数据库链接dataSourse.getConnection(),网络Socket对象,IO流对象等操作,需要通过手动close关闭,否则无法被回收

垃圾回收相关算法

标记阶段算法

标记阶段主要判断那些对象已经死亡,这些死亡的对象就是垃圾对象,标记阶段算法就是把没有被任何引用指向的对象标记出来

这一步的作用是对后续的垃圾回收提供标记的对象

判断垃圾对象一般有两种方式:引用计数算法和可达性分析算法

引用计数算法(Reference Counting)

给对象中添加一个引用计数器:

每当有一个地方引用它,计数器就加1;

当引用失效,计数器就减一;

任何时候计数器为0的对象就不可能再被使用,此时可以进行垃圾回收;

这种算法实现简单,效率高,但是目前主流的虚拟机中并没有使用这个算法来进行内存管理

主要原因是它很难解决对象之间的循环引用问题,这种循环引用会造成内存泄露问题

如下图中的Object1和Object2除了相互引用对方之外,这两个对象之间再无任何引用;

请添加图片描述

public class ReferenceCountingGc {
    Object instance = null;
    public static void main(String[] args) {
        ReferenceCountingGc objA = new ReferenceCountingGc();
        ReferenceCountingGc objB = new ReferenceCountingGc();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
    }
}

可达性分析算法(根搜索算法,追踪性垃圾收集)

当前主流的程序语言的内存管理子系统使用的都是可达性分析算法来进行标记

可达性分析算法可以解决引用计数算法中的循环引用问题

该算法的主要思想是从一些称为"GC Roots"的活跃对象作为起点,从这些节点开始向下搜索

节点走过的路径称为引用链,若一个对象到GC Roots没有任何引用链相连,则该对象已死亡

下图中的Objtct5-7之间虽然存在引用关系,但它们到GC Roots不可达,因此这些对象需要被垃圾回收

请添加图片描述

可作为GC Roots的对象

1.虚拟机栈中引用的对象 :例如各个线程被调用的方法中使用到的参数,局部变量等

2.方法区中类静态属性引用的对象,比如:java类的引用类型静态变量

3.所有被同步锁synchronized持有对对象

4.Java虚拟机内部的引用

5.本地方法栈(Native方法)中引用的对象

6.JNI(Java Native interface)引用的对象

7,方法区中常量引用的对象

基本数据类型对应的Class对象,一些常驻的异常对象(如:NullPointerException,OutOfMemoryError),系统类加载器。

对象可以被回收,就代表一定会被回收吗?

即使在可达性分析算法中不可达的对象,也不是一定要死,这时候处于一种缓刑的状态,真正宣布死亡至少要经历两次标记过程;

可达性分析法中不可达的对象被第一次标记并且进行一次筛选筛选的条件是此对象是否有必要执行finalize方法,当对象没有覆盖finalize方法,或finalize方法已经被虚拟机调用过,虚拟机将这两种情况视为没有必要执行,finalize规定只会被调用一次。

被判定为需要执行的对象将会放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

永远不要主动调用某个对象的finalize()方法,应该交给垃圾回收机制调用。原因如下:

1.在使用finalize()时可能会导致对象复活。

2.finalize()方法的执行时间是没有保障的,它完全GC线程决定,极端情况下,若不发生GC,finalize()方案发将会没有执行机会。

3.一个糟糕的finalize()会严重影响GC的性能,比如finalize是一个死循环。

可以重写finalize()去执行一些对象回收前的最终操作,但是不要主动调用该方法。

由于finalize()存在,把对象分为三种状态

可触及的:使用中的对象,没有被判定为垃圾

可复活的:被判定为垃圾对象,但是还没有调用finalize(),有可能在finalize()中复活

public static void main(String[] args) {
		try {
			obj = new CanReliveObj();
			// 对象第一次成功拯救自己
			obj = null;
			System.gc();//调用垃圾回收器,触发FULL GC  也不是调用后立刻就回收的,因为线程的执行权在操作系统
			System.out.println("第1次 gc");
			// 因为Finalizer线程优先级很低,暂停2秒,以等待它
			Thread.sleep(2000);
			if (obj == null) {
				System.out.println("obj is dead");
			} else {
				System.out.println("obj is still alive");
			}

			System.out.println("第2次 gc");
			// 下面这段代码与上面的完全相同,但是这次自救却失败了
			obj = null;
			System.gc();
			// 因为Finalizer线程优先级很低,暂停2秒,以等待它
			Thread.sleep(2000);
			if (obj == null) {
				System.out.println("obj is dead");
			} else {
				System.out.println("obj is still alive");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
protected void finalize() throws Throwable {
		System.out.println("调用当前类重写的finalize()方法");
		obj = this;//当前待回收的对象在finalize()方法中与引用链上的一个对象obj建立了联系
	}

不可触及:第二次被判定为垃圾,finalize()已经调用过了

回收阶段算法

标记-复制算法

可以有多块内存,每次回收时,将正在使用的内存快中的存活对象,复制到另一块未使用的内存块中,然后清除原来的内存块。它是一种移动式的垃圾回收算法

特点

使用多块内存

回收时需要移动对象,适合内存小,存活对象少,垃圾对象多的场景(适合新生代)

回收后的内存碎片少

请添加图片描述

标记-清除算法

标记-清除(Mark-and-Sweep)算法分为“标记(Mark)”和“清除(Sweep)”阶段:

首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。

它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:

1效率问题:标记和清除两个过程效率都不高。
2空间问题:标记清除后会产生大量不连续的内存碎片。

请添加图片描述

特点

只需要一块内存空间即可,对于存活对象不需要进行移动,只清除垃圾对象,是不移动的垃圾回收算法

标记-压缩算法

这种算法是根据老年代的特点提出的一种标记算法,标记过程与标记-清除算法一致,在其的基础上,对于存活的对象进行移动处理,将所有的存活对象向同一端移动,直接清理掉边界外的内存,它是一种移动式的垃圾回收算法,垃圾回收后没有内存碎片

适合老年代这种垃圾回收频率不是很高的场景

请添加图片描述

分代收集

年轻代的对象,存活的少,垃圾多,使用标记复制算法进行回收

老年代的对象大,存活时间长,采用标记清除和标记压缩算法进行回收

分代收集的原因是优化垃圾回收的效率

垃圾收集器

垃圾收集器是jvm垃圾回收的具体实现

垃圾收集器种类很多,不同的虚拟机中可以使用不同的垃圾收集器

垃圾收集器的分类

从垃圾收集器线程数量上划分

单线程,垃圾收集器中只有一个线程在执行垃圾回收操作

多线程,垃圾收集器中有多个线程在执行垃圾回收操作

从工作模式上分

独占式:当垃圾回收线程在执行时,其他用户线程暂停(把用户线程暂停的现象称为stop the world)

并发式:垃圾回收线程和用户线程可以同时执行

从工作内存上分

新生代垃圾收集器

老年代垃圾收集器

jdk8支持以下垃圾回收器

Serial,Serial Old,ParNew,Parallel Scavenge,Parallel Old,CMS,G1

下图展示了7种不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。

请添加图片描述

CMS收集器(Concurrent Mark Sweep)并发标记删除

首创了垃圾回收线程和用户线程并发执行,追求低延时

初始标记阶段:独占式

并发标记阶段:并发式

重新标记阶段:独占式

并发清除阶段:并发式

G1(Garbage-First)收集器

G1垃圾收集器适用于整堆收集,不再区分新生代,老年代

适用于服务器场景

G1垃圾收集器,将每个区域再划分为更小的区域,例如将伊甸园区划分为多个小区域

优先回收垃圾对象较多的区域,故名Garbage-First–垃圾优先

也是支持并发执行

查看并设置垃圾收集器

打印默认垃圾回收器

-XX:+PrintCommandLineFlags -version
JDK 8 默认的垃圾回收器
年轻代使用 Parallel Scavenge GC
老年代使用 Parallel Old GC

打印垃圾回收详细信息

-XX:+PrintGCDetails -version

设置默认垃圾回收器

Serial 回收器

-XX:+UseSerialGC 年轻代使用 Serial GC, 老年代使用 Serial Old GC

ParNew 回收器

-XX:+UseParNewGC 年轻代使用 ParNew GC,不影响老年代。

CMS 回收器

-XX:+UseConcMarkSweepGC 老年代使用 CMS GC。

G1 回收器

-XX:+UseG1GC 手动指定使用 G1 收集器执行内存回收任务。
-XX:G1HeapRegionSize 设置每个 Region 的大小。


http://www.kler.cn/a/567857.html

相关文章:

  • 域名解析ip后如何查询该ip地址
  • 保姆级教程:用Chart.js实现柱状图与折线图联动
  • Harmony os next~鸿蒙原子化服务开发实战:天气卡片开发全解析
  • PostgreSQL中的模式(Schema)
  • 安全运营的“黄金4小时“:如何突破告警疲劳困局
  • SourceTree配置SSH步骤详解
  • 从入门到精通:Linux 权限管理(rwx/chmod/chown)
  • 论文写作指南
  • 绕过信息过滤与注入限制
  • [预订酒店]
  • Android Activity启动流程详解
  • yunedit-post ,api测试比postman更好
  • 使用tkinter有UI方式来拷贝Excel文件
  • 软件工程----4+1架构模型
  • C# .NET Core HttpClient 和 HttpWebRequest 使用
  • anythingLLM和deepseek4j和milvus组合建立RAG知识库
  • kubernetes 初学命令
  • PostgreSQL10 物理流复制实战:构建高可用数据库架构!
  • 建易WordPress
  • 基于PHP+MySQL校园新闻管理系统设计与实现