G1垃圾回收器回收机制
垃圾标记过程
初始标记
这个阶段我们G1回暂停应用程序的执行,直接扫描根对象,标记这些对象直接引用的对象,这些根对象可能包括活跃线程栈帧、静态变量等,初始标记是一个短暂的停顿,目的就是标记一部分活跃的对象,方便后续的标记过程能够准确的追踪对象的引用关系。
并发标记
在并发标记的时候不会暂停我们的应用程序,我们的G1会扫描我们整个堆,从初始标记的对象开始,递归的查询对象的之间的关联,并标记所有可达对象。
最终标记
在并发标记结束后,G1会再次进行一次短暂的停顿,进行最终的标记,这个阶段,G1会标记在并发标记期间产生的新对象,
筛选
G1垃圾回收会选择一个最有价值内存区域进行回收(年轻代、老年代),这环节也包括了,年轻代内存A转到年轻代内存B的一个过程。
垃圾回收方法是
Young GC(年轻代垃圾回收)
这里的垃圾回收主要针对Eden和Survivor区域的对象进行回收。
当Eden去和Survivor区填满的时候会触发。
存活的对象可能移动到老年代,可能移动到幸存者区域。
Mixed GC(混合垃圾回收)
这个可以同时回收一部分老年代和年轻代中的对象, 该方法关注的点是减少停顿的同时对整个堆进行有效的回收。
它会动态选择优先级最高的区域进行回收。
FUll GC (完全回收)
对整个Java堆进行完整的垃圾回收包括老年代、年轻代。
只有在老年代碎片化严重的时候或者堆内存达到阈值的时候才会触发
FULL GC停留的时间较长,他需要对整个堆内存进行回收。
创建在各个内存中心分配的原理
新创建的对象放到Eden区,然后Eden区满了以后会进行一次Minor GC,然后Eden区存活的幸存者会放到幸存者B区,然后幸存者A区的幸存者也会放到幸存者B区,然后清空A区,然后幸存者B区变成A区,A区变B区,这么做的原因就是防止内存碎片。幸存区的对象如果存活了n次,就会转移到老年代,当然如果是大对象也会直接存放在老年代
如何避免内存泄漏
内存泄漏就是分配的内存对象没有被正确释放,导致内存长期占用,导致系统性能下降。
长期存活的对象未被回收。
G1的工作目的就是优化垃圾回收过程在最小化的停顿时间,如果我们的对象对设计为长期存在的对象,即使G1也无法回收。
java
import java.util.ArrayList;
import java.util.List;
public class LongLivedObjectExample {
// 静态列表,可能导致对象长期存活
private static List<Object> longLivedList = new ArrayList<>();
public static void main(String[] args) {
// 创建大量对象并添加到列表中
for (int i = 0; i < 100000; i++) {
Object obj = new Object();
longLivedList.add(obj);
}
// 模拟长期保持对列表对象的引用
while (true) {
try {
// 睡眠一段时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印列表的大小,以及一些其他操作
System.out.println("List size: " + longLivedList.size());
}
}
}
对象引用不正确。
如果对象之间形成了循环引用,而且至少有一个是强引用,即使对象不在使用了,也无法回收。如果一定出现循环引用可以使用弱引用或者虚引用来解决。
java
public class CircularReferenceExample {
static class Node {
Node next;
Node(Node next) {
this.next = next;
}
}
public static void main(String[] args) {
Node node1 = new Node(null);
Node node2 = new Node(null);
// 创建循环引用,node1.next 引用到 node2,node2.next 引用到 node1
node1.next = node2;
node2.next = node1;
// 此时,即使我们不再使用 node1 和 node2,它们也不会被垃圾回收器回收
// 因为它们之间形成了循环引用,且至少有一个是强引用
}
}
使用弱引用解决
java
import java.lang.ref.WeakReference;
public class WeakReferenceExample {
public static void main(String[] args) {
// 创建一个对象
Object obj = new Object();
// 创建一个弱引用,并引用这个对象
WeakReference<Object> weakRef = new WeakReference<>(obj);
// 置空原始对象的引用,使其只有弱引用
obj = null;
// 通过弱引用获取对象
Object retrievedObj = weakRef.get();
System.out.println("Retrieved object: " + retrievedObj);
// 手动触发垃圾回收
System.gc();
// 由于只有弱引用引用了对象,如果垃圾回收器认为需要回收对象,则弱引用的get()方法返回null
retrievedObj = weakRef.get();
System.out.println("Retrieved object after garbage collection: " + retrievedObj);
}
}
使用虚拟引用解决
java
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
public class PhantomReferenceExample {
public static void main(String[] args) {
// 创建一个对象
Object obj = new Object();
// 创建一个引用队列
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
// 创建一个虚引用,并指定引用队列
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, referenceQueue);
// 置空原始对象的引用,使其只有虚引用
obj = null;
// 虚引用对象可以随时被回收,因此 get() 方法始终返回 null
Object retrievedObj = phantomRef.get();
System.out.println("Retrieved object: " + retrievedObj);
// 手动触发垃圾回收
System.gc();
// 从引用队列中获取已被回收的虚引用
Reference<? extends Object> refFromQueue = referenceQueue.poll();
if (refFromQueue != null) {
System.out.println("Phantom reference enqueued");
} else {
System.out.println("No phantom reference enqueued");
}
}
}
没有及时的释放文件句柄、数据库连接
这个很简单,使用完记得释放,数据库连接为例Mybatis已经帮我们做了, 所以我们日常开发没有手动写释放。