第三章:垃圾收集器与内存分配策略(一)

3.1 概述

垃圾收集的历史要比Java久远,世界上第一个使用内存动态分配和垃圾收集技术的语言是1960年诞生于麻省理工学院的Lisp语言。当Lisp语言还处于胚胎时期时,其作者就已经思考过垃圾收集需要完成的三件事情:

  1. 哪些内存需要收集
  2. 什么时候收集
  3. 如何收集

当经过半个世纪的发展,内存的动态分配和垃圾收集技术已经相当成熟了,那我们为什么还要去学习这一部分知识呢?因为当需要排查各种内存溢出,内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术实施必要的监控和调节。
对于Java语言来说,我们已经知道Java内存运行时区域的各个部分,其中程序计数器,虚拟机栈,本地方法栈3各区域随着线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不紊的执行着进栈和出栈的操作,对于这几个区域来说,内存的分配和回收都有着确定性,就不需要过多的考虑如何回收的问题,当方法结束或者线程结束,内存自然也就跟着回收了。
但是对于Java堆和方法区来说,这两个区域有着显著的不确定性:一个接口的多个实现类需要的内存是不一样的,一个方法所执行的不同条件分支所需要的内存也是不一样的,只有处于运行期间,我们才能够知道程序究竟会创建那些对象,创建了多少对象,所以这部分的内存的分配和回收是动态的。这就正是垃圾收集器所关注的部分。

小结:垃圾收集器主要是工作在Java堆,和方法区中。因为这两部分的内存分配和回收是动态的

3.2 判断对象是否已死(在Java堆中)

在Java堆中存储了几乎所有的对象实例,那么垃圾收集器在进行回收之前,就必须要先判断哪些对象不可回收(存活),哪些对象是可回收的(死亡)

3.2.1 引用计数法(教科书方法)

在对象中添加一个引用计数器,每当有一个地方引用它时,引用计数器就加一;当引用失效时,计数器值就减一;任何时刻计数器的值为零时,该对象就不可能再被使用。
引用计数法简单且高效,但是在Java领域中,至少在主流的Java虚拟机中都没有使用这种方法来管理内存,主要原因是:这个看似简单的算法有很多的例外情况,必须要配合大量额外处理才能够正确地工作,譬如引用计数器就很难处理对象之间相互引用的问题。
比如:对象objA和对象objB都有一个字段instance,赋值令objA.instance = objB;objB.instance = objA;除此之外,这两个对象没有其他任何的引用,实际上,这两个对象已经不可能在被访问了,但是因为它们相互引用着对方,导致它们的引用计数器都不为零,引用计数器就无法收集它们。

3.2.2 可达性分析算法

当前的主流商用程序语言,如Java,C#等的内存管理子系统,都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活。
基本思想是:通过一系列称为“GC Roots”的根对象作为起始节点集,然后从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链(Reference Chain)”,如果某个对象到GC Roots之间没有任何的引用链相连,或者使用图论来说就是从GC Roots到这个对象不可达时,说明此对象是不可能再被使用的。
如下图:
GC Roots
上图中,虽然紫色的object1,object2,object3之间相互联系,但是它们到GC Roots是不可达的,因此它们将会判定成可回收的对象。

在Java体系中,可以被认为是 GC Roots的对象包括以下几种:

  1. 在虚拟机栈(栈帧中的本地变量表)中的引用对象,譬如当前正在运行的方法所使用的参数、局部变量、临时变量等。
  2. 在方法区中,类静态属性引用的对象,譬如Java类的引用类型静态变量
  3. 在方法区中常量引用的对象,譬如字符串常量池中的引用。
  4. 在本地方法栈中JNI(即通常所说的Native方法)引用对象。
  5. Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象等,还有系统类加载器。
  6. 所有被同步锁(synchronized关键字)所持有的对象
  7. 反映java虚拟机内部情况的JMXBean,JVMTI中注册的回调,本地代码缓存等。

除了上面的7中固定的GC Roots集合之外,根据用户所选用的垃圾收集器以及当前的内存区域不同,还可能会有其他对象“临时性”的加入,共同构成完整的GC Roots集合。

小结:判断对象是否存活有两种方法,引用计数法和可达性分析算法。其中引用计数法在实际的虚拟机中几乎没有用到,基本都是采用可达性分析算法

3.2.3 生存还是死亡?

无论是使用可达性分析算法将对象判断成为不可达对象,还是使用引用计数法判断没有引用的对象,其实这是的对象并非“非死不可”,这时候对象还处于“缓刑”阶段,要真正的宣布一个对象死亡,要经历两次标记过程:如果对象在进行可达性分析算法之后分析发现没有与GC Roots相连接的引用链,那么它将会被进行第一次标记。之后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。如果对象没有覆盖finalize()方法,或者虚拟机已经调用过该方法,那么虚拟机将这两种情况都视为“没有必要执行”,那么该对象真正死亡。
那么当对象有finalize()方法,没有执行,并且被判定为有必要执行finalize()方法,,这时该对象就会被放置在F-Queue队列之中。并在之后会被一条由虚拟机自动建立的,低调度优先级的Finalizer线程区执行它们的finalize()方法。并且注意,虚拟机负责执行,但是保证一定能够等待它运行结束。
对象的finalize()方法是对象逃离死亡的最后一次机会,稍后,收集器将会对F-Queue中的对象进行一次小规模的标记,如果在第二次标记之前,对象通过finalize()方法完成自救,那么在第二次标记的时候,它就会被移出“即将回收”的集合。如果这时候还没有逃脱,那么这个对象就真的要被回收了。
值得注意的是,任何一个对象的finalize()方法都只会被系统自动调用一次,如果下次还面临着被回收,那么该对象的finalize()方法就不会在被执行了。

小结:对象是否死亡被回收,要经过两次的标记,第一次是使用可达性分析算法,然后在进行第二次标记之前,对于被判定有执行finalize()方法价值的对象,会进入一个队列之中,然后由一个虚拟机自动建立,低优先级的Finalizer线程负责执行,那么在第二次标记的时候,执行过该方法的对象就会被移出“即将回收”的集合。finalize()方法只能够执行一次。

3.2.4 引用

在JDK 1.2版之前,Java里面传统的引用定义:如果reference类型的数据中存储的数值代表的是令一块内存的起始地址,就称该reference数据是代表某块内存,某个对象的引用。
在JDK1.2之后对引用的概念进行了扩充。,将引用分为强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,这4种引用强度逐渐减弱。

  • 强引用(Strongly Reference): 就是最传统的引用的定义,是指程序代码之间普遍存在的引用赋值,即类似的“Object obj = new Object()”这种引用关系。无论在什么情况之下,只要强引用谷氨醯还存在,垃圾收集器就永远不能够回收掉被引用的对象。
  • 软引用(Soft Reference): 是用来描述一些还有用,但非必须的对象。那些只被软引用关联着的对象,在系统将要发生内存溢出异常之前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  • 弱引用(Weak Reference): 也是用来描述那些非必要的对象,但是它的强度比软引用要弱一些,被弱引用关联着的对象只能够生存到下一次垃圾收发生为止。当垃圾收集器开始工作,无论当前的内存是否足够,都会回收掉之内弱引用关联着的对象。
  • 虚拟用(Phantom Reference): 也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全被不会对其生存时间构成影响,也无法通过虚引用来取得是否有虚引用的存在。那么为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被回收的时候收到一条系统的通知。

3.2.5 回收方法区

对于方法区是否有垃圾回收行为,《Java虚拟机规范》中是没有明确定义的,所以有的虚拟机中有,但有的虚拟机中没有实现。
方法区的垃圾回收主要有两部分内容:废弃的常量和不在使用的类型。
回收废弃常量与回收Java堆中的对象非常相似,假设一个字符串之前进入到常量池中,但是当前系统没有任何一个字符串对象引用这个字符串常量,且虚拟机中也没有其他地方引用这个字面量,那么这时如果有垃圾回收行为,该字符串就会被清楚出常量池。类似的,常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
判断一个类型是否属于“不在使用的类”的条件比较苛刻,需要同时满足以下三个条件:

  1. 该类所有的实例都已经被回收,即java堆中不应该在存在该类以及任何派生子类的实例。
  2. 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如JSP的重加载等,否则很难达成。
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
-Xnoclassgc	//判断是否要对类型进行回收
//以下三个指令可以以用来查看加载和卸载信息
-verbose:class 
-XX:+TraceClassLoading
-XX:+TraceClassUnLoading
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值