《深入理解Java虚拟机-第三版-周志明》-垃圾处理器

垃圾处理器

引言:
我们已经知道jvm管理的运行时数据内存大致分为:程序计数器、方法区、虚拟机栈、本地方方法栈、堆。其中的程序计数器、虚拟机栈、本地方法栈与线程同生同灭,当一个执行之后,虚拟机栈产生一个栈帧,程序计数器开始记录字节码执行位置,这些都是线程私有的。他们的内存管理,随着线程的创建而分配,随着线程的销毁而释放。
而作为共享区的方法区和堆,是大量实例化候对象,以及记录类信息的相关内存区域。由于实例话对象只有在运行时才知道创建的对象个数,所以这部分内存的分配、回收也是动态的。而方法区类的回收也需要算法进行判断。


1.概述

通常垃圾收集被认为是java发展的产物,但其实1960就有人研究过关于垃圾收集问题,大致可以分为三个部分

  • 哪些内存需要回收
  • 什么时候收集
  • 怎么回收

2.那些内存需要回收

哪些内存需要回收?当然是不在需要的对象需要回收。那么,如何判断一个对象不再需要呢?

2.1引用计数法

简单说就是在对象中记录该对象当前被引用的次数,被引用一次就加一,反之减一。引用计数法在java虚拟机判断对象存活中很少使用,缺陷之一就是无法解决对象之间循环引用的问题。

2.2可达性分析

可达性分析是大多数虚拟机采用的方法。所有从GC Root出发不能到达的对象,判定为已死对象,需要回收。


《深入理解Java虚拟机-第三版-周志明》-垃圾处理器
很多对象都可以成为GCRoot,比如,同步锁是有的对象、静态变量、局部变量、参数等等。

2.3已死对象就一定会被回收?

被判定为已死的对象也不会立即被回收,真正被回收至少需要两次标记过程,可达性分析判定不可达时标记一次,随后进行筛选,如果对象没有重写finalize()方法或已经被虚拟机执行过finalize()方法,那么该对象才真正宣告死亡。
所以说finalize()方法中,不可达对象依然可以获救。
如果说,在finalize方法中,对象被其他GC Root对象引用,那么就不会被清除

2.4方法区能不能回收

虽然说堆中进行垃圾回收的效益很客观,大概能回收70%~99%的内存,而方法去进行垃圾回收往往回收条件苛刻,而且效果不大。但是对于大量使用类加载器的框架,方法区需要得到重视。
方法区的回收对象主要是,常量池中废弃的常量、不再使用的类。

3.垃圾回收算法

分代理论:垃圾回收算法主要有以下三个理论支持,也是大量实验研究的结果经验
弱分代理论:大部分对象都是朝生夕死。
强分代理论:熬过垃圾回收的次数越多的对象越难回收。
跨代回收理论:对象关联大部分只发生在同代之间。
由此,使用分代理论的jvm把堆至少分为新生代和老年代两个部分。

垃圾回收算法,主要分为:标记-清除法,标记-整理法,标记-复制法。

3.1标记清除法

顾名思义,就是对不可达的对象/可达对象进行标记,随后清除/保留。
但是方法由于清理对象的随机性,会产生或大或小的内存间隙,无法获取到足够的连续内存

3.2标记复制法

1989年,针对新生代朝生夕死的特点,Andrew Appel提出了半代复制的优化,把内存划分为占80%的Eden和两块10%的Survivor。每次收集时,只使用一块大的和一块小的,并把存活的对象复制到另一块小的当中。

针对存活对象占比多于10%,也就是小的Survivor存不下的情况,Aplel也有响应的逃生门,也就是将多余的存活对象分配到堆中。

3.3标记整理法

针对老年代大多数存活的特点,标记整理法先对已死对象进行标记,然后任然存活的对象进行移动,移动到一侧。然后将边界之外的内存进行清空。可以说标记整理发和标记清除法的前半部分是一样的,但是标记真理在后半部分会进行移动。如图
《深入理解Java虚拟机-第三版-周志明》-垃圾处理器
大量的移动存活对象也是巨大的消耗,而且在移动过程中需要更新其他引用。
但是如果不移动,就会产生内存碎片化问题,需要依靠内存分配器和访问器来解决。内存访问是用户最频繁的操作,这样对本就负担过重内存操作加重。
基于以上,不同考虑的收集器会使用不同的算法。CMS收集器就是基于标记清除算法的,而Parallel Scavenge收集器就是基于标记整理法的。
另外,作者还提到了一种混合式的方法,就是在碎片化程度低的时候,使用标记清除法,而在碎片化程度不能忍受的时候使用标记整理法。