jvm常见垃圾回收算法
jdk1.7.0_79
众所周知,java是一门不用程序员手动管理内存的语言,全靠jvm自动管理内存,既然是自动管理,那必然有一个垃圾内存的回收机制或者回收算法。本文将介绍几种常见的垃圾回收(下文简称gc)算法。
在java堆上分配一个内存给实例对象时,此时在虚拟机栈上引用型变量就会存放这个实例对象的起始地址。
object obj = new object();
现在如果我们将变量赋值为null。
obj = null;
此时可以看到java堆上的实例对象无法再次引用它,那么它就是被gc的对象,我们称之为对象“已死”。那虚拟机栈上的obj变量呢?上文《jvm入门——运行时数据区》提到过,虚拟机栈是线程独占的,也就是说随着线程初始而初始,消亡而消亡,当线程被销毁后,虚拟机栈上的内存自然会被回收,也就是说虚拟机栈上的这块内存空间不在虚拟机gc范围。下图展示了垃圾回收的内存范围:
1.对象是否“已死”算法——引用计数器算法
对象中添加一个引用计数器,如果引用计数器为0则表示没有其它地方在引用它。如果有一个地方引用就+1,引用失效时就-1。看似搞笑且简单的一个算法,实际上在大部分java虚拟机中并没有采用这种算法,因为它会带来一个致命的问题——对象循环引用。对象a指向b,对象b反过来指向a,此时它们的引用计数器都不为0,但它们俩实际上已经没有意义因为没有任何地方指向它们。所以又引出了下面的算法。
2.对象是否“已死”算法——可达性分析算法
这种算法可以有效地避免对象循环引用的情况,整个对象实例以一个树呈现,根节点是一个称为“gc roots”的对象,从这个对象开始向下搜索并作标记,遍历完这棵树过后,未被标记的对象就会判断“已死”,即为可被回收的对象。
gc算法
1.标记-清除算法
等待被回收对象的“标记”过程在上文已经提到过,如果在被标记后直接对对象进行清除,会带来另一个新的问题——内存碎片化。如果下次有比较大的对象实例需要在堆上分配较大的内存空间时,可能会出现无法找到足够的连续内存而不得不再次触发垃圾回收。
2.复制算法(java堆中新生代的垃圾回收算法)
此gc算法实际上解决了标记-清除算法带来的“内存碎片化”问题。首先还是先标记处待回收内存和不用回收的内存,下一步将不用回收的内存复制到新的内存区域,这样旧的内存区域就可以全部回收,而新的内存区域则是连续的。它的缺点就是会损失掉部分系统内存,因为你总要腾出一部分内存用于复制。
在上文《jvm入门——运行时数据区》提到过在java堆中被分为了新生代和老年代,这样的划分是方便gc。java堆中的新生代就使用了gc复制算法。在新生代中又分为了三个区域:eden 空间、to survivor空间、from survivor空间,from survivor 和 to survivor大小相同,且保证一个为empty.。不妨将注意力回到这张图的左边新生代部分,:
新的对象实例被创建的时候通常在eden空间,发生在eden空间上的gc称为minor gc,当在新生代发生一次gc后,会将eden和其中一个survivor空间的内存复制到另外一个survivor中,如果反复几次有对象一直存活,此时内存对象将会被移至老年代。可以看到新生代中eden占了大部分,而两个survivor实际上占了很小一部分。这是因为大部分的对象被创建过后很快就会被gc(这里也许运用了是二八原则)。
3.标记-压缩算法(或称为标记-整理算法,java堆中老年代的垃圾回收算法)
对于新生代,大部分对象都不会存活,所以在新生代中使用复制算法较为高效,而对于老年代来讲,大部分对象可能会继续存活下去,如果此时还是利用复制算法,效率则会降低。标记-压缩算法首先还是“标记”,标记过后,将不用回收的内存对象压缩到内存一端,此时即可直接清除边界处的内存,这样就能避免复制算法带来的效率问题,同时也能避免内存碎片化的问题。老年代的垃圾回收称为“major gc”。
不积跬步,无以至千里;不积小流,无以成江海。
1.jvm的堆栈
栈:在jvm中栈用来存储一些对象的引用、局部变量以及计算过程的中间数据,在方法退出后那么这些变量也会被销毁。它的存储比堆快得多,只比cpu里的寄存器慢
堆:用来存储程序中的一些对象,比如你用new关键字创建的对象,它就会被存储在堆内存中,但是这个对象在堆内存中的首地址会存储在栈中。
栈内存在jvm中默认是1m,可以通过下面的参数进行设置
-xss
1
最小堆内存在jvm中默认物理内存的64分之1,最大堆内存在jvm中默认物理内存4分之一,且建议最大堆内存不大于4g,并且设置-xms=-xmx避免每次gc后,调整堆的大小,减少系统内存分配开销
-xms-xmx
1
2
3
在jvm的堆内存中有三个区域:
1.年轻代:用于存放新产生的对象。
2.老年代:用于存放被长期引用的对象。
3.持久带:用于存放class,method元信息。
如图:
一.年轻代
年轻代中包含两个区:eden 和survivor,并且用于存储新产生的对象,其中有两个survivor区如图:
可以使用参数配置年轻代的大小,如果配置它为100m那么就相当于2*survivor+eden = 100m
-xmn
1
可以配置eden 和survivor区的大小,这里配置的是比值,jvm中默认为8,意思就是eden区的内存比上survivor的内存等于8,如果年轻代的xmn配置的100m,那么eden就会被分配80m内存,每个survivor分配10m内存
-xx:survivorratio
1
还可以配置年轻代和老年代的比值,这里需要注意:老年代的内存就是通过这个比值设置,jvm没有给你直接设置老年代内存大小的参数;如果整个堆内存设为100m并且在这里设置年轻代和老年代的比值为7,如果持久代占用了10m,那么100m-10m=90m这里的90m就是老年代和年轻代的内存总和,且年轻代占用(90/(7+1)*7)的内存,老年代就占用(90/(7+1)*1)的内存。
-xx:newratio
1
二.老年代
年轻代在垃圾回收多次都没有被gc回收的时候就会被放到老年代,以及一些大的对象(比如缓存,这里的缓存是弱引用),这些大对象可以不进入年轻代就直接进入老年代(1.防止新生代有大量剩余的空间,而大对象创建导致提前发生gc;2.防止在eden区和survivor区的大对象复制造成性能问题),这个可以通过如下参数设置,表示单个对象超过了这个值就会直接到老年带(默认为0):
-xx:pretenuresizethreshold
1
并且大的数组对象也会直接放到老年代,比如array和arraylist(底层用数组实现),因为数组需要连续的空间存储数据。
三.持久代
持久代用来存储class,method元信息,大小配置和项目规模,类和方法的数量有关,一般配置128m就够了,设置原则是预留30%空间,它可以通过如下参数进行大小配置:
-xx: permsize -xx: maxpermsize
1
2
持久代也可能会被gc回收,如果持久代理的常量池没有被引用以及一些无用的类信息和类的class对象也会被回收。
相关文章:
jvm垃圾回收算法
java 详解垃圾回收与对象生命周期
相关视频:
javascript初级教程
以上就是jvm垃圾回收算法与jvm的堆内存中的三个区域的详细内容。