项目地址
jvm_04
在理解回收机制算法之前,我们需要了解。如何判断一个对象是否该被回收。
如何判断一个对象是否该被回收
在了解java的回收机制之前,我们可以了解一下,初期python的垃圾回收机制。顺带一提,比如python/java等语言都是基于c/c++来写的,但是C、C++却不存在垃圾回收机制。
Java中垃圾回收是自动化的,但其可控性差,内存容易溢出。内存溢出是因为JVM内存分配的对象过多,这些对象所需内存超出了JVM内存大小。虽然Java中是自动的。但是程序员仍可调用System.gc( )来进行手动回收,调用此方法会尝试释放被丢弃的对象占用的内存,但结果无法保证,因此附带一个免责声明。下面我们将从如何确定需要被回收的对象、什么时候回收、怎样进行回收这三个方面进行分析。
怎么确定对象需要回收?
1.回收对象方法
2.回收时间
CPU空闲时进行回收、堆内存满了后进行回收、程序运行中内存不够用时、调用System.gc()回收。
3.回收方法
现在了解基本的概念。那么我们来看看可达性分析算法。至于引用计数法中的循环引用类似于循环依赖,这里就不再提及了。
怎么去了解这个可达性分析算法呢?
核心思想:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。那我们怎么去查看这个引用链?
可以使用工具MemoryAnalyzer。
代码:
import java.io.IOException; /** * @author 龙小虬 * @date 2021/4/14 22:30 */ public class MemoryAnalyzerTest { public static void main(String[] args) throws IOException { Entity user = new Entity("lxq"); System.out.println("user拥有引用地址"); System.in.read(); user = null; System.out.println("user被清空了引用地址"); System.in.read(); System.in.read(); System.out.println("end"); } }
运行代码。
项目目录下运行命令:jps
获取线程的pid
并且运行命令:jmap -dump:format=b,live,file=a.bin 50416
获取到a.bin文件。打开MemoryAnalyzer,并且添加a.bin文件。配置:
之后就可以看到解析出了一部分数据。
系统class是永远不会释放的,因为他是属于main()方法定义的,只有在main()方法结束才会释放。
这里还有我们熟悉的锁,这个锁也是不会释放空间的,因为我们使用的synchronized就是这里面的。
好了,现在我们就直接找到当前的线程。
可以发现存在一个Entity对象被分配。
那我们返回到程序,随意输入一个数据,让程序向下运行。
使用命令jmap -dump:format=b,live,file=b.bin 50416
获取到b.bin文件,操作同上a.bin,最后可以找到当前的堆内存。
可以看到Entity已经消失了。这就是GC root回收,这些都是他的引用链。
我们在上面的图片可以看到
在Java中,可作为GC Root的对象包括以下几种:
这个我们了解了之后,来认识一下四种引用。
/** * @author 龙小虬 * @date 2021/4/14 23:03 */ public class StrongReference { public static void main(String[] args) { Entity user1 = new Entity("lxq"); Entity user2 = user1; user1=null; System.out.println(user2); } }
我们可以发现,我们这里已经把user1清空了,但是user2依旧存在数据。
这就是强引用,在Entity user1 = new Entity("lxq");
也属于强引用,但是为了更好的理解所以才有后面的代码。
import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.ArrayList; /** * @author 龙小虬 * @date 2021/4/14 23:13 * -Xmx8m */ public class SoftReferences { public static void main(String[] args) { ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<byte[]>(); ArrayList<SoftReference<byte[]>> objects = new ArrayList<SoftReference<byte[]>>(); for (int i = 0; i < 10; i++) { SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[4 * 1024 * 1024], referenceQueue); objects.add(softReference); } Reference<? extends byte[]> poll = referenceQueue.poll(); System.out.println("打印结果:"); objects.forEach((t) -> { System.out.println(t.get()); }); } }
我们可以看到结果:
前面的数据全部变为了null,为什么会这样?我们打印的是objects数据,也就是对象ArrayList,它存储的是ReferenceQueue<byte[]>对象的地址,因为内存不够,所以相应地址的内存被释放,所以前面的变为了null,那设置的是30M,不应该有两个不为null?因为不可能所有的内存都给ArrayList,所以只剩下一个不为null。
我们也可以看看是不是真的是这样。加入-XX:+PrintGCDetails -verbose:gc
可以看到有很多的Full GC,这也验证了我们这个软引用,是在内存不足的时候被回收了内存,那么这么多的null,我们要怎么办?怎么去清理null?加入代码:
while (poll != null) { objects.remove(poll); poll = referenceQueue.poll(); }
清除null数据即可。
最终代码:
import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.ArrayList; /** * @author 龙小虬 * @date 2021/4/14 23:13 * -Xmx8m * -Xmx8m -XX:+PrintGCDetails -verbose:gc */ public class SoftReferences { public static void main(String[] args) { ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<byte[]>(); ArrayList<SoftReference<byte[]>> objects = new ArrayList<SoftReference<byte[]>>(); for (int i = 0; i < 10; i++) { SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[4 * 1024 * 1024], referenceQueue); objects.add(softReference); } Reference<? extends byte[]> poll = referenceQueue.poll(); while (poll != null) { objects.remove(poll); poll = referenceQueue.poll(); } System.out.println("打印结果:"); objects.forEach((t) -> { System.out.println(t.get()); }); } }
import java.lang.ref.WeakReference; /** * @author 龙小虬 * @date 2021/4/14 23:06 */ public class WeakReferences { public static void main(String[] args) { Entity user1 = new Entity("lxq"); WeakReference<Entity> user2 = new WeakReference<Entity>(user1); user1 = null; System.gc(); System.out.println(user2.get()); } }
这里可以发现一旦强引用user1被清空,那么user2在被调用GC回收之后,也会为空,因为new Entity("lxq");
已经被回收了,而user2与它只是存在弱引用的关系,所以无法将它留下。
import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; /** * @author 龙小虬 * @date 2021/4/14 23:24 */ public class PhantomReferenceTset { public static void main(String[] args) { Entity user = new Entity("lxq"); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); PhantomReference<Entity> phantomReference = new PhantomReference<Entity>(user, referenceQueue); System.out.println(phantomReference.get()); } }