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

1.   GC概述

       当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对那些“自动化”的技术实施必要的监控和调节。

       对于Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不絮地执行者出栈和入栈操作,当方法或线程结束,内存就跟随着收集。Java堆和方法区则不一样,这部分内存的分配和收集都是动态的,垃圾收集器所关注的是这部分的内存。

 

2.   对象已死?

       在Java堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行收集前,第一件事情就是要确定这些对象之中哪些海“存活”,哪些已经“死去”(即不可能再被任何途径使用的对象)。

 

2.1 引用计数算法

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

       引用计数算法(Reference Counting)的实现简单,判定效率也很高,在大部分情况下它都是一个不错的算法,如Python中就使用了引用计数算法进行内存管理,但主流的Java虚拟机都没有选用引用计数算法来管理内存,最主要的原因是它很难解决对象之间互相循环引用的问题。

 

2.2 可达性分析算法

       在主流的商用程序语言(Java、C#)的主流实现中,都是通过可达性分析(Reachability Analysis)来判定对象是否存活的。这个算法所的基本思想就是通过一系列的称为“GCRoots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(ReferenceChain),当一个对象到GC Roots没有任何引用链相连(图论中称从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

       如下图,对象object5、object6、object7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为时可收集的对象。


在Java语言中,可作为GC Roots的对象包括下面几种:

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

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

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

4) 本地方法栈中JNI(即一般说的Native方法)引用的对象。

 

2.3 引用

       在JDK1.2以前,Java中引用的定义很传统:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用,这样非黑即白的状态不能满足很多应用场景。

       在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。

 

1) 强引用就是指在程序代码中普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

2) 软引用是用来描述一些还有用但非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。若这次回收还没有足够的内存,才会抛出内存溢出异常。

3) 弱引用也是用来描述非必需对象的,被弱引用关联的对象只能生存到下一次垃圾收集发送之前。

4) 虚引用也称幽灵引用或幻影引用,为一个对象设置需引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。


2.4 回收方法区(元空间)

       在Java虚拟机(以下简称JVM)中,类包含其对应的元数据,比如类的层级信息,方法数据和方法信息(如字节码,栈和变量大小),运行时常量池,已确定的符号引用和虚方法表。Java8后,元空间替代了持久区,在元空间中,类和其元数据的生命周期和其对应的类加载器是相同的。一个类加载器被垃圾回收器标记为不再存活,其对应的元空间会被回收。

       将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化Full GC以及对以后的并发隔离类元数据等方面进行优化。

 

3.   垃圾收集算法

3.1 标记-清除算法

       最基础的收集算法是“标记-清除”(Mark-Sweep)算法,首先标记处所有需要回收的对象,在标记完成后统一回收所有被标记的对象。主要不足有两点:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片。

 

3.2 复制算法

       复制(Copying)的收集算法将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉。复制算法虽然实现简单,运行高效且不容易产生内存碎片,但是却将内存空间缩小为原来的一半。

 

3.3 标记-整理算法

       在“标记-整理”(Mark-Compact)算法中,标记的过程仍然与“标记-清除”算法一样,但后续步骤是让所有存活的对象都向一段移动,然后直接清理掉端边界以外的内存。

 

3.4 分代收集算法

       当前商业虚拟机的垃圾收集都采用“分代收集”(GenerationalCollection)算法,这种算法一般把Java堆分为新生代和年老代,在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而年老代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。

       垃圾收集器一方面提高了开发效率,保证了程序的稳定性;另一方面由于垃圾收集器必须跟踪内存的使用情况,释放没用的对象,在完成内存的释放后还需要处理堆中的碎片,这些操作必定会增加JVM的负担,从而降低程序的执行效率。

 

4.   垃圾收集器

       垃圾收集算法是内存回收的理论基础,而垃圾收集器就是内存回收的具体实现。下面介绍一下HotSpot(JDK 7)虚拟机提供的几种垃圾收集器,用户可以根据自己的需求组合出各个年代使用的收集器。

 

4.1 Serial/Serial Old

  Serial/Serial Old收集器是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。

 

4.2 ParNew

  ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集。

 

4.3 Parallel Scavenge

  Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。

 

4.4 Parallel Old

Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。

 

4.5 CMS

  CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。

 

4.6 G1

  G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。

 

5.   内存分配与回收策略

       Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存。

       对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓存,将按线程优先在TLAB上分配。其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置。


1)对象优先在Eden分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次MinorGC。现在的商业虚拟机一般都采用复制算法来回收新生代,将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活的对象一次性地复制到另外一块Survivor空间上,最后处理掉Eden和刚才的Survivor空间。(HotSpot虚拟机默认Eden和Survivor的大小比例是8:1)当Survivor空间不够用时,需要依赖老年代进行分配担保。

2) 大对象直接进入老年代。所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。

3) 长期存活的对象将进入老年代。当对象在新生代中经历过一定次数(默认为15)的Minor GC后,就会被晋升到老年代中。

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


注意:

        Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间。垃圾收集器回收的是无任何引用的对象占据的内存空间而不是对象本身。

 

 

参考资料:

Java永久代去哪儿了:http://blog.csdn.net/chenleixing/article/details/48286127

Java垃圾回收机制:http://www.cnblogs.com/dolphin0520/p/3783345.html

《深入理解Java虚拟机》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值