首先需要澄清的是,垃圾收集(GC)的历史远比Java要久远,当我们意识到手动管理内存所带来的麻烦时,懒惰的天性推动先驱们寻找更为简单、易用、关键是傻瓜式的内存管理技术。GC技术起源于1960年诞生于MIT的Lisp语言,由此可见越聪明的人越懒惰。
最近有一种想法:程序开发,程序设计从本质上来讲是个哲学问题,虽然我们要解决的问题是现实在程序世界中的投射,那么问题本身就应该在现实世界中有相应的解决,而且我们也一直在模拟现实世界,由过程式,面向对象以及函数式编程以及我们下面所要看到的GC的设计思想可见。
因为不想根据运行环境的不同修改代码,我们希望有一个缓冲层能屏蔽底层硬件环境的不同,于是我们引入了JVM
因为有人工管理内存存在的不确定,JVM的设计者们引入了内存自动分配与垃圾收集
因为想要最大限度的利用内存,JVM内存被划分为线程内存(栈)与共享内存堆
为了能够更有效的分配及回收内存,堆被划分成了新生代与老年代,这样才能根据对象的不同采用更加具有效率的GC方案。
也是因为不同GC方案的需求,新生代又被划分为Eden区和survivor区
|---------- 新生代------------|------老年代------|
|Eden|survivor|survivor| old |
....
1. 程序计数器、虚拟机栈、本地方法栈三个区域随这线程生死,其内存分配都具有确认性,因此不必过多考虑,我们所说的内存分配与回收通常是指的堆
2. 我们怎么判断一个对象”死了“,也就是可以回收的
2.1 引用计数器方法(未使用)
最直观的方法,对象如果被引用,则引用计数+1,如果去引用,则引用计数-1,如果为0,则对象没有引用,可以被回收。但该算法无法解决循环引用的问题。JAVA虚拟机也没有采用该算法。
2.2 根搜索算法 GC Root Tracing(Java、C#、Lisp)
通过一系列的名称”GC Root“的对象作为起始点,从该节点向下搜索,走过的路径称为引用链,所有不在任意一条引用链上的对象(不可达)就可以判断为不可用。
该算法需要确定Root点,在JAVA中包括如下几种:虚拟机栈中本地变量表所引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈JNI中引用的对象
2.3 finalize
在finalize块中我们可以设置对象被重新引用,从而躲过GC,但不推荐在Finalize快中进行任何操作。因在finalize中的代码块会被放置在一个队列中执行,且不保证执行结果。
3. 引用
Java的引用可以分为强引用(Strong Reference)、软引用(SoftReference)、弱引用(WeakReference)、虚引用(PhantomReference)这四种,并且引用是由强到弱。
强引用是代码中普遍存在的,类似于Object obj = new Obeject()类的引用,其永远不会被回收
软引用用来描述一些还有用,但并非必须的对象,在发生内存溢出前,会对软引用的对象进行二次回收,仍无法分配内存时才会抛出异常。
弱引用用来描述非必须对象,被弱引用关联的对象智能生存到下一次GC之前
虚引用是最弱的引用关系,其不会对对象产生影响,其设置的唯一目的就是在对象被回收时收到一个系统通知。
4. 方法区的回收
方法区也是有垃圾收集的,只是效果通常不好。方法区(永久代)的垃圾收集主要包括两部分内容:废弃常量和无用的类。在大量使用反射、动态代理、GClib等bytecode的场景都需要虚拟机的卸载功能,以保证方法区不会溢出。