Java堆与方法区有着显著的不确定性,一个接口的多个实现类需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有处于运行期间,我们才能知道究竟会创建那些对象,创建多少个对象,这部分的分配与回收是动态的,垃圾回收关注的就是这部分的内存该如何管理。
垃圾收集
如何判断对象以‘死’?
- 引用计数法
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器的值加一;当引用失效时,计数器的值减一;计数器为0的对象就是可以被回收的。
引用计数法虽然占用了一些额外的内存空间来进行计数,但他的原理简单,判定效率高效,大多数情况下是一个不错的算法,然而对于例外的情况就需要配合大量的额外处理才能保证正确的工作,例如:对象之间相互循环依赖的问题。
- 可达性分析算法
通过一系列称为 “GC ROOTS” 的根对象作为起始节点集,从这些节点开始,根据引用关系进行向下搜索,搜索的过程所走过的路径称为 “引用链” ,如果某个对象到 “GC ROOTS” 间没有任何应用链相连,则证明对象可以被回收。
object5、object6,object7虽然有关联,但是到GC ROOTS是不可达的,因此会将他们判定为可回收的对象。
在JAVA技术体系里,固定可以作为GC ROOTS的对象包括。
- 在Java栈中(局部变量表)中引用的对象。
- 在方法区中类静态属性引用的对象。
- 在方法区中常量引用的对象,譬如:字符串常量池里的引用。
- 在本地方法栈中JNI(一般指native调用的方法)引用的方法。
- Java虚拟机内部的引用,如:基本数据类型对应的class对象。
- 所有被同步锁(synchronized关键字)持有的对象。
- ······(等等)
谈谈引用
早期的定义:如果引用(reference)类型的数据中存储的数值代表的是另一块内存的起始地址,就称该reference数据是代表某块内存、某个对象的引用。
在JDK1.2之后,Java对引用的概念进行了扩充,分为四种,强度依次减弱。
- 强引用: 最传统的引用的定义,默认生成的即为强引用,如:
Object object=new Object()
,任何情况下,只要强引用的关系还在,被引用的对象就不会被回收。 - 软引用: 描述一些还有用但非必须的对象。被软引用关联着的对象,在系统将要发生内存溢出前,会对这些对象进行回收,回收后还是没有足够的内存才会抛出内存溢出的异常。使用SoftReference类来实现软引用。
- 弱引用: 用来描述那些非必须的对象,用弱引用关联的对象只能生存到下次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否充足,都会回收掉被弱引用关联的对象。使用WeakReference类来实现弱引用。
- 虚引用: 一个对象是否有虚引用的存在完全不会对其生存时间构成影响,也无法通过一个虚引用取得一个对象实例,为一个对象设置虚引用的唯一目的就是为了能在这个对象被回收时,收到一个系统通知。使用PhantomReference类来实现虚引用。
垃圾收集的算法
分代收集理论
分代收集理论:实质是一套符合大多数程序运行实际情况的经验法则,建立在三个分代假说之上。
- 弱分代假说:绝大多数对象就是朝生夕灭的。
- 强分代假说:熬过越多次垃圾收集过程的对象就越难以消灭。
- 跨代引用假说:新生代的对象有可能被老年代所引用(仅占极少数)
一二两条假说共同奠定了多款常用垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将对象依据其熬过的垃圾收集的次数分配到不同的区域进行存储。对Java堆划分出不同的区域后,垃圾收集器才可以每次只回收其中某一个或者某些部分的区域,才能够针对不同的区域安排与里面存储对象存亡特征相匹配的垃圾回收算法。
设计者一般至少将Java堆划分成两个对象区域:新生代、老年代。新生代存活一定次数的对象将放置到老年代。
不同分代的收集名次:
- 部分收集(Partial GC):指不是完整收集整个Java堆的垃圾收集。有分为:
- 新生代收集(Minor GC/Young GC):只是新生代的垃圾收集。
- 老年代收集(Major GC/Old GC):只是老年代的垃圾收集。
- 混合收集(Mixed GC):跨代引用是使用。
- 整堆收集(Full GC):对整个Java堆和方法区进行垃圾收集。
标记—清除法
算法分为标记和清除两部分,首先标记出所有需要回收的对象,标记完成后统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
缺点:
- 执行效率不稳定: 如果Java堆中包含大量对象,而且大部分需要被回收,就必须进行大量标记和清除动作,导致标记和清除两个过程的执行效率都随对象数量的增长而降低。
- 内存空间的碎片化: 产生大量不连续的内存碎片。
标记—复制算法
将内存按容量划分为内存大小相等的两块,每次只使用其中的一块,当一块内存使用完了,就将还存活的对象复制到另一块上面,然后把已经使用过的内存空间一次清理掉。
优点: 实现简单、执行效率高
缺点: 产生大量内存复制的开销,空间浪费,可用内存减少为原来的一半。
标记—整理算法
让所有存活对象都想一端移动,然后之间清理掉边界以外的对象。
缺点: 移动存活对象,并更新所有引用这些对象的地方是一个极其负重的操作,而且对象的移动必须全程暂停用户引用程序才能进行。