垃圾收集器和内存分配策略

Posted aaron-cell

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了垃圾收集器和内存分配策略相关的知识,希望对你有一定的参考价值。

1.概述

  为什么虚拟机要进行垃圾回收?

    因为Java虚拟机中的内存是有限的,在程序运行中无时无刻不在创建对象,消耗内存,如果不对内存进行回收,就无法解决内存不足的问题,自然程序无法运行持久。

  如今内存动态分配与内存回收技术相当成熟,为什么还要了解它?

    因为即使内存动态分配和内存回收技术在怎么成熟,也不能保证不会出现内存溢出的情况。当需要排查各种内存溢出、内存泄漏的问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,就要实施必要的监控和调节。

  垃圾收集(Garbage Collection,简称GC),垃圾收集的历史远比Java悠久,实现垃圾收集需要思考三个问题:

  .哪些内存需要回收?

  .什么时候进行垃圾回收?

  .如何进行垃圾回收?

  在针对Java内存运行时区域的各个部分进行垃圾回收,大体分为两种:

    第一种:程序计数器、java虚拟机栈、本地方法栈这三块区域都是伴随线程而生,随线程死亡而灭。栈中的栈帧随着方法的进入和退出而有条不絮的执行栈帧的入栈和出栈操作。每个栈帧分配多少内存在类结构确定下来时就已知。因此这几个区域内存分配和回收都具有确定性,当方法结束或者线程结束,内存自然就跟随着回收。

    第二种:Java堆和方法区,这两个区域有很明显的不确定性,比如:一个接口的多个实现类需要的内存会不一样,一个方法的不同条件分支所需要的内存也不尽相同,只有在运行期间,才能够知道具体要创建哪个对象,执行某个方法需要多少内存。这部分内存是动态分配的。垃圾收集器主要关注的正是这一部分的内存管理,下文中内存分配与回收也特指这一部分。

2.对象如何判定死亡

  在堆中存放了几乎所有的对象实例,垃圾收集器在堆回收之前首要任务就是判定哪些对象还“存活”着,哪些对象已经“死亡”,下面介绍几种判断对象是否存活的算法:

  引用计数算法:简单说就是在对象中加入一个计数器,当该对象被引用时,计数器加一;当引用失效时,计数器减一;当计数器为零时,则表示该对象没有被引用。虽然引用计数算法会消耗一些额外功能进行计数,不过确实效率高。但是主流的java虚拟机都没有选用引用计数算法,原因很简单,看似简单高效的算法,在Java虚拟机中不适用,需要配合大量额外的处理才能正确工作,比如:很难去解决对象之间的相互引用。

原由:如果两个对象相互引用,但是两个对象之外又没有被其他所引用。

  看以下代码:

 

/**
 * testGC()方法执行后,objA和objB会被GC
 * @author Aaron
 *
 */
public class ReferenceCountingGC {
	public Object instance=null;
	private static final int _1MB = 1024*1024;
	//bigSize作用就是为了占内存
	private byte[] bigSize = new byte[2*_1MB];
	public static void testGC() {
		//objA和objB所引用的对象相互引用,然后objA和objB不在指向这两个对象
		ReferenceCountingGC objA = new ReferenceCountingGC();
		ReferenceCountingGC objB = new ReferenceCountingGC();
		objA.instance = objB;
		objB.instance = objA;
		objA = null;
		objB = null;
		System.gc();//该方法提示虚拟机进行GC
	}
	public static void main(String[] args) {
		ReferenceCountingGC.testGC();
	}
}

 在Debug时设置日志打印路径

技术图片

 

 运行结果如下图:

技术图片

 

 上图中:[GC 7997(年轻代回收前总大小)——>672(年轻代垃圾回收后大小)——>249344(年轻代总大小) 0.0022.45 回收消耗时间],通过分析可以看出java虚拟机并没有放弃对(除了对象间相互引用在也没有被引用的)对象进行回收。

 

  可达性分析算法:目前主流的商用程序语言的内存管理系统,都是通过可达性分析算法来判断对象是否存活。基本思路:通过一系列称为“GC Roots”的根对象作为起始节点集,如果某个对象到GC Roots之间没有任何引用链连接,则表明GC Roots到对象是不可达的,证明此对象不能在被使用。如图:

技术图片

 

 

  在Java中有一下几种方式可以作为固定的GC Roots去判定到对象的可达性:

  1.java虚拟机栈(栈帧中的本地变量表)中引用对象,比如:调用方法区/堆/栈中使用的参数、局部变量、临时变量。

  2.方法区中类的静态属性引用的对象,比如:java类中引用类型静态变量。

  3.在方法区中常量引用的对象,比如:常量池中引用的。

  4.同理 本地方法栈中引用的对象。

  5.Java虚拟机中内部引用,比如:基本数据类型对应的class对象、常驻的异常对象、类加载器等。

  6.被同步锁(synchronized)持有的锁对象

  7.Java虚拟机内部情况的JM XBean、JVMTI中注册的回调(内部实现)、本地代码缓存等

除了以上这些固定的GC Roots集合外,还可能受垃圾收集器的种类、当前回收区域、以及其他对象临时加入,共同构成了GC RootS集合。(对象的引用可能存在内存跨区域的情况,所以GC Roots集合中也要考虑其他区域的存在)

3.再谈引用

  在JDK1.2之前,Java中的传统引用定义:如果reference类型中的数据中存储的数值代表的是另一块内存的起始地址,就称reference数据代表的是某块内存、某个对象的引用。但是传统的引用定义现在看来有些狭隘,一个对象只有“被引用”和“未引用”两种状态,对于那些“食之无味、弃之可惜”的对象不能够很好描述。比如:我们希望一些对象在内存足够时存放在内存中,当内存不足时,对这些对象进行回收。

  在JDK1.2之后,Java引入了四种引用概念,将引用分为强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。四种引用强度依次降低。

  .强引用 只要对象存在被引用关系,无论何时,只要被引用关系还在,就不会被垃圾收集器回收。

  .软引用 只被软引用关联的对象,系统将要发生内存溢出前,会把这些对象列入回收范围之中进行第二次回收,如果这次没有回收到足够多的内存,则下一次会对列入的对象进行回收。

  .弱引用 强度比软引用要低,只能存活到下一次垃圾回收。

  .虚引用 一个对象是否存在虚引用,不会对其生存的时间造成影响,也无法通过这个引用获取对象实例。唯一的目的就是在对象会后是时,引用者能够收到系统通知。

 

 

  

 

  

  

以上是关于垃圾收集器和内存分配策略的主要内容,如果未能解决你的问题,请参考以下文章

垃圾收集器和内存分配策略

垃圾收集器与内存分配策略

垃圾收集器与内存分配策略之内存分配与回收策略

垃圾收集器与内存分配策略之垃圾收集算法

垃圾收集器与内存分配策略

垃圾收集器与内存分配策略之篇一:简要概述和垃圾收集算法