JVM的GC动作不受程序控制,它在满足条件的时候,自动触发。
发生GC的时候,一个对象,JVM会找到一个引用它的祖先。如果发现祖先已经名存实亡了,它们就会被清理掉。能购躲过垃圾回收的祖先,比较特殊,它们就是GC Roots
从GC Roots向下追溯、搜索,会场一个Reference Chain,当一个对象不能和任何一个GC Roots产生关系时,就会被回收。

如图所示:5、6、7会被回收

垃圾回收就是围绕GC Roots去做的。

GC Roots有哪些

  • Java线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值。也就是栈帧相关的各种引用。
  • 所有当前被加载的Java类
  • Java类的引用类型静态变量
  • 运行时常量池的引用类型常量(String或Class类型)
  • JVM内部数据结构的引用,比如sun.jvm.hotspot.memory.Universe
  • 用于同步的监控对象,比如调用了对象的wait()方法
  • JNI handles ,包括global handles和local handles。

上面大体可以分为三类:

  • 活动线程相关的各种引用
  • 类的静态变量的引用
  • JNI引用

  • 活跃的引用,不是指对象,对象不能作为GC Roots
  • GC 过程是找出所有活对象,并把其余空间认定为“无用”;而不是找出所有死掉的对象,并回收占用的空间。

引用级别

强引用

当内存空间不足,JVM就会抛出OutOfMemoryError。即使程序异常,这些对象也不会被回收。
Object obj=new Object()
就是强引用。

一个OOM例子
系统被大量用户访问,但是需要记录一个属性,比如访问时间,但是User对象没有该字段,此时必须通过额外方法记录。

static Map<User,Long> userVisitMap = new HashMap<>();
...
userVisitMap.put(user, time);

用完User对象后,由于它被map引用,无法remove掉,此时就发生了内存泄露。

软引用

用来维护可有可无的对象,内存足够时,不会被回收,只有内存不足时,则会回收软引用对象。
这种特性非常适合在缓存技术上,比如网页缓存、图片缓存。


Guava的CacheBuilder就提供了软引用和弱引用的设置方式。


软引用可以和一个引用队列联合使用,软引用所引用的对象被垃圾回收,JVM就会把这个软引用加入到与之关联的引用队列中。

软引用需要显示声明,使用泛型实现。

// 伪代码
Object object = new Object();
SoftReference<Object> softRef = new SoftReference(object);

弱引用

当JVM进行垃圾回收时,无论内存是否充足,都会回收弱引用关联的对象。

// 伪代码
Object object = new Object();
WeakReference<Object> softRef = new WeakReference(object);

虚引用 Phantom Reference

必须和引用队列联合使用,任何时候都可能被回收,虚引用的get总返回null。

Object  object = new Object();
ReferenceQueue queue = new ReferenceQueue();
// 虚引用,必须与一个引用队列关联
PhantomReference pr = new PhantomReference(object, queue);

主要用于跟踪对象被垃圾回收的活动。

典型的OOM场景


除了程序计数器,其他区域都有可能OOM。

典型的内存泄露场景:局部变量被外部静态map引用。

Last modification:March 30th, 2020 at 10:43 pm