JVM垃圾收集(一)----判断对象“存活”或“死亡”&四种垃圾收集算

判断对象“存活” 或 “死亡”

(一)引用计数器法

思路:为对象添加一个引用计数器,对象被引用一次,计数器加1,;当引用失效时,计数器减1;当对象的计数器为0时,则判断对象不可用;
存在问题:引用计数器无法解决对象间的相互循环引用,因此主流的java虚拟机未使用该方式管理内存。、
示例:

public class ReferenceCountingGc {
	public Object instanceObject = null;
	private static final int capacity =1024*1024;
	/**
	 * 定义一个成员变量,占用内存,在垃圾回收时从日志体现垃圾回收过程
	 */
	private byte[] bigSize = new byte [2*capacity];
	
	public static void main(String[] args) {
		ReferenceCountingGc obj1 = new ReferenceCountingGc();
		ReferenceCountingGc obj2 = new ReferenceCountingGc();
		obj1.instanceObject = obj2;
		obj2.instanceObject = obj2;
		obj2=null;
		obj1=null;
		System.out.println("----------------------------------");
		//手动进行垃圾回收,通过日志查看两个对象回收
		System.gc();
	}

}

GC日志:

[GC[DefNew: 3133K->481K(4928K), 0.0036787 secs] 3133K->2529K(15872K), 0.0037363 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
----------------------------------
[Full GC[Tenured: 2048K->480K(10944K), 0.0031065 secs] 4679K->480K(15872K), [Perm : 183K->183K(12288K)], 0.0031472 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 4992K, used 44K [0x25200000, 0x25760000, 0x2a750000)
  eden space 4480K,   1% used [0x25200000, 0x2520b398, 0x25660000)
  from space 512K,   0% used [0x25660000, 0x25660000, 0x256e0000)
  to   space 512K,   0% used [0x256e0000, 0x256e0000, 0x25760000)
 tenured generation   total 10944K, used 480K [0x2a750000, 0x2b200000, 0x35200000)
   the space 10944K,   4% used [0x2a750000, 0x2a7c8208, 0x2a7c8400, 0x2b200000)
 compacting perm gen  total 12288K, used 183K [0x35200000, 0x35e00000, 0x39200000)
   the space 12288K,   1% used [0x35200000, 0x3522dd70, 0x3522de00, 0x35e00000)
    ro space 10240K,  44% used [0x39200000, 0x3967b1c8, 0x3967b200, 0x39c00000)
    rw space 12288K,  52% used [0x39c00000, 0x3a2431d0, 0x3a243200, 0x3a800000)

  • 4679K->480K : System.gc()执行后,内存完成回收;没有因为两个对象互相引用而无法造成内存无法回收,也就说明虚拟没用使用引用计数器法来判断对象存活。

(2)可达性分析法

从一个GC ROOTs 对象节点开始,依次往下搜索,搜索所走过的路径称为引用链,,当一个对象与GC ROOTS节点没有引用链,则认为该对象“死亡”,则表明该对象可以回收。
示例
image

实际上,对象回收并不是没有引用链就立即被回收,被回收要经历两次标记才能最终被回收

引用:

JDK1.2之对引用定义:如Reference类型存储的值是指向另一块内存的起始地址,就称这块内存是一个引用。
JDK1.2后,将引用的概念进行扩充;将引用分为强引用、软引用、弱引用、引用

  • 强引用: new 出来的对象,强引用不会被垃圾收集器回收
  • 软引用: 一些有用但是非必须的对象;在内存即将溢出时将这部分对象进行回收
  • 弱引用: 被弱引用关联的对象只能活到下一次垃圾回收期之前,无论内存是否会发生溢出,都会回收;
  • 虚引用:“幽灵”引用,对象是否有虚引用不影响其生存时间

四种垃圾收集算法

标记-清除算法

顾名思义,算法需要经过“标记”、“清除”两步,先标记所有要回收的对象,再一次性清除标记的对象来回收内存
清理前:

image
清除后:

image
image
缺点:
(1)标记、清除都要扫描整个内存区,标记清除效率低;
(2)产生大量内存碎片,当大对象需要分配连续内存时,没有连续内存会导致再次出发垃圾收集动作;

复制算法

复制算法:将内存分为相同大小的两块,每次只使用其中一块,这块使用完后,将存活对象复制到另一块上,再将使用过的内存一次性清理
清理前:

image
复制清理后:

image
缺点:
(1)将内存缩小了一半

实际内存并非按照1:1来划分的,而是将内存划分为Eden区和两块小的survivor,HosPsot虚拟机默认比例Eden:survivor:survivir = 8:1:1;每次收集,将eden和Survivor区域存活对象复制到另外一块儿Survivor,再一次清理用过的的Eden和Survivor。

标记-整理算法

算法:该算法分为“标记”和“整理”两部分,标记和“标记清除-清除算法”标记过程一致,标记后不直接清除,而是将存活对象整理到内存另一端,然后清理以外的内存。

image
清理后:

image

分代收集算法

根据对象存活周期同将内存划分为几块,一般将堆内存分为新生代和老年代,根据不存代对象的特点进行对象清理。
(1)新生代:对象“朝生夕死”,垃圾收集时大量对象死亡,只存活少量对象,使用复制算法,只需要付出少量对象复制成本即可完成收集
(2)老年代:存活对象不会轻易被垃圾收集,且可能是大对象,没有额外空间进行分配担保,则必须采用“标记-整理”算法或“标记-清理”算法进行垃圾回收。

创建对象优进入新生代的Eden区域
大对象:需要连续占用内存空间的对象,如长字符串,大的数组
老年代:

  • 大对象直接进入老年代
  • 长期存活的对象进入老年代(虚拟机为每个对象设置年龄计数器,经过一次MinorGC并能成功进入>surivor内存空间,则年龄计数器+1,可以通过参数设置最大年龄)
    空间分配担保:新生代使用复制算法,当Minor GC时,任然存活大量对象,另外一块儿Survivor无法容纳,无法容纳的对象直接进入老年代,因此老年代需要提供容纳这些对象的空间
  • Minor GC : 发生在新生代的垃圾收集动作,由于新生代对象特点,Minor GC较为频繁
  • Full Gc /Major Gc :发生在老年代的垃圾收集动作,进行一个Major GC 伴随一次Minor GC (不是绝对),Major GC 的速度比Minor满
原文地址:https://www.cnblogs.com/ldd525/p/14822500.html