Java内存管理再探究

VSITren 9年前

来自: http://blog.csdn.net/tryitboy/article/details/50551523


以前写过JVM及内存管理的文章,现在看来,当时对Java虚拟机及其内存管理的方式还是认识不够深。今天结合书本再次做个整理。


来源

“垃圾回收”机制很容易理解。Java语言在创建对象的时候回占用内存,作为一种自我保护,避免内存泄露,Java提供了垃圾回收机制来回收不再使用的对象所占用的内存空间。


内存分配方式

关于Java常用虚拟机内存的分配,请参见以前的博文:点这里哦
Java虚拟机将自己分配的对象或者数组存储在某种数据结构中,简称这种数据结构为“分配表”。同时JVM还能区分栈帧里的局部变量指向堆里的哪一个对象或者数组。Finally,JVM能够追踪到堆中对象和数组保存的引用。
因为上述特性,JVM已经能够判断内存分配的对象在某个时刻是否依旧被对象或变量引用。在遇到不被引用的对象时,解释器就可以回收这个对象的内存。


基本标记算法

通常垃圾回收使用的方式叫做“标记算法”。顾名思义,就是把不再占用内存的对象标记出来,一一清除。具体过程如下:

  1. 迭代分配表,把所有的对象都标记为死亡。
  2. 从指向堆的局部变量开始,每次遇到对象,沿着对象的引用一直向下,每次遇到分配表中没有的对象或数组,就标记为存活(这就是标记算法)。一直向下,直到找出能从局部变量到达的所有引用为止。
  3. 再次执行第一步,迭代分配表。这次回收所有标记为死亡的对象占用的内存,同时释放内存,删除这些死亡对象。

    但是同时JVM面临一个问题,就是在回收过程中,应用程序可能一直在执行,所以回收执行前后的对象状态不一定一致,某个对象在回收前可能是活跃的,在回收执行之后也恰好不活跃了,这怎么处理?
    此时JVM也有优化机制。就是在执行回收时,应用程序进行短暂停顿(stop the world,STW),这个停顿不会影响到程序的正常执行。停顿之后,进行垃圾回收,然后继续执行应用程序。


弱代假设(Weak Generational Hypothesis)

然而,在实际的运行环境中,对象的状态不是均匀分布的。通常大部分对象在创建之后很早就不再被使用;同时对于旧的对象,也很少引用新的对象。因此在这里,可以将堆分成存放旧对象和新对象的不同区域,也就是常说的老年代和新生代(这就是新生代和老年代的由来,说到底还是为了方便垃圾回收)。


复制回收算法

此时就可以用复制算法了,也叫筛选算法
来看下文轩网技术团队的笔记:

由于新生代对象98%都是朝生夕死,故采用复制算法回收效率最高,将新生代分为一块Eden,二块Survivor区域
Eden区域用于新对象的内存分配。Eden内存分配采用bump-the-pointer技术,使用一个指针指向已分配内存的末尾,分配内存时,仅检查剩余内存是否满足新对象分配。效率高。对于多线程内存分配采用Thread-Local Allocation Buffers TLABS,每个线程有自己的一块空闲内存分配缓冲区。不需要任何锁机制,只有当一个TLAB满了以后才需要同步。
两块Survivor区域分为From和To,To区域用于下次新生代GC存活对象的存放地,From区域存放着至少活过一次新生代GC的对象。在一次新生代GC结束后,From变为To,To变为From。原文地址

上文说得很明显,这种复制算法的主要工作就是复制存活的对象,所以至始至终它处理的是活性对象,效率更高。新生代的Eden部分存放新对象,Survivor部分不停地变换From和To。此时如果存在好几次变换后依旧活着的对象,直接移到老年代(老不死的对象…)。
此外还有持久代,主要存放类信息什么的(类定义,结构,字段,方法(数据及代码)以及常量在内的类相关数据),和垃圾回收关系不大。而且在Java 8中,持久代已经废弃,取而代之的是元空间(metaspace),参见:Java 8的元空间Java 8: 从永久代(PermGen)到元空间(Metaspace)

到这里Java的内存管理差不多总结完了,如果你还想了解其他的垃圾回收算法,推荐阅读这篇文字:几种经典的垃圾回收算法


FYI:

http://www.open-open.com/lib/view/open1413872607965.html
http://blog.jobbole.com/80499/
http://developers.winxuan.com/blog/434
http://my.oschina.net/hnuweiwei/blog/291367?p=1