可达型分析
可达性分析的理论
基本思路:
通过一系列GC Roots的根对象作为起始节点集, 根据引用关系向下搜索,
搜索过程中走过的路径称为引用链,所有在这个引用链上的对象都是可达对象,
而其他的没有与GC Roots根对象关联的单独存在的引用链上的对象则为不可达对象
图示:
在内存中的场景:
上图的 object1 中有字段object2 , object3 的引用,
当我们把object1的引用ref赋值为null,
那么object1就变为了 上图右侧的不可达对象的图例了..需要被gc回收掉了,如下图引用
代码实现举例子:
@Data
public class Test {
public Test instance;
public int i;
public Test(int i) {
this.i = i;
}
}
public static void main(String[] args) {
Test test0 = new Test(0);
Test test1 = new Test(1);
Test test2 = new Test(2);
test0.instance=test1;
test1=test2;
System.out.println(test0.getInstance().getI());
}
执行结果 1
为什么不是2呢,test1已经被test2赋值了呀!
这是因为引用变量只能是指向某个对象的,而不能是指向引用的。
所以test0.instance 指向的是test1在堆中的对象,
所以在后面改变了test1的指向时, 并没有影响之前的指向.
public static void main(String[] args) {
Test test0 = new Test(0);
Test test1 = new Test(1);
Test test2 = new Test(2);
test0.instance=test1;
test1.instance=test2;
test0=null;
}
ps: test0=null则 test0的原映射对象是不在引用链上了,会被gc回收.
且test0.instance的由上例子可知是引用的对象test1, 但这里不会回收test1,
因为test1也自己定义了一个根对象, 所以test1还是在test1引用链上,但不在test0的引用链上了,
如果将test1=null也这样设置,那么test1也将会被gc回收;
问题:那么在平时的代码中我们要不要在使用后对象就将对象置空呢?
这个问题要看 JVM 垃圾回收-判断对象是否可以回收 中的描述了
可达性根枚举对象
GC Roots的对象
1. 虚拟机栈中的引用的对象 如:Object o = new Object(); o即为虚拟栈中的引用对象
2. 方法区中类静态属性引用的对象 如:public static String static_str="111"; static_str 即为引用对象
3. 方法区中常量引用的对象
4. 本地方法栈中 JNI(native方法)引用的对象
5. 被同步锁synchronize持有的对象
ps:这里提到了很多引用,我们下面将详细描述引用的分类,引用之所以进行设定不同的分类,
是因为在内存中数据的要求是多样的,
比如 我们希望在内存充足的情况下,保留这些类,但内存不足的时候,就进行回收(缓存机制)
引用是如何分类
强引用
使用方法
正常的写的java代码都是强引用99.999%
Object o = new Object();
这种就是强引用了,是不是在代码中随处可见,最亲切。
只要某个对象有强引用与之关联,这个对象永远不会被回收
即使内存不足,JVM宁愿抛出OOM,也不会去回收
回收的方法:
o = null;
示例:
我们需要新写一个类,然后重写finalize方法
public class Student {
@Override
protected void finalize() throws Throwable {
System.out.println("Student 被回收了");
}
}
public static void main(String[] args) {
Student student = new Student();
student = null;
System.gc();
}
运行结果:
Student 被回收了
注释:finalize方法是当gc时,系统来调用该方法
释放该对象在堆中占用的内存. 该方法在Object中
软引用
使用方法
用java.lang.ref.SoftReference 进行包装
SoftReferencestudentSoftReference=new SoftReference(new Student());
回收方法
gc自动处理
当内存不足,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用的包裹的对象给干掉
也就是只有在内存不足,JVM才会回收该对象
示例:
修改idea的堆内存大小 -Xmx20m 最大堆内存为20m , 设置内存追踪 -XX:+PrintGCDetails
SoftReference softReference = new SoftReference(new byte[1024*1024*10]);
System.out.println(softReference.get());
System.gc();
System.out.println(softReference.get());
byte[] bytes = new byte[1024 * 1024 * 10];
System.out.println(softReference.get());
创建一个软引用对象,里面包裹了byte[],byte[]占用了10M,然后又创建了10Mbyte[]
结果:
[GC (Allocation Failure) 5632K->1393K(19968K), 0.0049815 secs]
[B@763d9750
[GC (System.gc()) 16187K->12252K(19968K), 0.0014227 secs]
[Full GC (System.gc()) 12252K->11862K(19968K), 0.0101228 secs]
[B@763d9750
[GC (Allocation Failure) 12032K->11926K(19968K), 0.0004047 secs]
[GC (Allocation Failure) 11926K->11926K(19968K), 0.0002842 secs]
[Full GC (Allocation Failure) 11926K->11784K(19968K), 0.0049854 secs]
[GC (Allocation Failure) 11784K->11784K(19968K), 0.0002981 secs]
[Full GC (Allocation Failure) 11784K->1495K(16896K), 0.0066588 secs]
null
分析: 当gc回收时, 软
注释:一般是缓存使用
弱引用
使用方法
java.lang.ref.WeakReference包装
WeakReference weakReference = new WeakReference(new byte[1024*1024*10]);
System.out.println(weakReference.get());
回收方式
System.gc();
不管内存是否足够,只要发生GC,都会被回收
示例
WeakReference weakReference = new WeakReference(new byte[1]);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());
结果:
[GC (Allocation Failure) 5632K->1410K(19968K), 0.0016663 secs]
[B@763d9750
[GC (System.gc()) 5726K->2034K(19968K), 0.0015112 secs]
[Full GC (System.gc()) 2034K->1914K(19968K), 0.0113147 secs]
null
注释:弱引用在很多地方都有用到,比如ThreadLocal、WeakHashMap。
虚引用
使用方法:
当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中
ReferenceQueue queue = new ReferenceQueue();
PhantomReference reference = new PhantomReference(new byte[1], queue);
System.out.println(reference.get());
作用时,当gc回收的时候会产生一条记录到ReferenceQueue中
回收方式:
System.gc() 自动回收
示例:
@Data
@Builder
public class Student {
@Override
protected void finalize() throws Throwable {
System.out.println("Student 被回收了");
}
}
public static void main(String[] args) {
ReferenceQueue queue = new ReferenceQueue();
List bytes = new ArrayList<>();
PhantomReference reference = new PhantomReference(new Student(),queue);
new Thread(() -> {
for (int i = 0; i < 100;i++ ) {
bytes.add(new byte[1024 * 1024]);
}
}).start();
new Thread(() -> {
while (true) {
Reference poll = queue.poll();
if (poll != null) {
System.out.println("虚引用被回收了:" + poll);
}
}
}).start();
Scanner scanner = new Scanner(System.in);
scanner.hasNext();
}
结果:
[GC (Allocation Failure) 5632K->1372K(19968K), 0.0011861 secs]
[GC (Allocation Failure) 7004K->2185K(19968K), 0.0017764 secs]
[GC (Allocation Failure) 7205K->6394K(19968K), 0.0028557 secs]
[GC (Allocation Failure) 11653K->11554K(19968K), 0.0027832 secs]
[Full GC (Ergonomics) 11554K->11374K(19968K), 0.0134628 secs]
[Full GC (Ergonomics) 16624K->16456K(19968K), 0.0055213 secs]
[Full GC (Ergonomics) 18620K->18488K(19968K), 0.0098597 secs]
[Full GC (Allocation Failure) 18488K->18402K(19968K), 0.0115410 secs]
Student 被回收了
[Full GC (Ergonomics) 18949K->17924K(19968K), 0.0106182 secs]
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
at com.logistic.JVMTest.lambda$main$0(JVMTest.java:16)
at com.logistic.JVMTest$$Lambda$1/747464370.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
虚引用被回收了:java.lang.ref.PhantomReference@5eb93cd7
注释:
第一个线程往集合里面塞数据,随着数据越来越多,肯定会发生GC
第二个线程死循环,从queue里面拿数据,如果拿出来的数据不是null,就打印出来