Java的内存回收之内存泄漏与垃圾回收

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Julin1214/article/details/51728650
内存泄漏
程序运行过程中会不断地分配内存空间,那些不再使用的内存空间应该即时回收它们,从而保证系统可以再次使用这些内存,如果存在无用的内存没有被回收起来,那就是内存泄漏。
垃圾回收机制
垃圾回收:
1:跟踪并完成每个Java对象,当某个对象处于不可达状态时,回收该对象所占用的内存;
2.清理内存分配、回收过程中产生的内存碎片
实现高效jvm的一个重要方面就是提供高效的垃圾回收机制。高效的垃圾回收机制既能保证垃圾回收的快速运行,避免内存的分配和回收成为应用程序的性能瓶颈,又不能导致应用程序产生停顿。
垃圾回收的基本算法
实际上,垃圾回收机制不可能实时检测到每个对象的状态,因此当一个对象失去引用后,它也不会被立即回收,只有等垃圾回收运行时才会被回收。
对于一个垃圾回收器的设计算法来说,大致有如下可供选择的设计。
一串行回收和并行回收:
串行回收就是不管系统有多少个cpu,始终只用一个cpu来执行垃圾回收操作;而并行回收就是把整个回收工作拆分成多部分,每个部分由一个cpu负责,从而让多个cpu并行回收。并行回收的执行效率很高,但复杂度增加,另外也有其他的一些副作用,比如内存碎片会增加。
二并发执行和应用程序停止:
Stop-the-word的垃圾回收方式在执行垃圾回收的同时会导致应用程序的暂停。并发执行的垃圾回收虽然不对导致应用程序的暂停,但由于并发执行垃圾回收需要解决和应用程序的执行冲突(应用程序可能会在垃圾回收的过程中修改对象),因此并发执行垃圾回收的系统开销比stop-the-word更高,而且执行时也需要更多的堆内存。
三压缩和不压缩和复制:
为了减少内存碎片,支持压缩的垃圾回收器会把所有的活对象搬迁到一起,然后将之前占用的内存全部回收。不压缩式的垃圾回收器只是回收内存,这样回收回来的内存不可能是连续的,因此将会有较多的内存碎片。较之前压缩式的垃圾回收,不压缩式的垃圾回收回收内存块,而分配内存时就会更慢,而且无法解决内存碎片的问题。复制式的垃圾回收将所有可达对象复制到另一块相同的内存中,这种方式的优点是垃圾回收过程不会产生内存碎片,但缺点也很明显,需要复制数据和额外的内存。

下面是关于三种垃圾回收的方式:
1复制:
将堆内存分成两个相同空间,从根(类似于前面介绍的有向图的起始定点)开始访问每一个关联的可达对象,将空间A的可达对象全部复制到空间B,然后一次性回收整个空间A。
对于复制算法而言,因为只需 访问所有的可达对象,将所有可达对象复制走之后就回收整个空间,完全不用理会那些不可达对象,所以遍历空间的成本较小,但需要巨大的复制成本和较多的内存。
2标记清除:
也就是不压缩的回收方式。垃圾回收器先从根开始访问所有可达对象,将它们标记为可达状态,然后再遍历一次整个内存区域,把所有没有标记为可达对象进行回收处理。
标记清除:无需进行大规模的复制操作,而且内存利用率高。但这种算法需要两次遍历堆内存空间,遍历的成本较大,因此造成应用程序暂停的时间随堆空间大小线性增大。而且垃圾回收的内存往往是不连续的,因此整理后堆内存里的碎片很多。
3标记压缩:
这是压缩方式,这种方式充分利用上诉两种算法的优点,垃圾回收器先从根开始访问所有可达对象,将它们标记为可达状态。接下来垃圾回收器会将这些活动对象搬迁到一起,这个过程也被称为内存压缩。然后垃圾回收机制再次回收那些不可达对象所占用的内存空间,这样就避免了回收产生的内存碎片。
不论采用哪种机制实现垃圾回收,不论采用哪种内存回收方式,具体实现起来总是利弊参半。因此,实际实现垃圾回收时总会综合使用多种设计方式,也就是针对不同的情况采用不同的垃圾回收实现。

现行的垃圾回收器用分代的方式来采用不同的回收设计。分代的基本思路是根据对象生存时间的长短,把堆内存分成3个代:
Young(年轻代)
Old(老年代)
Permanent(永久代)
垃圾回收器会根据不同代的特点采用不同的回收算法,从而充分利用各种回收算法的优点。

堆内存的分代回收
分代回收的一个依据是对象生存时间的长短,然后再根据不同的垃圾回收策略。采用这种“分代回收”的策略基于如下两点事实:
绝大多数的对象不会被长时间引用,这些对象在其Young期间就会被回收。
很老的对象和很新的对象之间很少存在相互引用的情况。
根据上面两点事实,对于young代的对象而言,大部分对象都会很快的进入不可达状态,只有少量的对象能熬到垃圾回收执行时,而垃圾回收器只需要保留Young代中可达状态的对象,如果采用复制算法只需要少量的复制成本,因此大部分垃圾回收器对Young代都采用复制算法。
1.Young代
对Young代采用复制算法只需遍历那些处于可达状态的对象,而且这些对象的数量较少,可复制成本也不大,因此可以充分发挥复制算法的优点。
Young代由一个Eden区和2个Survivior区构成。绝大多数对象先分配到Eden区中(有一些大的对象可能会直接被分配到Old代中)Survivor区中的对象都至少在Young代中经历过一次垃圾回收,所以这些对象在被转移到Old代之前会先保留在Survivor空间中。同一时间2个Survivor空间中有一个用来保存对象,另一个是空的,用来在下次垃圾回收时保存Young代中的对象。每次赋值就是将Eden和第一个Survivor的可达对象复制到第二个Survivor区中,然后Eden与第一个Survior区。Eden与第一个Survivor
2.Old代
Old代的垃圾回收具有如下两个特征:
Old代垃圾回收的执行频率无需太高,因为很少有对象会死掉
每次对old代执行垃圾回收需要更长时间来完成
垃圾回收器通常会使用标记压缩算法,这种算法可以避免复制Old代的大量对象,而且由old代的对象不会很快死亡,回收过程不会大量产生内存碎片。
3.Permanent代
Permanent代主要用于装载Class,方法等信息,默认为64M,垃圾回收机制通常不会回收Permanent代中的对象。对于那些需要加载很多类的服务器程序,往往需要加大Permanent代内存,否则可能因为内存不足而导致程序终止。
当Young代的内存需要将要用完的时候,垃圾回收机制会对Young代进行垃圾回收,垃圾回收机制会 采用较高的频率对Young代进行扫描和回收。因为这种回收的系统开销比较小,因此也被称为次要回收。当Old代的内存将要用完时,垃圾回收机制会进行全回收,也就是对Young代和Old代都要进行回收,此时回收的成本就要大的多,也被称为主要回收
Young代的内存先回收,会使用专门的回收算法,对于Old代的回收频率则要低的多,因此也会采用专门的回收算法。如果需要进行内存压缩,每个代都独立进行压缩。
常见垃圾回收器
1.串行回收器
串行回收器通过运行Java程序时使用-xx:+UseSerialGC附加选项启用。
串行回收器对Young代和Old代的回收都是串行的。而且垃圾回收执行期间会使得应用程序产生暂停。具体策略为,Young代采用串行复制的算法,Old代采用串行标记压缩算法。
2。并行回收器
并行回收器通过运行Java程序时使用-xx:+userParallelGC  附加选项启用,它可以充分利用计算机的多个CPU来提高垃圾回收的吞吐量。
并行回收器对于Young代采用与串行回收器基本相似的回收算法,只是增加了多CPU并行的能力,既同时启动多线程并行来执行垃圾回收。线程数默认为CPU个数,当计算机CPU很多时,可用-xx:ParallelGCThreads=size来减少并行线程的数目。并行回收器对于Old代采用与串行回收器完全相同的回收算法,不管计算机有几个CPU,并行回收器依然采用单线程、标记整理的方式进行回收。
对于并行回收器而言,只有多CPU并行的机器才能发挥其优势。
3.并行压缩回收器
并行回收压缩器通过运行Java程序时使用-xx:UseParallelOldGc附加选项启用,一样可通过-xx:ParallelGCThreads=size来设置并行线程的数目。
并行压缩回收器对于Young代采用与并行回收器完全相同的回收算法。
并行压缩回收器的改变主要体现在对Old代的回收上,系统首先讲old代划分成几个固定大小的区域。在mark阶段,多个垃圾回收线程会并行标记old代中的可达对象。当某个对象被标记为可达对象时,还会更新该对象所在区域的大小及该对象的位置信息。
summary阶段会从最左边的区域开始检验每个区域的密度,当检测到某个区域中能回收的空间达到了某个数值的时候,垃圾回收器会判定该区域以及该区域右边的所有区域都应该进行回收,而该区域左边的区域都会被会被标识为密集区域。垃圾回收器既不会把新对象移动到这些密集区域中去,也不会对这些密集区域进行压缩。summary阶段目前还是串行操作的,虽然并行是可以实现的,但重要性不如对mark和压缩阶段的并行重要。最后是compact阶段。回收器利用summary阶段生成的数据识别出有哪些区域是需要装填的,多个垃圾回收线程可以并行地将数据复制到这些区域中。
4.并发标识-清理回收器
并发标识-清理回收器通过运行Java程序时使用-xx:+UseConcMarkSweepGC附加选项启用。
CMS 回收器对Young代的回收方式和并行回收器的回收方式完全相同。
将young的内存设的过大也有一个坏处:当垃圾回收器回收Young代内存时,复制成本会显著上升,所以回收会让系统暂停时间显著加长。
CMS对old代的回收多数是并发操作,而不是并行操作。
CMS不会进行内存压缩,也就是说不可达对象占用的内存被回收以后,垃圾回收器不会移动可达对象占用的内存。
对于CMS回收器而言,当垃圾回收器执行并发标识时,应用程序在运行的同时也在分配对象,因此old代也同时在增长。而且,虽然可达对象在标识阶段会被识别出来,但有些在标识阶段成为垃圾的对象并不能立即被回收,只有等到下次垃圾回收时才能被回收。因此CMS回收器较之前面的几种回收器需要更大的堆内存。
对于Permanent代内存,CMS可通过运行java程序时使用-xx:+CMSClassUnloadingEnabled-xx:+CMSPermGenSweepingEnabled附加选项强制回收Permanent内存。




展开阅读全文

JAVA 垃圾回收

06-28

请看下面的程序运行的结果,rnrn为什么垃圾回收器会连续回收两个最近的对象(4731hello2,4730hello2)rnrn然后再从最前面回收(hello1)然后又回收两个最近的对象(4729hello2,4728hello2)rnrn然后回收当前最前面的对象(1hello2)rn而以后的规律却变成了rnrn1.回收当前最近的对象(4727hello2)rnrn2.回收当前最久的对象(3hello2)rnrn3.重复执行上面1、2两个步骤。rnrnrn我的系统是winxp jdk 用的是 1.6.0-beta2rnrn为了能看到了这么多行结果我在Eclipse 中运行了这个程序rnrnrn是不是所有的垃圾回收都是按这个规律回收的呀,rn还是跟本就没有什么规律rn我想知道它的工作过程rnrnrnrnpublic class test rn static long time=1;rn public test() rn rn public static void main(String[] args) throws Exception rn System.gc();rn hello1 h1=new hello1();rn h1=null; rn for(long t=0;t<10000;t++) rn rn System.out.println(t); rn rn hello2 tt=new hello2(); rn rn if(t<4744) time=1;//经过多次实验,我的机子会在这附近进行垃圾回收rn else time=500;rn Thread.sleep(time); rn rn rn rnrnclass hello1rn static long count;rn long con=0;rn hello1()rn count++;rn con=count;rn rn protected void finalize()rn System.out.println("回收第"+con+"hello1");rn rnrnclass hello2rn static long count;rn long con=0;rn hello2()rn count++;rn con=count; rn rn protected void finalize()rn System.out.println("回收第"+con+"Hello2");rn rnrnrnrn某一次程序执行后显示的代码:rnrn1rnrn2rnrn......rnrn4730rnrn4731rn回收第4731Hello2rn回收第4730Hello2rn回收第1hello1rn回收第4729Hello2rn回收第4728Hello2rn回收第1Hello2rn回收第4727Hello2rn回收第2Hello2rn回收第4726Hello2rn回收第3Hello2rnrn......rn 论坛

没有更多推荐了,返回首页