
public class SystemGCTest {
public static void main(String[] args) {
new SystemGCTest();
System.gc();//提醒jvm的垃圾回收器执行gc,但是不确定是否马上执行gc
//System.gc()与Runtime.getRuntime().gc() 的作用一样。
System.runFinalization();//强制调用失去引用对象的finalize()方法
}
//GC回收之前会调用finalize()方法
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("SystemGCTest 重写了finalize()");
}
}

public class LocalVarGC {
public void localvarGC1() {
byte[] buffer = new byte[10 * 1024 * 1024];//10MB
System.gc();
}
public void localvarGC2() {
byte[] buffer = new byte[10 * 1024 * 1024];
buffer = null;
System.gc();
}
public void localvarGC3() {
{
byte[] buffer = new byte[10 * 1024 * 1024];
}
System.gc();
}
public void localvarGC4() {
{
byte[] buffer = new byte[10 * 1024 * 1024];
}
int value = 10;
System.gc();
}
public void localvarGC5() {
localvarGC1();
System.gc();
}
public static void main(String[] args) {
LocalVarGC local = new LocalVarGC();
local.localvarGC1();
}
}
设置JVM参数 -XX:+PrintGCDetails
代码示例1
/**
* 触发 Minor GC 没有回收对象,然后在触发 Full GC 将该对象存入 old 区
*/
public void localvarGC1() {
byte[] buffer = new byte[10 * 1024 * 1024];
System.gc();
}

代码示例2
/**
* 触发 YoungGC 的时候,已经被回收了
*/
public void localvarGC2() {
byte[] buffer = new byte[10 * 1024 * 1024];
buffer = null;
System.gc();
}

可以看到在触发Young GC的时候就已经将该对象回收了
代码示例3
/**
* 不会被回收,因为它还存放在局部变量表索引为 1 的槽中
*/
public void localvarGC3() {
{
byte[] buffer = new byte[10 * 1024 * 1024];
}
System.gc();
}



代码示例4
/**
* 会被回收,因为它还存放在局部变量表索引为 1 的槽中,但是后面定义的 value 把这个槽给重用了
*/
public void localvarGC4() {
{
byte[] buffer = new byte[10 * 1024 * 1024];
}
int value = 10;
System.gc();
}


代码示例5
/**
* localvarGC5中的数组已经被回收
*/
public void localvarGC5() {
localvarGC1();
System.gc();
}

首先说没有空闲内存的情况:说明 Java 虚拟机的堆内存不够。原因有二:
举个生活中的例子:
买房子:80 平的房子,但是有 10 平是公摊的面积,我们是无法使用这 10 平的空间,想用又用不了,想去掉又去不了,这就是所谓的内存泄漏
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ECoaXxZE-1658275553174)(https://cdn.nlark.com/yuque/0/2022/png/2707463/1647760937578-770162e1-11d1-4147-b32f-533265ed8cd8.png#clientId=u31ddd08b-f4d5-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=285&id=uf22b2c6d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=511&originWidth=1079&originalType=url&ratio=1&rotation=0&showTitle=false&size=300855&status=done&style=none&taskId=u961dfd79-5876-4418-9d02-ad9a87bd0cd&title=&width=601)]
Java 使用可达性分析算法,最上面的数据不可达,就是需要被回收的。后期有一些对象不用了,按道理应该断开引用,但是存在一些链没有断开,从而导致没有办法被回收。
/**
* PrintThread 线程每秒钟打印一次
* WorkThread 线程每创建10000个数组就进行GC一下
*/
public class StopTheWorldDemo {
public static class WorkThread extends Thread {
List<byte[]> list = new ArrayList<byte[]>();
public void run() {
try {
while (true) {
for (int i = 0; i < 1000; i++) {
byte[] buffer = new byte[1024];
list.add(buffer);
}
if (list.size() > 10000) {
list.clear();
System.gc(); //会触发full gc,进而会出现STW事件
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
public static class PrintThread extends Thread {
public final long startTime = System.currentTimeMillis();
public void run() {
try {
while (true) {
// 每秒打印时间信息
long t = System.currentTimeMillis() - startTime;
System.out.println(t / 1000 + "." + t % 1000);
Thread.sleep(1000);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
public static void main(String[] args) {
WorkThread w = new WorkThread();
PrintThread p = new PrintThread();
w.start();
p.start();
}
}
可以看到程序运行结果并不是每秒准时打印的,这是因为GC过程中会产生STW,用户线程会稍微卡顿一定的时间



并发和串行,在谈论垃圾收集器的上下文语境中,它们可以解释如下:

并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾回收线程在执行时不会停顿用户程序的运行。

如何在 GC 发生时,检查所有线程都跑到最近的安全点停顿下来呢?
实际执行过程:
我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存中;如果内存空间在进行垃圾收集后还是很紧张,则可以抛弃这些对象。
【既偏门又非常高频的面试题】强引用、软引用、弱引用、虚引用有什么区别?具体使用场景是什么?
在 JDK 1.2 版之后,Java 对引用的概念进行了扩充,将引用分为:
这 4 种引用强度依次逐渐减弱。除强引用外,其他 3 种引用均可以在 java.lang.ref 包中找到它们的身影。如下图,显示了这 3 种引用类型对应的类,开发人员可以在应用程序中直接使用它们。

Reference 子类中只有终结器引用是包内可见的,其他 3 种引用类型均为 public,可以在应用程序中直接使用。
/**
* 强引用的测试
*/
public class StrongReferenceTest {
public static void main(String[] args) {
StringBuffer str = new StringBuffer("Hello,阿里巴巴");
StringBuffer str1 = str;
str = null;
System.gc(); //调用垃圾回收
try {
Thread.sleep(3000); //设置3秒钟延迟保证GC垃圾回收能够实现
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str1);
}
}
程序运行结果:

强引用例子:
StringBuffer str = new StringBuffer("hello 阿里巴巴");

如果此时,再运行一个赋值语句StringBuffer str = new StringBuffer("Hello,阿里巴巴");StringBuffer str1 = str;
对应的内存结构为:

那么我们将 str = null; 则原来堆中的对象也不会被回收,因为还有其它对象指向该区域
本例中的两个引用,都是强引用,强引用具备以下特点:
软引用是用来描述一些还有用,但非必需的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
注意,这里的第一次回收是不可达的对象
一句话概括:当内存足够时,不会回收软引用的可达对象;内存不够时,才会回收软引用的可达对象。
在 JDK 1.2 版之后提供了 java.lang.ref.SoftReference 类来实现软引用
// 声明强引用
Object obj = new Object();
// 创建一个软引用
SoftReference<Object> sf = new SoftReference<>(obj);
obj = null; //销毁强引用,这个操作是必须的,不然会存在强引用和软引用
/**
* 软引用的测试:内存不足即回收
*/
public class SoftReferenceTest {
public static class User {
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int id;
public String name;
@Override
public String toString() {
return "[id=" + id + ", name=" + name + "] ";
}
}
public static void main(String[] args) {
//创建对象,建立软引用
// SoftReference userSoftRef = new SoftReference(new User(1, "songhk"));
//上面的一行代码,等价于如下的三行代码
User u1 = new User(1, "songhk");
SoftReference<User> userSoftRef = new SoftReference<User>(u1);
u1 = null;//取消强引用
//从软引用中重新获得强引用对象
System.out.println(userSoftRef.get()); //[id=1, name=songhk]
System.gc();
System.out.println("After GC:");
//垃圾回收之后获得软引用中的对象
System.out.println(userSoftRef.get()); //[id=1, name=songhk] 由于堆空间内存足够,所有不会回收软引用的可达对象。
//
try {
//让系统认为内存资源不够
// byte[] b = new byte[1024 * 1024 * 7]; //会报OOM,软引用为null
//让系统认为内存资源紧张
byte[] b = new byte[1024 * 7168 - 350 * 1024]; //不会报OOM,软引用为null
} catch (Throwable e) {
e.printStackTrace();
} finally {
//再次从软引用中获取数据
System.out.println(userSoftRef.get());//在报OOM之前,垃圾回收器会回收软引用的可达对象。
}
}
}
设置JVM参数 -Xms10m -Xmx10m
程序运行结果

//让系统认为内存资源紧张

在 JDK 1.2 版之后提供了 WeakReference 类来实现弱引用:
/**
* 弱引用的测试
*/
public class WeakReferenceTest {
public static class User {
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int id;
public String name;
@Override
public String toString() {
return "[id=" + id + ", name=" + name + "] ";
}
}
public static void main(String[] args) {
//构造了弱引用
WeakReference<User> userWeakRef = new WeakReference<User>(new User(1, "songhk"));
//从弱引用中重新获取对象
System.out.println(userWeakRef.get()); // [id=1, name=songhk]
System.gc();
// 不管当前内存空间足够与否,都会回收它的内存
System.out.println("After GC:");
//重新尝试从弱引用中获取对象
System.out.println(userWeakRef.get()); //null
}
}
弱引用对象与软引用对象的最大不同就在于:当 GC 在进行回收时,需要通过算法检查是否回收软引用对象,而对于弱引用对象,GC 总是进行回收。弱引用对象更容易、更快被 GC 回收。
面试题:你开发中使用过 WeakHashMap 吗?
WeakHashMap 用来存储图片信息,可以在内存不足的时候,及时回收,避免了 OOM。
在 JDK 1.2 版之后提供了 PhantomReference 类来实现虚引用。
// 声明强引用
Object obj = new Object();
// 声明引用队列
ReferenceQueue phantomQueue = new ReferenceQueue();
// 声明虚引用(还需要传入引用队列)
PhantomReference<Object> sf = new PhantomReference<>(obj, phantomQueue);
obj = null;
虚引用的测试:https://www.bilibili.com/video/BV1PJ411n7xZ?p=167&spm_id_from=pageDriver
/**
* 虚引用的测试
*/
public class PhantomReferenceTest {
public static PhantomReferenceTest obj; //当前类对象的声明
static ReferenceQueue<PhantomReferenceTest> phantomQueue = null; //引用队列
/**
* 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,
* 将这个虚引用加入引用队列,以通知应用程序对象的回收情况。
* 这个线程就是来操作引用队列的
*/
public static class CheckRefQueue extends Thread {
@Override
public void run() {
while (true) {
//第2次GC的时候将obj对象回收了,此时引用队列就会存在虚引用了
if (phantomQueue != null) {
PhantomReference<PhantomReferenceTest> objt = null;
try {
//从引用队列里面取出虚引用
objt = (PhantomReference<PhantomReferenceTest>) phantomQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (objt != null) {
System.out.println("追踪垃圾回收过程:PhantomReferenceTest实例被GC了");
}
}
}
}
}
/**
* 垃圾回收之前会先调用finalize()
*
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable { //finalize()方法只能被调用一次!
super.finalize();
System.out.println("调用当前类的finalize()方法");
obj = this; //让当前对象重新被引用
}
public static void main(String[] args) {
Thread t = new CheckRefQueue();
t.setDaemon(true); //设置为守护线程:当程序中没有非守护线程时,守护线程也就执行结束。
t.start();
phantomQueue = new ReferenceQueue<PhantomReferenceTest>(); //实例化引用队列
obj = new PhantomReferenceTest(); //实例化当前类对象
//构造了 PhantomReferenceTest 对象的虚引用,并指定了引用队列
PhantomReference<PhantomReferenceTest> phantomRef = new PhantomReference<PhantomReferenceTest>(obj, phantomQueue);
try {
//不可获取虚引用中的对象
System.out.println(phantomRef.get()); //null
//将强引用去除
obj = null;
//第一次进行GC,由于对象可复活,GC无法回收该对象
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj 是 null");
} else {
System.out.println("obj 可用"); //最终执行这一句
}
System.out.println("第 2 次 gc");
obj = null;
System.gc(); //一旦将obj对象回收,就会将此虚引用存放到引用队列中。
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj 是 null"); //最终执行这一句
} else {
System.out.println("obj 可用");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果

从上述运行结果我们知道,第一次尝试获取虚引用的值,发现无法获取的,这是因为虚引用是无法直接获取对象的值,然后进行第一次 GC,因为会调用 finalize() 方法,将对象复活了,所以对象没有被回收,但是调用第二次 GC 操作的时候,因为 finalize() 方法只能执行一次,所以就触发了 GC 操作,将对象回收了,同时将会触发第二个操作就是将回收的值存放到引用队列中。