代码改变世界

Java内存管理和垃圾回收

2013-02-17 06:51 onm 阅读(...) 评论(...) 编辑 收藏
最简单的,Java可以通过new关键字来申请分配一块内存。例如:Object obj = new Object();变量obj是这个内存对象的引用。同时Java对于引用引入四种类型,分别是:强引用,软引用,弱引用,幽灵引用。前面这个obj就是个强引用。其他三个引用分别对应三个类SoftReference 、 WeakReference 和 PhantomReference。从垃圾收集角度考虑,强引用在引用存在的情况下是不会进行内存回收的,SoftReference在内存不足需要回收时是可以被回收的,WeakReference每次来及回收的时候都是可以被回收的。 那么,为什么要进行垃圾回收呢?如若不进行垃圾回收,不断的进行内存申请分配,内存容量是有限的,则会造成内存溢出。那么内存都在哪些区域分配的呢,哪些区域又会造成内存溢出呢?来看下面这张图,这是网络上面的一张图画,看到运行期数据区是内存非配的区域。分为:方法区,Java堆,Java栈,程序计数器,本地方法栈。 jvm内存非配 通常,我们所说的内存溢出大部分是Java堆部分。Java栈也会造成内存溢出,常见的例子是递归调用方法造成死循环,这个时候会造成内存溢出,这和Java方法调用过程有关。Java堆部分则是前面所说给对象申请分配内存,内存不足导致的内存溢出。内存溢出也就是常说的OOM。 写C,C++的程序员都知道要自己申请内存空间,然后自己小心的释放。这种手工管理内存的方法,由于马虎大意很可能出现纰漏。Java采用了自动垃圾收集的方式,解放了生产力。那么Java的垃圾回收是什么样的呢? 要回收垃圾,首先要找到垃圾所在地,最简单的方式是引用计数方法,一个对象每增加一个引用增加一个计数,反之减少一个计数,当计数是0的时候,我们知道这个对象是不可达的,所以可以安全的将这个对象的内存释放。但是引用技术存在一些问题,比如常说的循环引用问题。另一种方式是对象引用遍历方式,Java采用的就是这种方式。如下图所示。“对象引用遍历从一组对象(GC ROOT)开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。 ” 对象引用遍历 找到了垃圾那么怎么回收呢?有几种方法,如标记-清除,标记-压缩,复制。对应示意图分别如下: 标记-清除,就是标记的那些不可达的对象内存区域直接释放,优点是是速度快,缺点是内存碎片多。 标记清除 标记-压缩,就是那些可达的对象内存区域重新排列,整理成顺序区块。优点是没有内存碎片,缺点是速度慢。 标记压缩 复制,内存区域分两半,一半不使用,当使用的那半用满需要收集的时候,将分配的单元复制到之前没用的那一半,然后以此类推。优点是速度和内存随便达到了较好的平衡,缺点是浪费空间。 复制 那么到底使用哪种方法呢?对于Java虚拟机而言,对于不同的分代和不同的版本的垃圾收集器往往采用不同的方法。Java虚拟机采用分代垃圾收集机制。如下图所示: 分代垃圾收集 分为三代,分别是年轻代、老年代和持久代。 “年轻代:所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。 年老代:在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。 持久代:用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。 对于实际的java虚拟机又有很多版本,简要罗列一下,分别有:Serial 最基本,最古老的收集器 ,新生代复制算法,老年代标记整理算法;ParNewSerial多线程版本(新生代) ;Parallel Scavenge;Parallel Old (Parallel Scavenge老年代版本) ;CMS(Concurrent Mark Sweep);G1(JDK1.7)。此处需要一些注意说说明,传统早期的GC(垃圾收集)需要停止其他的活动活动。这种方法意味着所有与应用程序相关的工作停止,只有GC运行。 对于并行的GC,多个线程同时进行GC,其它线程仍然需要停止。对于的并发GC,实现复杂,其它线程最短时间的停止。