【JVM】一篇通关JVM垃圾回收

目录

  • 1. 如何判断对象可以回收
    • 1-1. 引用计数法
    • 1-2. 可达性分析算法
    • 1-3. 四种引用
      • 强引用
      • 软引用
      • 弱引用
      • 虚引用
      • 终结器引用
  • 2. 垃圾回收算法
    • 2-1. 标记清除
    • 2-2. 标记整理
    • 2-3. 复制
    • 2-4. 总结
  • 3. 分代垃圾回收
  • 4. 垃圾回收器
  • 5. 垃圾回收调优

1. 如何判断对象可以回收

1-1. 引用计数法

引用计数法

  • 只要一个对象被其他变量所引用,那么就让这个对象的计数+1
  • 如果其他变量不再引用,让这个对象的计数 -1
  • 让这个对象的引用计数为 0时,则说明没有变量引用了,就可以作为一个垃圾进行回收

引用计数法弊端

  • 循环引用问题,A对象与B对象循环引用,他们的引用计数始终为1,不能作为一个垃圾进行回收
  • 出现内存泄漏

【JVM】一篇通关JVM垃圾回收_第1张图片

1-2. 可达性分析算法

根对象:一些肯定不能作为垃圾的对象

可达性分析算法

  • 在垃圾回收之前,会对堆内存所有的对象进行扫描

  • 查看每一个对象是不是被根对象直接或间接引用

  • 如果是,则不能被垃圾回收

  • 如果不是,则可以被垃圾回收

  • Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象

  • 扫描堆中的对象,看是否能够沿着GC Root对象 (堆中对象) 为起点的引用链找到该对象,找不到,表示堆中对象可以回收

  • 哪些对象可以作为GC Root

    • 活动线程中局部变量所引用的堆中对象可以作为根对象

1-3. 四种引用

实线代表强引用

平时用的引用都是强引用,例如:赋值运算
【JVM】一篇通关JVM垃圾回收_第2张图片

强引用

  • 所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收。

软引用

  • 只有【软引用】引用该对象时,在垃圾回收后,内存仍不足 则会回收软引用对象
  • 可以配合【引用队列】来释放软引用自身,因为软引用自身也占用内存

★应用场景

  • 非核心业务资源(比如:图片)被强引用特别多时,有可能报OOM异常,因为强引用是不会被回收的,内存满直接抛异常
  • 那么就可以用【软引用】来指向这些资源,当内存不足时,回收这些资源。
  • 以后如果再使用图片资源,重新读取一遍。

弱引用

  • 只有【弱引用】引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
  • 可以配合【引用队列】来释放弱引用自身,因为弱引用自身也占用内存

虚引用

  • 必须配合【引用队列】使用
  • 例如 ByteBuffer 对象不再【强引用】时, ByteBuffer 对象本身可以被垃圾回收,但是占用的直接内存 是属于操作系统的,无法被回收。
  • 那么就可以将【虚引用】放入【引用队列】, 由 Reference Handler 线程调用虚引用相关方法释放【直接内存】
  • 总结:【虚引用】引用的对象被垃圾回收时,【虚引用】被放入【引用队列】,从而由一个线程可以调用【虚引用】的方法。

终结器引用

  • Object 类中有 finallize() 方法
  • 当一个类重写了Object 类中有 finallize() 方法,并且该对象没有被【强引用】时,就可以进行垃圾回收
  • 第一次垃圾回收时,将【终结器引用】放入【引用队列】,并且由一个优先级很低的Finalizer线程去寻找【终结器引用】的对象,找到后执行该对象的 finallize() 方法。
  • 直到第二次垃圾回收时,才将该对象进行垃圾回收。

软引用使用:

public class Demo1 {
	public static void main(String[] args) {
		final int _4M = 4*1024*1024;
		//list是强引用,byte数组是软引用
		List<SoftReference<byte[]>> list = new ArrayList<>();
		SoftReference<byte[]> ref= new SoftReference<>(new byte[_4M]);
	}
}

软引用配合引用队列使用:

public static void main(String[] args) throws IOException {
    ///使用引用队列,用于移除引用为空的软引用
    ReferenceQueue<byte[]> queue=new ReferenceQueue<>();
	
    List<SoftReference<byte[]>> list = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        //关联了引用队列,当软引用所关联的byte数组被回收时,软引用自己就会加入到引用队列queue中去
        SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB],queue);
        System.out.println(ref.get());
        list.add(ref);
        System.out.println(list.size());
    }
    
    //获取队列中第一个软引用
    Reference<? extends byte[]> poll = queue.poll();
	//遍历引用队列,如果有软引用,则移除
    while(poll!=null){
        list.remove(poll);
        poll=queue.poll();
    }
    System.out.println("=============");
    System.out.println("循环结束:" + list.size());
    for (SoftReference<byte[]> ref : list) {
        System.out.println(ref.get());
    }
}

弱引用使用:

弱引用的使用和软引用类似,只是将 SoftReference 换为了 WeakReference

public static void main(String[] args) {
	//list是强引用,byte数组是弱引用
    List<WeakReference<byte[]>> list=new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        WeakReference<byte[]> ref=new WeakReference<>(new byte[_4MB]);
        list.add(ref);
        for (WeakReference<byte[]> w : list) {
            System.out.print(w.get()+" ");
        }
        System.out.println();
    }
    System.out.println("循环结束:"+list.size());
}

2. 垃圾回收算法

2-1. 标记清除

【JVM】一篇通关JVM垃圾回收_第3张图片
标记清除

在垃圾回收的过程中

  • 标记:确定哪些对象是可回收对象,
  • 清除:标记好之后,清除可回收对象的内存,并不是将内存空间字节清零,而是记录内存起始地址。

注意:这里的清除并不是将内存空间字节清零,而是记录这段内存的起始地址,下次分配内存的时候,会直接覆盖这段内存。

优点: 速度快

缺点: 容易产生内存碎片。一旦分配较大内存的对象,由于内存不连续,导致无法分配,最后就会造成内存溢出问题

2-2. 标记整理

【JVM】一篇通关JVM垃圾回收_第4张图片

标记整理

  • 先采用标记算法确定可回收对象
  • 然后整理剩余的对象内存,将可用的对象内存移动到一起,使内存更加紧凑,连续的空间就更多。

优点:不会有内存碎片

缺点:速度慢

2-3. 复制

【JVM】一篇通关JVM垃圾回收_第5张图片
【JVM】一篇通关JVM垃圾回收_第6张图片

复制算法

  • 将内存分为等大小的两个区域,FROM和TO(TO中为空)。
  • 将被GC Root引用的对象从FROM放入TO中,然后回收不被GC Root引用的对象。
  • 回收完之后交换FROM和TO两个区域。这样也可以避免内存碎片的问题,但是会占用双倍的内存空间。

优点:不会有内存碎片

缺点:会占用双倍的内存空间。速度慢

2-4. 总结

  • JVM会根据不同的情况来采用这3种算法
  • 不会只使用一种算法

3. 分代垃圾回收

4. 垃圾回收器

5. 垃圾回收调优

你可能感兴趣的:(JVM虚拟机,java)