垃圾回收

一、java内存区域

5个区域:方法区,虚拟机栈,本地方法栈,堆,程序计数器。

程序计数器:线程私有

可以看做是当前字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。(分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器完成)

java虚拟机栈:线程私有。

生命周期与线程相同,虚拟机栈描述的是java方法执行的内存模型:每个方法在执行时都会创建一个栈帧,用于存储变量表,操作数栈,动态链接,方法出口等信息。

每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

局部变量表:

存放了编译器可知的各种基本数据类型(boolean,char,int,float,double,byte,short,long),对象引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量的大小。

本地方法栈:

与虚拟机栈的区别:虚拟机栈为虚拟机执行java方法(字节码)服务,本地方法栈则为虚拟机使用到的native方法服务。

Sun HotSpot虚拟机把本地方法栈和虚拟机栈合二为一。

java堆:线程共享

java heap是java虚拟机所管理的内存中最大的一块,用于存放对象实例,虚拟机启动时创建。java堆是垃圾收集器管理的主要区域,也叫GC堆。采用分代收集算法

方法区:线程共享

用于存储已被虚拟机加载的类信息,常量,静态变量,即时编辑器编译后的代码等数据。(HotSpot虚拟机将GC分代收集扩展至方法区,用永久代来实现方法区)

运行时常量池:

方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中存放。

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

生存还是死亡:如何判断对象还活着?

1.引用计数算法:

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1,当引用失效时,计数器值减1,任何时候计数器值为0就是不可能再被使用的。

但是,很难解决对象之间互相循环引用的问题。虚拟机不是通过这个方法来判断对象是否存活的。

package edu.xatu.gc;

public class GC {

    public Object instance = null;
    public static void testGC(){
        GC obja = new GC();
        GC objb = new GC();
        obja.instance = objb;
        objb.instance = obja;
        
        obja = null;
        objb = null;
        
//        
        System.gc();
    }
}

上面的代码,obja,objb已经为空,不可能再被引用,但是因为互相引用着对方,引用计数器不为0,按理说不能回收,但是实际虚拟机已经回收了。

2.可达性分析算法:

基本思想:通过一系列的称为“GC Roots”的对象作为起始点,从这个节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则此对象不可用。

可作为GC Roots的对象有:

(1)虚拟机栈(栈帧中的本地变量表)中引用的对象

(2)方法区中类静态属性引用的对象

(3)方法区中常量引用的对象

(4)本地方法栈中JNI即native方法引用的对象

垃圾收集算法:

(1)标记-清除算法:Mark-Sweep

首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。

不足:效率低,标记,清除两个过程效率都低。标记清除后会出现大量不连续的内存碎片。

(2)复制算法:(新生代)

将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块内存用完了,就把还存活着的对象复制到另一块上面,然后把这一块内存空间一次清理掉。

虽然效率高,但是内存缩小了一半,代价高。

对象存活率较高时进行较多的复制操作,所以用于回收新生代(朝生夕死),将内存分为一块Eden和两块Survivor空间,每次使用Eden和一块Survivor,回收时,将Eden和这块Survivor还存活着的对象复制到另一块survivor空间上,清除掉Eden和这块Survivor。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1

(3)标记-整理算法:(老年代)

标记的过程与标记-清除算法一样,但是后续不是直接对可回收对象急性清理,而是让所有存活的对象都像一端移动,然后直接清除掉端便捷以外的内存。

分代收集算法:

根据对象存活周期不同,将java堆分为新生代和老年代,根据各个年代的特点选择最适用的的收集算法。

3.内存分配与回收策略:
(1)对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配,当Eden没有足够空间进行分配时,虚拟机会发起一次Minor GC

Minor GC:新生代GC,比较频繁,回收速度也比较快

Major GC/Full GC:发生在老年代的GC,速度较慢。

(2)大对象直接进入老年代

大对象:需要大量连续内存空间的JAVA对象,比如很长的字符串以及数组byte[]数组。最怕遇到一群朝生夕灭的短命大对象。

(3)长期存活的对象将进入老年代

虚拟机给每个对象定义了一个对象年龄计数器,如果对象在Eden出生并且经过一次Minor GC仍然存在,并且能够被Survivor容纳的话,将被移动到Survivor空间中,年龄设为1.在Survivor中每熬过一次GC,年龄就增加1岁,当增加到一定程度时(默认15),会被晋升到老年代中。

(4)动态对象年龄判定

并不是永远要求对象的年龄必须达到maxTenuringThreshold才能晋升到老年代。如果Survivor空间中相同年龄所有对象大小的综合大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无须等到要求的年龄。

空间分配担保:

在发生Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确定是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次是有风险的。如果小于,或者HandlePromotionFailure设置不允许冒险,那这次也要改为进行一次Full GC

JDK 6 Update 24之后,HandlePromotionFailure参数不会再影响到虚拟机的空间分配担保策略。规则变为:只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进Minor GC,否则将进行Full GC

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章