概述

本文就内存的分代设计理论和不同的虚拟机的区别进行总结和学习,因为后续要想对jvm进行调优我们就必须要了解jvm的内存设计和回收的算法还有每款虚拟机的特点等等。

堆内存的回收

堆内存的回收相比于那些虚拟机栈、本地方法栈、程序计数器这类线程私有,随着线程创建而生存然后随着线程的销毁而灭亡的内存回收要复杂的多,因为堆内存和方法区都有着很不确定性,接口会有不同的实现类,方法的执行还会存在不同的分支需要不同的内存空间,只有在程序运行期间我们才知道要创建哪些对象,这些内存的都是动态的,垃圾收集器重点关注的也是这部分动态内存的回收,怎么确定哪些对象是存活的呢?

  1. 引用计数法
    1. 通过一个计数器来统计对象被引用的次数,被引用加1,引用失效减1 直到对象的引用为0说明对象没有引用的地方那么这个对象就可以被回收掉了。但是存在一个循环引用的问题,有两个对象A、B互相引用除此之外再无其他引用,实际这两个对象已经没有什么用了,因为互相引用计数器不能清零,所以引用计数算法无法把他们回收掉,现在主流的虚拟机都不是基于此去寻找哪些对象是存活的哪些是要回收的。
  2. 可达性分析算法

    1. 现在商用的java语言的垃圾回收系统都是基于可达性分析算法来判断哪些对象是存活的哪些是要回收的,这个算法的基本思路是以一系列被称为 GC Roots的根节点集合为起始点根据引用关系向下搜索,搜索的路径称之为引用链,如果某个对象没有到这个引用链上的引用那么我们就称之为对象不可达即要被垃圾回收器回收的对象。
    2. GC Roots根节点集合包含的一下几种对象
      1. 在虚拟机栈中引用的对象,各个线程被调用的方法堆栈中所使用到的参数、局部变量、临时变量等。
      2. 方法区中静态属性所引用到的对象,比如java类的引用类型静态变量
      3. 方法区中常量引用的对象,比如字符串常量池中的引用
      4. 本地方法栈中所引用的对象
      5. java虚拟机内部所引用的对象,比如基本类型所对应的class对象、异常对象、类加载器等。
      6. 被线程同步锁持有的对象
    3. 堆内存中的对象不是孤立封闭的,对象完全有可能被堆内存中的其他区域所引用,这样在分析可达性的时候需要一并加入到扫描中才能保证分析的正确性。只要涉及到局部回收肯定会涉及到这样的问题。

      对象的引用

      无论是上述哪种算法最终都离不开对象的引用,对象的引用共分为四类:
  3. 强引用

  4. 弱引用
  5. 软引用
  6. 虚引用

    方法区的内存回收

    方法区的回收性价比相比较新生代中对象的回收要低很多,因为新生代中的对象大多数是朝生夕灭的一次性就能回收个百分之七八十左右,由于方法区这部分回收条件严苛所以性价比会比较低。
    这部分涉及到两部分内容的回收:

  7. 常量的回收

    1. 比如常量池中字符串常量”java” 已经没有任何其他字符串对象进行引用,包括虚拟机内部的引用,在回收时如果虚拟机认为有必要这个常量就会被回收掉。
  8. 类型的卸载

    1. 所以实例被回收,包括派生的子类实例
    2. 加载该类的类加载器被回收
    3. 该类对应的class对象没有任何地方引用,也无法通过反射访问该类。

      分代收集理论三种设计理论原则

      当前虚拟机大多都基于此三种设计理论进行设计,其实就是符合大多数程序实际运行情况的经验。
  9. 弱分代假说

    1. 绝大多数对象都是朝生夕灭的。
  10. 强分代假说
    1. 熬过回收的次数越多的对象越难以消亡
  11. 跨代引用假说
    1. 跨代引用相对于同代引用来说仅仅占极少数

      总结

      分代理论是根据对象的年龄(也就是熬过垃圾回收的次数)划分为不同的区域,能够对对象进行更好的管理,朝生夕灭的对象我们就将他们放在一起我们只需要关注少量存活的对象,这样我们就能以较低的代价获得较大的内存空间,把难以消亡的对象放在一起,我们以一个较低的频率进行回收这样就兼顾了垃圾收集的时间开销和内存空间的有效利用。