GC在回收内存时 :
在几个线程私有的运行时区域:
它们的内存分配和回收 大多 都具有确定性,随着线程的创建而产生,随着线程的停止而被回收。栈帧中的内存大小基本在类的结构确定下来时就已知。
而在线程共有的 Java堆(Heap)
和 方法区(Class(Method) Area)
这两个区域则不同:
比如,一个接口有不同的实现类(类的信息在方法区中),这几个实现类的内存大小肯定不一,没法在运行前就已知需要多大的内存,只有在运行期间才知道创建的对象的大小。
在知道哪些内存需要回收之前,我们要知道怎么判断一个对象是否还存活,当它不再存活时,就回收它。
而 引用计数算法
就是用来判断对象是否存活的一个算法。
算法描述:给对象添加一个引用计数器,当有一个地方引用了它,计数器+1,当引用失效,计数器-1,在任何时刻,计数器为0时此对象将不能再被使用。
引用计数法在大多数情况下表现都不错,也有被很多公司采用的应用案例。但是在JVM中并没有采用这种算法,原因是: 无法解决对象之间存在相互引用的问题 。
public class Person { Object instance = null; public static void main(String[] args) { Person a = new Person(); Person b = new Person(); a.instance = b; b.instance = a; a = null; b = null;// 正常情况下在这里GC就会把a,b回收掉 } } 复制代码
正常情况下在执行11-12行代码时,JVM的GC会把a,b两个对象回收,但是在引用计数算法的情况下:
a=null b=null
在Java语言中是通过可达性分析来判断对象是否存活。
算法描述 : 通过一系列的 GC Roots
作为起始点,从这些起始点开始向下搜索,能搜索的到的对象说明其可用,不会被GC回收掉,搜索所走过的路径称为 引用链(Reference Chain)
。相反,如果一个对象没有到达GC Roots的路径,则说明它不可用,被判定为可被GC回收的对象。
如图 : 1区域的对象虽然互相关联,但是它们不可到达GC Roots,所以他们会被回收掉,而2区域的对象与GC Roots之间是有可到达路径的,所以它们不会被回收。
这些都可作为GC Roots.
我们在上面的 引用计数算法
和 可达性分析
中,都提到了 对象之间的 引用
关系。
在Java1.2之前,关于 引用
的定义 :
如果 reference
类型的数据存储的数值代表的是另一块内存的起始地址,就说这块内存代表一个引用。
JDK1,2之后,又引入了 强引用
, 软引用
, 弱引用
, 虚引用
,这四个概念,并且这四种表现的引用关系越来越弱。
例:
Object o = new Object(); 复制代码
只要强引用还在,GC永远不会回收掉被引用的对象。
有用,但非必须,在将要发生内存溢出时,会把 软引用
的对象回收掉,如果内存依然不够用,则抛出OOM异常。
非必需对象,只要GC发生了垃圾回收,不管此时内存是否充足, 弱引用
的对象都会被回收掉。
一个对象仅仅通过上面说的可达性分析看它没有与GC ROOTS关联来判定这个对象是否可被回收是不够的。
一个对象要经过下面一段判断过程来判断它是否要被回收(建议收藏( ^__^ ) 嘻嘻……):
上面我们说的是存在于Java堆中的对象的回收,但其实在方法区还要回收以下东西:
假如常量池中有一个字符串 "abc" ,但是系统中没有一个String 对象指向它,也就是这个常量没有被引用,
当GC在回收时会回收此字面量。
GC在回收方法区时会采用一下2种方式:
GC在回收内存时会采用多种垃圾收集算法,这些算法各有优劣。
此算法是最基础也是最古老的垃圾回收算法,该算法主要经过2个过程
复制算法针对效率问题进行了优化,它将内存区域划分为2块,每次只使用其中一块。
如图:
左右两侧的区域状态在每一次回收后都来回转换...
IBM公司经研究表明,Java堆新生代种的对象98%是 '朝生夕死' 的对象,比如临时变量等作用域很少的对象。
所以现在的虚拟机并不会按照 1:1的比例划分两个区域。
现在的JVM虚拟机中,将新生代划分为一块 Eden
区域,和2块较小的 Survivor
区域(from ,to区)。
每次使用Eden区和1块Survivor区(from区)最为活动区域,当发生内存回收时,将这2块内存中的存活对象复制到另一块Survivor区(to区)。
在 HotSpot
虚拟机中,Eden区和Survivor的划分是: 8: 1,这样,活动区域占新生代的 (8+1)/10 *100% = 90%,只有10%的内存浪费。
老年代: 当将存活对象从活动区域(Eden,from) 复制到 to区时,如果to区不够用,则将剩下的存活对象放到 老年代。
(完~)