三、垃圾回收
1.如何判断对象是否可以回收
①引用计数法——早期python中使用
当一个对象被引用时,就当引用对象的值加一,当值为 0 时,就表示该对象不被引用,可以被垃圾收集器回收。
这个引用计数法听起来不错,但是有一个弊端,如下图所示,循环引用时,两个对象的计数都为1,导致两个对象都无法被释放。
②可达性分析算法——JVM使用
VM 中的垃圾回收器通过可达性分析来探索所有存活的对象
扫描堆中的对象,看能否沿着 GC Root 对象为起点的引用链找到该对象,如果找不到,则表示可以回收
可以作为 GC Root 的对象有:
Ⅰ. 虚拟机栈(栈帧中的本地变量表)中引用的对象;
Ⅱ. 方法区中类静态属性引用的对象;
Ⅲ. 方法区中常量引用的对象;
Ⅳ. 本地方法栈中 JNI(即一般说的Native方法)引用的对象;
案例演示:
public static void main(String[] args) throws IOException {
ArrayList<Object> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add(1);
System.out.println(1);
System.in.read();
list = null;
System.out.println(2);
System.in.read();
System.out.println("end");
}
对于以上代码,可以使用如下命令将堆内存信息转储成一个文件,然后使用
Eclipse Memory Analyzer 工具进行分析。
第一步:
使用 jps 命令,查看程序的进程
第二步:
使用 jmap -dump:format=b,live,file=1.bin 16104 命令转储文件
dump:转储文件
format=b:二进制文件
file:文件名
16104:进程的id
第三步:打开 Eclipse Memory Analyzer 对 1.bin 文件进行分析。
分析的 gc root,找到了 ArrayList 对象,然后将 list 置为null,再次转储,那么 list 对象就会被回收
③四种引用
强引用:
只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
软引用(SoftReference):
仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象,可以配合引用队列来释放软引用自身
弱引用(WeakReference):
仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象,可以配合引用队列来释放弱引用自身
虚引用(PhantomReference):
必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存
终结器引用(FinalReference):
无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象。
Ⅰ.软引用&引用队列案例演示——SoftReference
/**
* 演示 软引用
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class Code_08_SoftReferenceTest {
public static int _4MB = 4 * 1024 * 1024;
public static void main(String[] args) throws IOException {
method2();
}
// 强引用,设置 -Xmx20m , 演示堆内存不足,
public static void method1() throws IOException {
ArrayList<byte[]> list = new ArrayList<>();
for(int i = 0; i < 5; i++) {
list.add(new byte[_4MB]);
}
System.in.read();
}
// 演示 软引用
public static void method2() throws IOException {
ArrayList<SoftReference<byte[]>> list = new ArrayList<>();
for(int i = 0; i < 5; i++) {
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
System.out.println("循环结束:" + list.size());
for(SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
}
method1 方法解析:
首先会设置一个堆内存的大小为 20m,然后运行 mehtod1 方法,会抛异常,堆内存不足,因为 mehtod1 中的 list 都是强引用
method2 方法解析:
在 list 集合中存放的是软引用对象,当内存不足时,会触发 full gc,将软引用的对象回收。细节如图:
可以看到第五次循环时发现内存不足,触发了垃圾回收,将前4个软引用对象全部回收;
上面的代码中,当软引用引用的对象被回收了,但是软引用还存在,所以,一般软引用需要搭配一个引用队列一起使用。
修改 method2 如下:
// 演示 软引用 搭配引用队列
public static void method3() throws IOException {
ArrayList<SoftReference<byte[]>> list = new ArrayList<>();
// 引用队列
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for(int i = 0; i < 5; i++) {
// 关联了引用队列,当软引用所关联的 byte[] 被回收时,软引用自己会加入到 queue 中去
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
// 从队列中获取无用的 软引用对象,并移除
Reference<? extends byte[]> poll = queue.poll();
while(poll != null) {
list.remove(poll);
poll = queue.poll();
}
System.out.println("=====================");
for(SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
此时已经为空的软引用本身也被移除了,输出结果如下:
Ⅱ.弱引用案例演示——WeakReference
弱引用
public class Code_09_WeakReferenceTest {
public static void main(String[] args) {
// method1();
method2();
}
public static int _4MB = 4 * 1024 *1024;
// 演示 弱引用
public static void method1() {
List<WeakReference<byte[]>> list = new ArrayList<>();
for(int i = 0; i < 10; i++) {
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB]);
list.add(weakReference);
for(WeakReference<byte[]> wake : list) {
System.out.print(wake.get() + ",");
}
System.out.println();
}
}
// 演示 弱引用搭配 引用队列
public static void method2() {
List<WeakReference<byte[]>> list = new ArrayList<>();
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for(int i = 0; i < 9; i++) {
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB], queue);
list.add(weakReference);
for(WeakReference<byte[]> wake : list) {
System.out.print(wake.get() + ",");
}
System.out.println();
}
System.out.println("===========================================");
Reference<? extends byte[]> poll = queue.poll();
while (poll != null) {
list.remove(poll);
poll = queue.poll();
}
for(WeakReference<byte[]> wake : list) {
System.out.print(wake.get() + ",");
}
}
}
2.垃圾回收算法
①标记清除Mark Sweep
对于没有被GC Root引用的对象做标记,然后直接清除
优点:速度较快
缺点:会产生内存碎片
②标记整理Mark Compact
对于有被GC Root引用的对象做标记并将其移动到连续内存空间中,其它全部清除
优点:没有内存碎片
缺点:速度慢
③复制Copy
将内存空间分为两部分FROM和TO,只在FROM区域存放对象,垃圾回收时先将有被GC Root引用的对象做标记并复制到TO区域中,
然后清除FROM区域的全部数据,最后让FROM和TO区域互换身份
优点:没有内存碎片
缺点:需要占用两倍内存空间
3.分代垃圾回收
标签:对象,list,System,笔记,GC,引用,JVM,new,out From: https://www.cnblogs.com/AvavaAva/p/17614648.html