垃圾收集器与内存分配策略

说起垃圾回收,我们大概关注三个问题:

  • 哪写内存需要回收
  • 什么时候回收
  • 如何回收

下面我们来一一解释:

  1. 回收的对象是已经不需要的,也就是没有引用指向的对象,那么如何确定对象是否已死,有如下几种算法:
    1. 引用计数法
    给对象添加一个引用计数器, 有引用就加一,失效就减一。但是它没办法解决对象之间循环引用的问题。
public class ReferenceCountingGC {
	public Object instance = null;
	private static final int _1MB = 1024*1024;
	private byte[] bigSize = new byte[2*_1MB];
	
	public static void testGC() {
		ReferenceCountingGC objA = new ReferenceCountingGC();
		ReferenceCountingGC objB = new ReferenceCountingGC();
		
		objA.instance = objB;
		objB.instance = objA;
		
		objA = null;
		objB = null;
		
		// 假定发生GC
		System.gc();
 	}
	public static void main(String[] args) {
		testGC();
	}
}

上述代码演示了Java虚拟机并没有因为循环引用而不回收,所以Java虚拟机不是通过引用计数来判断对象是否存活的。

  1. 可达性分析
    通过一系列的称为“GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
    在Java中,可作为GC Roots的对象包括下面几种:

    1. 虚拟机栈中引用的对象
    2. 方法区中静态属性引用的对象
    3. 方法区中常量引用的对象。
    4. 本地方法栈中JNI引用的对象。
      引用类型:
    5. 强引用:类似于Object obj = new Object(), 只要强引用还在,就不会被回收
    6. 软引用:用来描述一些有用但是并非必需的对象,使用SoftReference来实现软引用。
      软引用会在内存要发生溢出之前回收,看回收了这些之后还会不会发生内存溢出
    7. 弱引用:强度比软引用更弱,使用WeakReference来实现弱引用。
      再下一次垃圾回收前一定会被回收
    8. 虚引用,又称幽灵引用,它的存在可以用形同虚设来说明,使用PahantomReference来实现。
      它存在的唯一目的是能在这个对象被回收时收到一个系统通知。

    那么对于不可达的对象就一定会被回收吗?其实对象还有一次自我拯救的机会。
    因为如果要真正宣告一个对象死亡,至少要经过两次标记。 在第一次标记中,会进行一次筛选,筛选的条件是此对象是否有必要执行finallize方法。若对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,就没有必要执行。 如果某个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在F-Queue队列中,并稍后由虚拟机触发执行。因为这里会执行finalize方法,所以可以在这个方法中将对象重新赋予某个引用,则拯救自己。

public class FinalizeEscapeGC {
	public static FinalizeEscapeGC SAVE_HOOK = null;
	
	public void isAlive(){
		System.out.println("yes, i am still alive: ");
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("Finalize method executed!");
		FinalizeEscapeGC.SAVE_HOOK = this;
	}
	
	public static void main(String[] args) throws InterruptedException {
		SAVE_HOOK = new FinalizeEscapeGC();
		
		// 对象第一次成功拯救自己
		SAVE_HOOK = null;
		System.gc();
		// 因为finalize方法优先级低,所以暂停0.5s以等待它
		Thread.sleep(500);
		if(SAVE_HOOK != null)
			SAVE_HOOK.isAlive();
		else
			System.out.println("no, i am dead :(");
		
		// 第二次拯救失败
		SAVE_HOOK = null;
		System.gc();
		// 因为finalize方法优先级低,所以暂停0.5s以等待它
		Thread.sleep(500);
		if(SAVE_HOOK != null)
			SAVE_HOOK.isAlive();
		else
			System.out.println("no, i am dead :(");
	}
}

现在,已经明确了要回收的内存。

  1. 垃圾回收算法

    1. 标记-清除算法
      这个算法分为“标记“和”清除“两个阶段,首先对要回收的内存进行标记,然后统一回收。
      这个算法清除后会留下大量的空间碎片,导致之后无法分配大块内存。
    2. 复制算法
      它将可用内存按容量分为大小相等的两块,每次只使用其中的一块。当这一块内存用完了,就将还存活的对象复制到另一个对象上去,然后再把已使用的空间一次性回收
    3. 标记-整理算法
      先标记,然后不是直接清除,而是让存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
  2. HotSpot的算法实现
    使用一组成为OopMap的数据结构来标记那些地方存放着对象引用,以便在寻找GC Roots的时候不用全部检查。
    在OopMap的协助下,HotShot可以快速且准确的完成GC Roots的枚举。但还需保证一致性,就是在算法期间不能再有引用关系变化,HotShot中靠安全点来实现。程序执行时只能在到达安全点时才能暂停,称作safepoint。

  3. HotSpot虚拟机所包含的垃圾收集器

    1. Serial收集器
      它在JDK1.3.1之前,是虚拟机新生代的唯一选择。这是一个单线程收集器,它在收集的时候,必须暂停其他所有线程。虽然它有这么大的缺点,但到现在1.7为止,他还是虚拟机运行在Client模式下的默认新生代收集器,因为它简单高效,没有线程交互的开销。
      在这里插入图片描述

    2. ParNew收集器
      这个收集器是Serial的多线程版本,它是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关的主要原因是因为除了Serial收集器之外,目前只有它能够与CMS收集器配合工作。
      在这里插入图片描述

    3. Parallel Scavenge收集器是一个新生代收集器,使用复制算法,并且是一种并行的多线程收集器。
      这个收集器的目标是达到一个可控制的吞吐量,而不是像其他收集器一样是为了尽可能地缩短垃圾收集时用户线程的停顿时间。这个收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。示意图与2类似

    4. Serial Old收集器
      这个收集器是Serial收集器的老生代版本,主要用于Client模式下的虚拟机。如果用在Server模式下,它有两个作用:1. 在JDK1.5一起与Parallel Scavenge收集器搭配使用。 2. 作为CMS收集器的后备预案,在并发集发生Concurrent Mode Failure使用。示意图见1。

    5. Parallel Old 收集器
      于1.6后开始提供,是Parallel Scavenge收集器的老年版本,应用于吞吐量优先的场合。
      在这里插入图片描述

    6. CMS收集器
      这是一种以获取最短回收停顿时间为目标的收集器。基于“标记-清除”算法,整个过程分为四个步骤:

      1. 初始标记

      2. 并发标记

      3. 重新标记

      4. 并发清除
        其中,初始标记和重新标记这两个步骤仍然需要“stop the world"。
        初始标记只是标记一下GC Roots能直接关联到的对象,速度很快。
        并发标记就是进行GC Roots Tracing的过程。
        重新标记是为了修正在并发标记期间因用户程序继续运作而导致标记变动的对象
        从整体上说,CMS收集器的内存回收过程是与用户线程一起并发执行的,具体如图:
        在这里插入图片描述
        优点

        1. 并发收集
        2. 低停顿

        缺点

        1. 对cpu资源非常敏感
        2. 会有大量空间碎片产生
        3. 无法处理浮动垃圾
    7. G1 收集器
      这个收集器伴随jdk1.7诞生,是一款面向服务端应用的垃圾收集器。
      特点:

      1. 并发与并行
        使用多个CPU来缩短Stop-The-world停顿的时间。
      2. 分代收集
        对不同代的对象采用不同的回收方式。
      3. 空间整合
        G1从整体上来看是基于”标记-整理“算法,从局部上来看是基于复制,但是都不会产生内存空间碎片。
      4. 可预测的停顿
        这是G1相对于CMS的另一大优势,G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定一个在长度为M毫秒的时间片段外,消耗在垃圾收集上的时间不得超过N毫秒。

      G1收集器将Java堆划分成多个大小相等的独立区域(Region),并且采用回收价值优先的策咯进行回收。
      问题:和其他收集器老生代和新生代中的对象可能相互引用一样,G1收集器中的不同Region之间也可能相互引用,那么是不是在判断某个Region的存活对象的时候还需要遍历整个Java堆才能保证准确性?
      在G1收集器中,采用Remembered Set 来避免遍历整个Java堆。
      步骤如图:
      在这里插入图片描述

  4. 内存分配与回收策略

    1. 对象优先在(新生代的)Eden分配
    2. 大对象直接进入老年代
    3. 长期存活的对象将进入老年代
    4. 动态对象年龄判断
    5. 空间分配担保
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值