垃圾回收相关概念
# 1. System.gc()
在默认情况下,通过System.gc()
或者 Runtime. getRuntime().gc()
的调用,会显式触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。
然而System.gc()
调用附带一个免责声明,无法保证对垃圾收集器的调用
只是提醒JVM进行GC,不保证JVM一定会GC。类似于线程的优先级
System.gc()不一定会立即进行垃圾回收:
public class SystemCGTest {
public static void main(String[] args) {
new SystemCGTest();
//提醒JVM的垃级回收器执行gc,但是不确定是否马上执行gc
System.gc();
}
@Override
protected void finalize() {
System.out.println("重写finalize方法,对象自我拯救...");
}
}
2
3
4
5
6
7
8
9
10
11
12
不同的结果出现:
gc()
的底层实现是
Runtime.getRuntime().gc();
System.runFinalization会强制立即进行垃圾回收:
public class SystemCGTest {
public static void main(String[] args) {
new SystemCGTest();
//提醒JVM的垃级回收器执行gc,但是不确定是否马上执行gc
System.gc();
//调用finalize(),强制GC立即进行垃圾回收
System.runFinalization();
}
@Override
protected void finalize() {
System.out.println("重写finalize方法,对象自我拯救...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
只会出现:重写finalize方法,对象自我拯救...这一种结果
runFinalization()
会强制调用引用的对象的finalize()
方法,而只在回收垃圾对象时GC才会调用finalize()
,所以推导得出runFinalization()
会使JVM立即进行垃圾回收
JVM实现者可以通过System.gc()
调用来决定JVM的GC行为。而一般情况下,垃圾回收应该是自动进行的,无须手动触发。
在些特殊情况下,如我们正在编写一个性能基准测试,保证起始点和空间够用,可以在运行之间调用System. gc()
# 2. 内存溢出
javadoc中对OutofMemoryError
的解释是,没有空闲内存,并且垃圾收集器也无法提供更多内存。
1. 没有空闲内存
造成没有空闲内存的原因有二:
堆内存设置不够
-Xms
、-Xmx
来调整代码中创建了大对象,长时间存在引用而不能被垃圾回收器回收(内存泄漏)
永久代:java.lang.OutOfMemoryError:PermGen space
元空间:java.lang.OutOfMemoryError:Metaspace
2. 无法提供更多内存
意思就是GC回收了垃圾对象之后,仍然没有足够的内存空间来存储。GC完之后,才报OOM
【OOM举例】
# OOM调试
【参数设置】
-Xms8m
(设置堆空间)-Xmx8m
-XX:+HeapDumpOnOutOfMemoryError
(OOM时导出Dump文件,进行分析)
/**
* @Author: Mr.Q
* @Date: 2020-08-05 11:58
* @Description:堆空间OOM测试,dump快照分析
*
* -Xms8m
* -Xmx8m
* -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOM {
byte[] bufffer = new byte[1 * 1024 * 1024]; //1MB
public static void main(String[] args) {
ArrayList<HeapOOM> list = new ArrayList<HeapOOM> ();
int count = 0;
try {
while(true) {
list.add(new HeapOOM());
count++;
}
} catch (Throwable t) {
System.out.println("count = " + count);
t.printStackTrace();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
生成Dump文件,查看内存情况
# 3. 内存泄漏
内存泄漏Memory Leak:对象不再使用了,但是GC又无法将其回收
尽管内存泄漏并不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中的可用内存就会被逐步蚕食,直至耗尽所有内存,最终出现 OutofMemory
异常,导致程序崩溃。
内存泄漏可能导致内存溢出,并不绝对。
注意,这里的存储空间并不是指物理内存,而是指JVM虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。
【Memory Leak举例】
单例模式。单例的生命周期和类是一样长的,因为是
static
类型。所以单例程序中,如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内存泄漏的产生。一些提供close的资源未关闭,导致内存泄漏。数据库连接、网络连接Socket和 IO 连接必须手动close,否则是不能被回收的。
# 4. 常用的JVM参数
-Xss2M
:设置JVM栈内存大小
-Xms20M
:设置堆内存初始值
-Xmx20M
:设置堆内存最大值
-Xmn10M
:设置堆内存中新生代大小
-XX:SurvivorRatio=8
:设置堆内存中新生代Eden 和 Survivor 比例
-XX:PermSize=10M
:设置方法区内存初始值
-XX:MaxPermSize=10M
:设置方法区内存最大值
-XX:MaxDirectMemorySize=10M
:设置堆内存中新生代大小
# 5. 再谈引用
【面试题】:强引用、软引用、弱引用、虚引用有什么区别?具体使场景是什么?
我们希望能描述这样一类对象:
当内存空间还足够时,则能保留在内存中;
如果内存空间在进行垃圾收集后还是很紧张,则可以抛弃这些对象。
在JDK1.2版之后,Java对引用的概念进行了扩充,将引用分为
强引用( Strong Reference)
软引用( Soft Reference)
弱引用( Weak Reference)
虚引用( Phantom Reference)
4种,这种引用强度依次逐渐减弱
强引用( StrongReference):传统定义的“引用”,即Object obj = new Object()
这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被强引用的对象。
软引用( SoftReference):在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。如果回收后还没有足够的内存,才会抛出内存溢出异常。没有足够的内存空间才会回收
弱引用( WeakReference):被弱引用关联的对象只能生存到下一次垃圾收集之前。当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象。
虚引用( PhantomReference):一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例。为一个对象设置虚引用关联的唯一目的就是——能在这个对象被收集器回收时收到一个系统通知,进行对象回收跟踪。