public class SystemGCTest {
public static void main(String[] args) {
new SystemGCTest();
System.gc();//提醒jvm的垃圾回收器执行gc,但是不确定是否马上执行gc
//与Runtime.getRuntime().gc();的作用一样。
System.runFinalization();//强制调用使用引用的对象的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();
//输出: 不会被回收, FullGC时被放入老年代
//[GC (System.gc()) [PSYoungGen: 14174K->10736K(76288K)] 14174K->10788K(251392K), 0.0089741 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
//[Full GC (System.gc()) [PSYoungGen: 10736K->0K(76288K)] [ParOldGen: 52K->10649K(175104K)] 10788K->10649K(251392K), [Metaspace: 3253K->3253K(1056768K)], 0.0074098 secs] [Times: user=0.01 sys=0.02, real=0.01 secs]
}
public void localvarGC2() {
byte[] buffer = new byte[10 * 1024 * 1024];
buffer = null;//引用制空
System.gc();//再次进行GC,buffer对应的堆空间就没有引用指向了,属于不可达的了,就要把他回收掉
//输出: 正常被回收
//[GC (System.gc()) [PSYoungGen: 14174K->544K(76288K)] 14174K->552K(251392K), 0.0011742 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//[Full GC (System.gc()) [PSYoungGen: 544K->0K(76288K)] [ParOldGen: 8K->410K(175104K)] 552K->410K(251392K), [Metaspace: 3277K->3277K(1056768K)], 0.0054702 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
}
public void localvarGC3() {
{//代码块中声明一个变量,看看GC能不能回收
byte[] buffer = new byte[10 * 1024 * 1024];
}
System.gc();
//输出: 不会被回收, FullGC时被放入老年代
//[GC (System.gc()) [PSYoungGen: 14174K->10736K(76288K)] 14174K->10784K(251392K), 0.0076032 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
//[Full GC (System.gc()) [PSYoungGen: 10736K->0K(76288K)] [ParOldGen: 48K->10649K(175104K)] 10784K->10649K(251392K), [Metaspace: 3252K->3252K(1056768K)], 0.0096328 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]
}
public void localvarGC4() {
{
byte[] buffer = new byte[10 * 1024 * 1024];
}
int value = 10;
System.gc();
//输出: 正常被回收
//[GC (System.gc()) [PSYoungGen: 14174K->496K(76288K)] 14174K->504K(251392K), 0.0016517 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
//[Full GC (System.gc()) [PSYoungGen: 496K->0K(76288K)] [ParOldGen: 8K->410K(175104K)] 504K->410K(251392K), [Metaspace: 3279K->3279K(1056768K)], 0.0055183 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
}
public void localvarGC5() {
localvarGC1();
System.gc();
//输出: 正常被回收
//[GC (System.gc()) [PSYoungGen: 14174K->10720K(76288K)] 14174K->10744K(251392K), 0.0121568 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
//[Full GC (System.gc()) [PSYoungGen: 10720K->0K(76288K)] [ParOldGen: 24K->10650K(175104K)] 10744K->10650K(251392K), [Metaspace: 3279K->3279K(1056768K)], 0.0101068 secs] [Times: user=0.01 sys=0.02, real=0.01 secs]
//[GC (System.gc()) [PSYoungGen: 0K->0K(76288K)] 10650K->10650K(251392K), 0.0005717 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//[Full GC (System.gc()) [PSYoungGen: 0K->0K(76288K)] [ParOldGen: 10650K->410K(175104K)] 10650K->410K(251392K), [Metaspace: 3279K->3279K(1056768K)], 0.0045963 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
}
public static void main(String[] args) {
LocalVarGC local = new LocalVarGC();
local.localvarGC5();
}
}
首先说没有空闲内存的情况:说明Java虚拟机的堆内存不够。原因有二:
java. lang. OutOfMemoryError: PermGen space
”。随着元数据区的引入,方法区内存已经不再那么窘迫,所以相应的00M有所改观,出现00M,异常信息则变成了:“java. lang. OutOfMemoryError: Metaspace"
。 直接内存不足,也会导致0OM。
这里面隐含着一层意思是,在抛出0utOfMemoryError之 前,通常垃圾收集器会被触发,尽其所能去清理出空间。
当然,也不是在任何情况下垃圾收集器都会被触发的
创建PrintThread没隔1秒会打印一次
public class StopTheWorldDemo {
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) {
PrintThread p = new PrintThread();
p.start();
}
}
我们加入一个WorkThread线程,创建一个10M的list,然后执行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();
}
}
每次触发full gc,进而会出现STW事件,整个用户的线程会卡顿一下
并发和并行,在谈论垃圾收集器的上下文语境中,它们可以解释如下:
我们希望能描述这样一类对象: 当内存空间还足够时,则能保留在内存中;如果内存空间在进行垃圾收集后还是很紧张,则可以抛弃这些对象。
0bject obj=new object( )
”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。public class StrongReferenceTest {
public static void main(String[] args) {
StringBuffer str = new StringBuffer ("Hello,尚硅谷");
StringBuffer str1 = str;
//str 和str1都是强引用,str与str1都指向堆空间的统一地址
str = null;
//str制空后,还有str1指向堆空间地址,所以不能回收
System.gc();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str1);
}
}
StringBuffer str = new StringBuffer ("Hello,尚硅谷");
此时,如果再运行一个赋值语句:
StringBuffer str1 = str;
本例中的两个引用,都是强引用,强引用具备以下特点:
软引用是用来描述一 些还有用,但非必需的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
软引用通常用来实现内存敏感的缓存。比如:高速缓存就有用到软引用。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
垃圾回收器在某个时刻决定回收软可达的对象的时候,会清理软引用,并可选地把引用存放到一个引用队列( Reference Queue)。
类似弱引用,只不过Java虚拟机会尽量让软引用的存活时间长一些,迫不得.已才清理。
软引用:
在JDK 1. 2版之后提供了java.lang.ref.SoftReference类来实现软引用。
Object obj = new object();//声明强引用
//创建了一个软引用,这个对象,即有一个强引用的关联,又有一个软引用的关联
SoftReference<0bject> sf = new SoftReference<0bject>(obj);
//销毁强引用
obj = null; //销毁强引用
/**
* 软引用的测试:内存不足即回收
* -Xms10m -Xmx10m -XX:+PrintGCDetails
*/
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());
System.gc();
System.out.println("After GC:");
// //垃圾回收之后获得软引用中的对象
System.out.println(userSoftRef.get());//由于堆空间内存足够,所有不会回收软引用的可达对象。
try {
//让系统认为内存资源紧张、不够
// byte[] b = new byte[1024 * 1024 * 7];
byte[] b = new byte[1024 * 7168 - 635 * 1024];//恰好能放下数组又放不下u1的内存分配大小 不会报OOM,但是还会回收u1
} catch (Throwable e) {
e.printStackTrace();
} finally {
//再次从软引用中获取数据
System.out.println(userSoftRef.get());//在报OOM之前,垃圾回收器会回收软引用的可达对象。
}
}
}
由于堆空间内存足够,所有不会回收软引用的可达对象。当执行到try,byte[] b = new byte[1024 * 1024 * 7];
让系统认为内存资源紧张、不够,在报OOM之前,垃圾回收器会回收软引用的可达对象。 System.out.println(userSoftRef.get());
,再次从软引用中获取数据为null
恰好能放下数组又放不下u1的内存分配大小 不会报OOM,但是还会回收u1
Object obj = new object(); //声明强引用
WeakReference<0bject> sf = new WeakReference<0bject>(obj);
obj = null; //销毁强引用
通过查看WeakHashMap源码,可以看到其内部类Entry使用的就是弱引用
line 702 -> private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {...}
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());
System.gc();
// 不管当前内存空间足够与否,都会回收它的内存
System.out.println("After GC:");
//重新尝试从弱引用中获取对象
System.out.println(userWeakRef.get());
}
}
虚引用(Phantom Reference),也称为“幽灵引用”或者“幻影引用”,是所有引用类型中最弱的一个。
一个对象是否有虚引用的存在,完全不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它和没有引用几乎是一样的,随时都可能被垃圾回收器回收。
它不能单独使用,也无法通过虚引用来获取被引用的对象。当试图通过虚引用的get()方法取得对象时,总是null。
为一个对象设置虚引用关联的唯一目的在于跟踪垃圾回收过程。比如:能在这个对象被收集器回收时收到一个系统通知。
虚引用必须和引用队列一起使用。虚引用在创建时必须提供一个引用队列作为参数。当垃圾回收器准备回收一个对象时,如果发现它还有虛引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序对象的回收情况。
由于虚引用可以跟踪对象的回收时间,因此,也可以将一些资源释放操作放置在虛引用中执行和记录。
在JDK 1. 2版之后提供了PhantomReference类来实现虚引用。
object obj = new object();
ReferenceQueuephantomQueue = new ReferenceQueue( ) ;
PhantomReference<object> pf = new PhantomReference<object>(obj, phantomQueue);
obj = null;
public class PhantomReferenceTest {
public static PhantomReferenceTest obj;//当前类对象的声明
static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;//引用队列
@Override
protected void finalize() throws Throwable { //finalize()方法只能被调用一次!
super.finalize();
System.out.println("调用当前类的finalize()方法");
obj = this;
}
public static void main(String[] args) {
phantomQueue = new ReferenceQueue<PhantomReferenceTest>();
obj = new PhantomReferenceTest();
//构造了 PhantomReferenceTest 对象的虚引用,并指定了引用队列
PhantomReference<PhantomReferenceTest> phantomRef = new PhantomReference<PhantomReferenceTest>(obj, phantomQueue);
//不可获取虚引用中的对象
System.out.println(phantomRef.get());
}
}
它不能单独使用,也无法通过虚引用来获取被引用的对象。当试图通过虚引用的get()方法取得对象时,总是null。
public class PhantomReferenceTest {
public static PhantomReferenceTest obj;//当前类对象的声明
static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;//引用队列
@Override
protected void finalize() throws Throwable { //finalize()方法只能被调用一次!
super.finalize();
System.out.println("调用当前类的finalize()方法");
obj = this;
}
public static void main(String[] args) {
phantomQueue = new ReferenceQueue<PhantomReferenceTest>();
obj = new PhantomReferenceTest();
//构造了 PhantomReferenceTest 对象的虚引用,并指定了引用队列
PhantomReference<PhantomReferenceTest> phantomRef = new PhantomReference<PhantomReferenceTest>(obj, phantomQueue);
try {
//不可获取虚引用中的对象
System.out.println(phantomRef.get());
//将强引用去除
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无法回收该对象,第二次GC,finalize只能调用一次,所以对象被回收了
增加一个线程CheckRefQueue ,一开始的时候由于phantomQueue是null,所以一开始的时候可以理解为是一个空转循环。
当我们的第二次GC操作一执行的时候,把obj对象回收掉了,一旦将obj对象回收,就会将此虚引用存放到引用队列中。phantomQueue 就不是Null了, 通过objt = (PhantomReference
取出最近的虚引用对象,再判断objt != null
输出System.out.println("追踪垃圾回收过程:PhantomReferenceTest实例被GC了");
public class PhantomReferenceTest {
public static PhantomReferenceTest obj;//当前类对象的声明
static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;//引用队列
public static class CheckRefQueue extends Thread {
@Override
public void run() {
while (true) {
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了");
}
}
}
}
}
@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());
//将强引用去除
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();
}
}
}