System.gc()
者 Runtime.getRuntime().gc()
的调用,会显式触发 Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。System.gc()
调用附带一个免责声明,无法保证对垃圾收集器的调用。(不能确保立即生效)System.gc()
调用来决定 JVM 的 GC 行为。而一般情况下,垃圾回收应该是自动进行的,无须手动触发,否则就太过于麻烦了。在一些特殊情况下,如我们正在编写一个性能基准,我们可以在运行之间调用 System.gc()
。代码示例
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()");
}
}
System.runFinalization()
会强制调用失去引用对象的finalize( )
方法System.gc( )
与System.runFinalization( )
是一起使用的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();
}
}
-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();
}
代码示例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();
}
OutOfMemoryError
的解释是,没有空闲内存,并且垃圾收集器也无法提供更多内存。首先说没有空闲内存的情况:说明 Java 虚拟机的堆内存不够。原因有二:
-Xms
、-Xmx
来调整。OutOfMemoryError
也非常多见,尤其是在运行时存在大量动态类型生成的场合;类似 intern 字符串缓存占用太多空间,也会导致 OOM 问题。对应的异常信息,会标记出来和永久代相关:“java.lang.OutOfMemoryError:PermGen space
"。java.lang.OutOfMemoryError:Metaspace
"。直接内存不足,也会导致 OOM。OutOfMemoryError
之前,通常垃圾收集器会被触发,尽其所能去清理出空间。
java.nio.BIts.reserveMemory()
方法中,我们能清楚的看到,System.gc()
会被调用,以清理空间。OutOfMemoryError
。OutOfMemory
异常,导致程序崩溃。举个生活中的例子:
买房子:80 平的房子,但是有 10 平是公摊的面积,我们是无法使用这 10 平的空间,想用又用不了,想去掉又去不了,这就是所谓的内存泄漏
Java 使用可达性分析算法,最上面的数据不可达,就是需要被回收的。后期有一些对象不用了,按道理应该断开引用,但是存在一些链没有断开,从而导致没有办法被回收。
dataSourse.getConnection()
),网络连接(Socket)和 IO 连接必须手动 close,否则是不能被回收的。System.gc();
会导致 Stop-The-World 的发生。代码示例
/**
* 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();
}
}
并发和串行,在谈论垃圾收集器的上下文语境中,它们可以解释如下:
实际执行时:
我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存中;如果内存空间在进行垃圾收集后还是很紧张,则可以抛弃这些对象。
【既偏门又非常高频的面试题】强引用、软引用、弱引用、虚引用有什么区别?具体使用场景是什么?
在 JDK 1.2 版之后,Java 对引用的概念进行了扩充,将引用分为:
这 4 种引用强度依次逐渐减弱。除强引用外,其他 3 种引用均可以在 java.lang.ref 包中找到它们的身影。如下图,显示了这 3 种引用类型对应的类,开发人员可以在应用程序中直接使用它们。
Reference 子类中只有终结器引用是包内可见的,其他 3 种引用类型均为 public,可以在应用程序中直接使用。
强引用(StrongReference)
:最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似 "Object obj = new Object()
" 这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。软引用(SoftReference)
:在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存流出异常。弱引用(WeakReference)
:被弱引用关联的对象只能生存到下一次垃圾收集之前。当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象。虚引用(PhantomReference)
:一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。代码示例
/**
* 强引用的测试
*/
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);
}
}
Hello,阿里巴巴
强引用例子:
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之前,垃圾回收器会回收软引用的可达对象。
}
}
}
-Xms10m -Xmx10m
[id=1, name=songhk]
After GC:
[id=1, name=songhk]
null
java.lang.OutOfMemoryError: Java heap space
at cn.xx.java1.SoftReferenceTest.main(SoftReferenceTest.java:43)
代码示例
在 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。
get()
方法取得对象时,总是 null。虚引用无法获取到我们的数据
在 JDK 1.2 版之后提供了 PhantomReference 类来实现虚引用。
// 声明强引用
Object obj = new Object();
// 声明引用队列
ReferenceQueue phantomQueue = new ReferenceQueue();
// 声明虚引用(还需要传入引用队列)
PhantomReference<Object> sf = new PhantomReference<>(obj, phantomQueue);
obj = null;
代码示例
/**
* 虚引用的测试
*/
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();
}
}
}
null
调用当前类的finalize()方法
obj 可用
第 2 次 gc
追踪垃圾回收过程:PhantomReferenceTest实例被GC了
obj 是 null
finalize()
方法,将对象复活了,所以对象没有被回收,但是调用第二次 GC 操作的时候,因为 finalize()
方法只能执行一次,所以就触发了 GC 操作,将对象回收了,同时将会触发第二个操作就是将回收的值存放到引用队列中。finalize()
方法,也可以称为终结器引用。finalize()
方法,第二次 GC 时才回收被引用的对象。