目录
垃圾回收,我们首先要判断一个对象是否是垃圾,即这个对象是否已经不再被使用到。
一、对象的生死
1.1 引用计数法
在对象中添加一个引用计数器,如果一个地方引用了它,则计数器+1,相应的一个引用失效时,计数器减一;计数器为0为不可再被使用。
原理简单,效率高,但无法解决循环引用问题。
主流的java虚拟机未使用此方法
public class _01Reference {
public Object instance=null;
private static final int _1MB=1024*1024;
private byte[] bigSize=new byte[2*_1MB];
/**
* -XX:+PrintGC 输出GC日志
* -XX:+PrintGCDetails 输出GC的详细日志
* -XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
* -XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
* -XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
* -Xloggc:../logs/gc.log 日志文件的输出路径
* @param args
*/
public static void main(String[] args) {
_01Reference r1=new _01Reference();
_01Reference r2=new _01Reference();
r1.instance=r2;
r2.instance=r1;
r1=null;
r2=null;
System.gc();
}
}
两个对象互相引用,但虚拟机没有因为两个对象互相引用而不进行GC
1.2 可达性分析法
该算法是当前主流的商用语法的内存管理子系统所采取的,也是JVM所采取的。
该算法试通过一系列**“GC Roots"的根对象作为起始节点集合**,从这些节开始根据引用关系向下搜索,搜索过程走过的路径称为“引用链“,如果某个对象到GC Roots没有引用链,说明此对象时不可被利用的。
简单说,就是从根节点集合的所有不可达的对象判定为死亡,可达的对象判定为活跃的。
如上:虽然对象5,6,7互相存在关联,但它们是从GC Roots不可达的,因为被判定为可回收对象。
可作为GC Roots的对象:
-
栈中(栈帧中的本地变量表)引用的对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象(如字符串常量池里的引用)
-
本地方法栈中JNI(Native方法)引用的对象
-
虚拟机内部引用(基本数据类型对应的Class对象,常驻的异常对象(NPE、OOM),系统类加载器)
-
所有被synchronize关键字持有的对象
1.3 引用
java引用分为 强引用、软引用、弱引用和虚引用
整体架构
位于java.lang.ref包下:
强引用
类似Object obj=new Object()
这样的引用关系就叫做强引用
对于强引用的对象,即使出现OOM也不会回收该对象
Object obj1=new Object();
Object obj2=obj1;
obj1=null;
System.gc();
Sytem.out.printIn(obj2)
如上代码,obj2不会进行回收
软引用
位于java.lang.ref.SoftReference
一些有用,但非必须的对象。在系统将要出现内存溢出前,会将这些对象列为第二次回收对象,如果此次回收还没有足够内存,则抛出OOM。
即 内存够用 就 不回收
内存不够用 就 回收 ----> 回收还不够 -----> OOM
用在内存敏感的地方,如高速缓存
/**
* 内存够用时 软引用对象不被回收
*/
public static void memoryEnough(){
Object obj1=new Object();
SoftReference<Object>softReference=new SoftReference<>(obj1);
System.out.println(obj1);
System.out.println(softReference.get());
obj1=null;
System.gc();
System.out.println(obj1);//null
System.out.println(softReference.get());//有对象
}
/**
* 内存不够用,会回收软引用对象
* -Xms10M -Xmx10M -XX:+PrintGCDetails
*/
public static void notEnough(){
Object obj1=new Object();
SoftReference<Object>softReference=new SoftReference<>(obj1);
System.out.println(obj1);
System.out.println(softReference.get());
obj1=null;
try {
byte[]bytes=new byte[20*1024*1024];
}finally {
System.out.println(obj1);//null
System.out.println(softReference.get());//null
}
}
软引用用途
缓存中会非常常用,我们使用软引用保存缓存数据,当发生OOM时,系统就会回收这些软引用的缓存数据。
弱引用
位于java.lang.ref.WeakReference
非必须对象,不管内存够不够用,都会被GC回收
public static void main(String[] args) {
Object o1=new Object();
WeakReference<Object>weakReference=new WeakReference<>(o1);
System.out.println(o1);//java.lang.Object@677327b6
System.out.println(weakReference.get());//java.lang.Object@677327b6
o1=null;
System.gc();
System.out.println(o1);//null
System.out.println(weakReference.get());//null
}
如上内存够用的话,发生GC仍然会被回收,内存不够也是会被回收的,这里就不演示了。
WeakHashMap
private static void weakHashMap() {
WeakHashMap<Integer,Object> map=new WeakHashMap<>();
Integer key=new Integer(1);
Object value="value";
map.put(key,value);
System.out.println(map);//{1=value}
key=null;
System.gc();
System.out.println(map);//{}
}
private static void hashMap(){
HashMap<Integer,String>map=new HashMap<>();
Integer key=new Integer(1);
String value="wml";
map.put(key,value);
System.out.println(map);//{1=wml}
key=null;
System.gc();
System.out.println(map);//{1=wml}
}
虚引用
位于java.lang.ref.PhantomReference
也称“幽灵引用”或“幻影引用”,最弱的引用关系
无法通过虚引用的get()来取得一个对象实例。
仅用于对象被回收时收到一个系统通知,或后续添加进一步处理,必须和引用队列一起使用。
public static void main(String[] args) throws InterruptedException {
Object o1=new Object();
ReferenceQueue<Object>queue=new ReferenceQueue<>();
PhantomReference<Object> phantomReference=new PhantomReference<>(o1, queue);
System.out.println(o1);//java.lang.Object@677327b6
System.out.println(phantomReference.get());//null
System.out.println("引用队列:"+queue.poll());//null
System.out.println("=============发生GC后========");
o1=null;
System.gc();
System.out.println(o1);//null
System.out.println(phantomReference.get());//null
System.out.println("引用队列:"+queue.poll());//java.lang.ref.WeakReference@14ae5a5
}
1.4 对象的自我拯救
一个对象的死亡至少需要两次标记。
第一次:即对GC Roots进行可达性分析后没有与之相连的引用连时
第二次:对象是否要执行finalize()方法
如果对象覆盖了finalize方法,并在该方法中重新与引用链上的任何一个对象建立连接,则代表该对象自我拯救成功,会被从被回收的集合中移除。
package com.wml.jvm.gc;
/**
* 1.对象可以在被GB时进行逃逸
* 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
* @author MaoLin Wang
* @date 2020/3/1216:39
*/
public class _02FinalizeEscapeGC {
public static Object obj1 =null;
@Override
protected void finalize() throws Throwable {
System.out.println("finalize方法执行");
obj1 =this;
}
private static void gc(){
System.gc();
System.out.println("开始垃圾回收");
}
public static void main(String[] args) throws InterruptedException {
obj1 =new _02FinalizeEscapeGC();
//对象第一次进行自我拯救(逃避GC)
obj1 =null;
gc();
//因为finalize方法优先级很低,所有等待一下
Thread.sleep(500);
if (obj1 !=null){
System.out.println("first->对象仍然存活");
}else {
System.out.println("first->对象已死亡");
}
//对象第二次进行自我拯救(逃避GC),但因为finalize已经被调用了一次,因此无法逃逸成功
obj1 =null;
System.gc();
//因为finalize方法优先级很低,所有等待一下
Thread.sleep(500);
if (obj1 !=null){
System.out.println("Second->对象仍然存活");
}else {
System.out.println("Second->对象已死亡");
}
}
}
结果:
调用了finalize方法
开始垃圾回收
first->对象仍然存活
Second->对象已死亡
但官方已不推荐这种做法,因为运行代价高,不确定性大。
1.5 方法区的回收
回收目标:
-
废弃的常量
某个常量没有其他任何地方引用它时,就会被回收。常量池中其他类、方法、字段的符号引用
-
不再使用的类型
类型被允许回收的条件如下:
a.该类所有的实例都被回收
b. 加载该类的类加载器已被回收
c. 该类对应的java.class.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
在大量使用反射、动态代理、CGLib等字节码框架都需要虚拟机能够进行类型卸载,以减少方法区压力。
======================================================================
其他相关笔记:
JVM笔记(一)java内存区域与内存溢出以及对象的创建、布局和定位
JVM笔记(三)垃圾收集算法以及HotSpot的算法实现(安全点、记忆集与卡表、写屏障、三色标记等)
JVM笔记《四》七个常见的垃圾收集器
JVM笔记(五)类加载机制、类加载器和双亲委派机制
================================================================
参考:
《深入理解java虚拟机第三版》