这里,我建议大家先读一下拙作: java内存管理
上图中的5部分:
方法区永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
方法区也成永久代(permanent generation)
java堆是java虚拟机管理的最大一块内存。且被所有线程共享。这块区域在虚拟机启动时就会创建。它存在的唯一目的就是存放对象。
我们主要就是在堆里回收垃圾
我们先看看java中堆的组成
不能被访问到的对象就是垃圾
那么我们如何判断一个对象已经无法被访问到呢?这里至少有两种算法。
Person a=new Person("name1"); Person b=a; pserson c=a; c=new Person("name2");在上面的代码中,new Person在堆里产生了一个对象,这个对象被引用了三次。后面c=new Person("name2"),原来的那个对象的被引用次数就较少一次,成了2。
public class ReferenceCountingGC { public static void main(String[] args) { testGC(); } public Object instance = null; private static final int _1MB=1024*1024; /** * 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否被回收过 */ private byte[] bigSize = new byte[2 * _1MB]; public static void testGC(){ ReferenceCountingGC objA=new ReferenceCountingGC(); ReferenceCountingGC objB=new ReferenceCountingGC(); objA.instance=objB; objB.instance=objA; objA=null; objB=null; // 假设在这行发生GC,那么objA和objB是否能被回收? System.gc(); } }逻辑上来说,obja与objb已经无法被访问到了,它就是垃圾#应该被清除,但是从引用计数法上来说,obj1与ojb2的被引用数都不为0,他们不应该被清除#
我们读gc报告后,就能知道,这个两个对象都已经被清除了,这就说明java并没有用引用计数法来判定对象是否不可达#
(怎么对gc的报告,一会再说)另外,java不用引用技术法并不能说明这个方法不好,Python就用引用法来管理
这个方法逻辑上也很简单
上图中,从根可以到达object1234,但是object5,object6,object7是我们无法到达的。所有我们认为object5,6,7就是垃圾,就是应该回收的。
那么问题来了,跟是什么?
在Java语言里,可作为GC Roots的对象包括下面几种:
虚拟机栈(栈帧中的本地变量表)中的引用的对象。
方法区中的类静态属性引用的对象。
方法区中的常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)的引用的对象。
这里也至少有三种算法
第一步:根据上面的跟搜索算法,确定那些对象是应该清除的,并且标记这个对象
第二步:根据第一步做的标记,清除对象。
示意图如下:
这算法的缺陷很明显:有内存碎片的问题。
算法的前提是:将内存区域分成两部分,并且每次只使用一部分
回收垃圾时
第一步:把正在使用部分中的存活对象,复制到第二块内存上
第二步:将第一块内存完全擦除。
示意图如下:
这个算法的优势在于:没有碎片,但是劣势但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。而且复制算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。
这个算法与标记-回收算法类似
第一步:标记出存活对象,垃圾对象。
第二步:将存活对象向内存的一端移动。
第三步:清除掉边界外的内存。
示意图如下:
堆的分代情况在上文已经说到了。
在新生代我们采用的是复制算法。
老生代采用的是标记-整理算法或标记清理算法。
这里就牵扯了一个新问题,对象在内存中,是怎么分配的?
如果是小对象,直接放到新生代的eden区和一个Survivor区;如果是大对象,就直接放到老生代。
如果在新生代分配内存时,发现不够用了,就使用复制算法,将eden和一个survivor区的存活对象复制到另一个suvivor区。
等于说是,用10%的区域来存放90%的内容。
这个能放下吗?
大部分情况下,答案都是肯定的,因为ibm有研究表明新生代的对象有98%是朝生夕死的。
那如果只占10%的survivor区域真的存不下90%区域中的存活对象呢?
答案是:把这些对象放到老年代中。
如果新生代的某个对象经历了15次垃圾回收都没有死,那把它也放到老生代里。
那如果新生代,老生代都满了呢?
不是还有一个异常叫OutOfMemoryError: Java heap space么?
另外,垃圾收集的动作也有两种:
新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
老年代 GC(Major GC / Full GC):指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程)。MajorGC 的速度一般会比 Minor GC 慢 10倍以上。
那么大对象,小对象的边界是什么?多大算大,多小算小呢?jvm有一个参数来设定这个值,大家有兴趣的百度之。
下一节,我们看看几个gc的实例及gc报告的阅读
深入理解java虚拟机 第三章
http://www.cnblogs.com/dolphin0520/p/3783345.html
http://www.th7.cn/Program/java/201409/276272.shtml
http://www.cnblogs.com/gw811/archive/2012/10/19/2730258.html
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/dlf123321/article/details/47786083