各语言内存操作对比:
语言 |
申请内存 |
释放内存 |
C |
malloc |
free |
C++ |
new |
delete |
Java |
new |
自动释放 |
Java语言的自动内存管理设计最终可以归结为自动化地解决了两个问题:
- 给对象分配内存,可查看JVM内存模型、Java对象创建等文章;
- 回收分配给对象的内存。
即对象内存的分配和回收。
了解JVM是垃圾回收机制,如何有效防止内存泄露、保证内存的有效使用,需要思考三个方向的问题:
- 什么对象的内存需要回收?(什么是垃圾?怎么判断对象是垃圾?)
- 用什么方式回收?(垃圾回收的算法?)
- 有什么垃圾收集器?
一、对象回收判断
进行垃圾回收的第一步:什么是垃圾? 没有引用指向的一个对象或多个对象(循环引用)。
定位垃圾的方法有两种:引用计数法和可达性分析。
1、对像的引用
Java中将数据类型分为两大类:基本类型和引用类型。如果reference类型数据中存储的数值为另一个块内存的起始地址,就称这块内存代表一个引用。在JDK1.2开始对引用的概念进行了扩充,分为强、软、弱、虚四种引用,且强度依次逐渐降低。详细见Java-四种引用类型。
Person p = new Person();
// 等号后面的 new Person(); 是真正的实例对象,其内容存储在Java堆内存中
// 等号前面的 p 只是一个引用标识符,保存在虚拟机栈内存中,它存储的只是一个地址,是 new Person() 在堆内存中的起始位置,因此 p 就是一个引用。
// 等号是一种强引用方式
2、引用计数法(Reference Count已淘汰 )
引用计数法是垃圾收集器中的早期策略,通过判断对象的引用数量来决定对象是否可以被回收。该方法为堆中每个对象添加一个计数器,有地方引用了此对象则该对象的计数器加1,如果引用失效了则计数器减1,引用计数为0的对象实例则可以被当作垃圾收集。
优点:实现简单,判定效率也很高;
缺点:很难解决对象之间循环引用的问题;互相引用导致他们的引用计数都不为0,最终不能回收他们。
3、根可达性分析算法(Root Searching)
通过判断对象的引用链是否可达GCRoot来决定对象是否可以被回收。从离散数学中的图论引入,把所有的引用关系当作一张关系图谱,从被称为"GCRoot"的对象作为起始点,沿着引用链(Reference Chain,即引用路径)向下搜索,如果一个对象没有任何引用链连接到GCRoot节点,则证明此对象是不可用的/不可达,则此对象可被回收。
JAVA中可以作为GCRoot对象/根对象包括以下几种:
- 虚拟机栈(栈帧中的局部变量表)中引用的对象;
- 方法区中类静态变量引用的对象;
- 方法区中运行常量池引用的对象;
- 本地方法栈中Native方法引用的对象;
如图,引用链连接上GCroot的Object1、2、3、4都是被使用的对象,但是Object5、6、7却不能通过任何方式连接上根节点,因此判定Object5、6、7为可回收的节点。
4、对象死亡和自我拯救
可达性分析之后,不可达的对象一定会被垃圾收集器回收吗?不一定。
在可达性分析后发现不可达的对象会被进行一次标记,然后进行筛选,筛选的条件是判断该对象有没有必要执行finalize()方法;
- 回收情况:如果对象没有重写finalize()方法或者对象的finalize方法已经被虚拟机调用过一次了,则都将视为"没有必要执行",垃圾回收器直接回收对象。
- 自救情况:如果对有重写finalize()方法且对象的finalize方法没有被虚拟机调用过,则将该对象判定"有必要执行",那么虚拟机会把这个对象放置在一个F-Queue的队列中,然后由一个专门的Finalizer线程去执行这个对象的finalize()方法;在这个方法中可以进行对象的"自我拯救",即重新与引用链上的任何一个对象建立关联就可以了,如把this赋值给某个类的变量、或者对象的成员变量;但在又被第二次GC标记时它将被移除"即将回收"的集合。
1、对象无finalize方法:直接去除引用链,一次GC时便直接回收实例对象。
// JVM执行参数 -Xmx20m -XX:+PrintGCDetails
public class FinalizeTest {
static class M {
// 10M大小
private byte[] bytes = new byte[10 * 1024 * 1024];
}
public static void main(String[] args) throws Exception {
M m = new M();
m = null;
// new M()被去掉引用,且M中没有自救方法finalize,所以在GC时会直接回收
System.gc();
// GC相关线程优先级低,主线程等待一下
Thread.sleep(1000);
}
}
// 日志
/* 年轻代PSYoungGen的总内存大小为6144k=6M,所以10M的new M()对象会直接被分配在老年代ParOldGen*/
[GC (System.gc()) [PSYoungGen: 1749K->480K(6144K)] 11989K->10765K(19968K), 0.0018203 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
// FGC情况下,ParOldGen老年代从10285K->471K
[Full GC (System.gc()) [PSYoungGen: 480K->0K(6144K)] [ParOldGen: 10285K->471K(13824K)] 10765K->471K(19968K), [Metaspace: 3087K->3087K(1056768K)], 0.0046541 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
/* 可知new M()有10M大小,从整个堆上各代的使用内存情况,可知new M()已经不在堆上面,也可以通过jmap来查看实例对象存活*/
/* 后面的三个内存地址值指的是:起始地址,已使用空间的结束地址,对应区整个内存空间的结束地址 */
Heap
PSYoungGen total 6144K, used 1255K [0x00000007bf980000, 0x00000007c0000000, 0x00000007c0000000)
eden space 5632K, 22% used [0x00000007bf980000,0x00000007bfab9f10,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 13824K, used 471K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000)
object space 13824K, 3% used [0x00000007bec00000,0x00000007bec75e70,0x00000007bf980000)
Metaspace used 3613K, capacity 4536K, committed 4864K, reserved 1056768K
class space used 399K, capacity 428K, committed 512K, reserved 1048576K
Process finished with exit code 0
2、对象有finalize方法,但不进行自我拯救:直接去除引用链,第一次GC时对象放入F-Queue的队列且进行finalize方法的拯救,不会被回收,但finalize中并未拯救,所以第二次GC时对象会被回收。
public class FinalizeTest {
static class M {
// 10M大小
private byte[] bytes = new byte[10 * 1024 * 1024];
@Override
protected void finalize() {
System.out.println("m的finalize执行了,但并不拯救");
}
}
public static void main(String[] args) throws Exception {
M m = new M();
m = null;
System.out.println("第一次GC-----------");
// 因为M的存在finalize,所以被放入F-Queue的队列中,拯救线程执行finalize方法,所以10M的new M()对象本次不被回收
System.gc();
// GC相关线程优先级低,主线程等待一下
Thread.sleep(1000);
// 第二次GC,
System.out.println("第二次GC-----------");
// 因为M的finalize已经执行了,但并未拯救,所以10M的new M()对象被回收了
System.gc();
Thread.sleep(1000);
}
}
// 日志
第一次GC-----------
[GC (System.gc()) [PSYoungGen: 1862K->512K(6144K)] 12102K->10797K(19968K), 0.0022455 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
/* 第一次FGC情况下,ParOldGen老年代从10285K->10712K,说明并未回收10M的new M()对象*/
[Full GC (System.gc()) [PSYoungGen: 512K->0K(6144K)] [ParOldGen: 10285K->10712K(13824K)] 10797K->10712K(19968K), [Metaspace: 3101K->3101K(1056768K)], 0.0040101 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
m的finalize执行了,