JVM学习笔记2 垃圾收集器与内存分配策略

来源:互联网 发布:人工智能 研究报告 编辑:程序博客网 时间:2024/04/29 03:20

一、对象已死?

垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象有哪些还“活着”,哪些已经“死去”(即不可能再被任何途径使用的对象)

1、根搜索算法(GC Roots Tracing)

算法的基本思路是:通过一些名为“GC Roots” 的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

JAVA中,可作为GC Roots的对象包括:

  • 虚拟机栈(栈帧中的本地变量表)中的引用变量
  • 方法区中的类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈中JNI的引用的对象

2、JAVA的引用

JDK1.2之前:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。

JDK1.2之后:对1.2之前的概念进行扩充,将引用分为四种类型:

  • 强引用(Strong Reference):类似“Object obj = new Object()”这类的引用;只要强引用还存在,垃圾收集器永远不会回收被引用的对象
  • 软引用(Soft Reference):描述有用但并非必需的对象;系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中,并进行第二次回收(如果这次回收后还是没有足够的内存,才会抛出内存溢出异常)。
  • 弱引用(Weak Reference):被弱引用关联的对象只能生存到下一次垃圾收集发生之前。(1.2之后,提供WeakReference类来实现弱引用)
  • 虚引用(Phantom Reference)(也称幽灵引用、幻影引用):一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。(1.2之后,提供了PhantomReference类来试下虚引用)

3、对象回收的二次标记过程

clip_image001

二、垃圾收集算法

算法

描述

缺点

适用

标记 - 清除算法

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

效率问题:标记和清除的效率都不高

空间问题产生大量不连续内存碎片

老年代

复制算法

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

有一半的空间浪费了(新生代其实不需要按照1:1的比例划分内存,HotSpot虚拟机是9:1)

新生代

标记 - 整理算法

先标记,然后让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

 

老年代

分代收集算法

根据对象的存活周期的不同将内存划分为几块,各个块适用最适当的收集算法

 

根据各个年代的特点采用不同的收集算法

三、垃圾收集器

如果说垃圾收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。

Sun HotSpot虚拟机1.6 u22的垃圾收集器:

clip_image002

clip_image003

四、内存分配与回收策略

  • 自动内存管理最终归结为自动化解决两个问题:给对象分配内存、回收分配给对象的内存
  • 对象的内存分配,往大方向讲,就是在堆上分配
  • 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特征,所以Minor GC非常频繁,一般回收速度也比较快。
  • 老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在ParallelScavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。

1、对象优先在Eden分配

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

/**
* VM args:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails
*/
public class TestAllocation {
private static final int _1MB = 1024*1024 ;
public static void main(String[] args) {
byte[] allocation1,allocation2,allocation3,allocation4;
        allocation1 = new byte[2 * _1MB] ;
        allocation2 = new byte[2 * _1MB] ;
        allocation3 = new byte[2 * _1MB] ;
        allocation4 = new byte[2 * _1MB] ;
    }
}

参数说明:

1. -gerbose:gc:表示输出虚拟机中GC的详细情况

2. -XX:+PrintGCDetails :在发生垃圾收集行为时打印内存回收日志,在进程退出时输出当前内存各区域分配情况

3. -Xms20M:最小堆内存大小20M

4. -Xmx20M:最大堆内存大小20M

5. -Xmn10M:新生代大小10M,则剩下10M的分配给老年代

6. -XX:SurviorRatio=8:新生代中Eden区与一个Survior区的空间比例是8:1,则新生代可用空间为8+1=9M

运行结果:

clip_image004

  • 在执行allocation4的分配时,发生了GC(可以在此处断点观察),新生代从6471k变为150k,总内存从6471k变为6294k,基本没改变
  • 从最后进程退出时打印出的堆详情可看成,allocation4存在在新生代中(def new generation),占用了4410k(总大小为9126k = 9M)
  • 而allocation1、allocation2、allocation3因为还存活,故没被回收,且由于Survivor空间才1M,无法容纳,所以移动到了老年代(tenured generation)(通过分配担保机制提前转移到老年代)

2、大对象直接进入老年代

  • 大对象:需要大量连续内存空间的Java对象,最典型的就是那种很长的字符串及数组
  • 这样做是为了避免在Eden区及两个Survivor区之间发生大量的内存拷贝。
  • 参数:-XX:PretenureSizeThreshold

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

  • 虚拟机给每个对象定义了一个对象年龄计数器
  • 参数:-XX:MaxTenuringThreshold(默认是15),每次Minor GC,Survivor区中的对象年龄都增加1,当超过参数设定的值,则晋升到老年代。

4、动态对象年龄判定

为了能更好地适应不同程序的内存状况,虚拟机并不总是要去对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

5、空间分配担保

clip_image005

参数:-XX:+HandlePromotionFailure

0 0
原创粉丝点击