java也谈gc

1 篇文章 0 订阅

转载site:http://blog.csdn.net/liguogangde/article/details/9063261

author:李国刚


java也谈gc

来谈谈java关于gc方面的知识。如果有纰漏,也请大家多多指教。

本文主要是针对Hotspot虚拟机讨论。其它的虚拟机可能有所不同,请读者自行区别呵。因为我们通常用的也是Hotspot。嘿嘿,开源,免费嘛!

众所周知,java对内存的回收是由gc自动回收。这就不得不说java中有哪些内存需要回收,那么就得先看看java有哪些内存区域。

java运行时的内存区域:

1.java堆,这是java程序中应用最多的一块内存区域。我们new一个对象的内存都在这里分配。

2.方法区,这是java程序的代码区域。类,方法,变量的信息都存在这个区域,当然常量池也在这个区域。

3.虚拟机栈,java程序是支持多线程的程序,也是基于栈解释的程序。所以程序调用方法的压栈,出栈就是指的这个虚拟机栈。虚拟机栈主要是分配对象的引用和基本类型。

4.本地方法栈,java程序是支持native方法的程序。那么native方法栈的空间将在这部分内存分配。

5.程序计数器,这个用到的内存极少,很多时候不需要考虑。当然这个是和一个线程绑定的。存放程序命令执行到的位置。

6.直接内存,需要强调这个不是在jvm进程的内存空间,而是用的操作系统的内存。之所以把它写到这里,是要提醒大家千万别忘了这块内存。这也是容易发生OOM的地方。特别是NIO的时候。下面会具体谈。

可以看出这些内存区域,1,2,6内存区域是全局共享的,也就是所有的线程都共用这些内存空间。3,4,5是线程绑定的,也就是说这些内存空间的生命周期是和线程息息相关的。那么3,4,5的内存释放就非常简单了。比如虚拟机栈,随着压栈内存分配,出栈内存释放。所谓压栈,出栈就是方法调用和方法退出。本地方法栈也一样。程序计数器随线程的回收而回收。下面重点谈谈1,2,6的内存回收。

关于java堆的内存回收:

毫无疑问这块内存是java中最活跃的内存区域。所有的java new一个对象都要在这里完成内存分配。当这块内存在发生gc后任然不能分配对象时,将发生OOM。java.lang.OutOfMemoryError: ......java heap space。这个通常是最容易产生的内存溢出。如果发生了这类OOM就要考虑是否堆内存设置得太小,或者程序是不是一直在分配内存并且持有强引用,使这些对象一直是强可及对象?好了,直接进入正题说这块内存是如何回收的。要知道如何回收先要知道关于任何gc都需要解决的问题。

1.内存中有哪些对象,哪些对象又是可以回收的,哪些对象是不能回收的

2.如何回收,怎么把需要回收的对象从内存中移除。

3,什么时候需要回收呢。

4.如何解决回收时程序的延迟,以及发生gc时的内存分配请求。特别是多线程程序,在发生gc时候必然还有很多线程在请求堆内存分配。怎么处理这些请求。

下面对这些问题一一解答。请记住问题的编号,下面会提到问题的编号。

关于第1个问题——java中找到对象是用的根搜索算法(不是引用计数)。那么java中的根如何判定呢。java中的根主要存在在这些地方:实例变量,静态变量,方法栈上的引用,本地方法栈的引用。也就是说java只能在这里找到现在程序还在使用的对象,不能回收的对象。那么怎么找到不需要的对象呢。嘿嘿,当然就是对内存中的所有变量进行标记,把需要使用的对象统统标记出来,打过标记的对象就是要用的,不要回收掉了。这里就产生了一系列问题呵,这些问题也是用来解答问题4的。java的内存可以分配N个G,那么这么多的对象,每次都标记,得要多久啊。况且java中有些对象是程序启动到结束一直都在堆里面的。每次都来标记它,然后又不清除,这多浪费啊!所以java的gc强大之处就展现出来了。

关于问题2如何回收——java采用分代回收。java中的堆内存分成两个代:新生代,老年代。

所谓新生代:  可以从字面上理解,就是那些还比较年轻的对象。就是"才"分配出来的对象。新生代的对象会根据一定的晋升策略进入老年代。晋升策略有(可能不全):

1,new一个对象这个对象足够大(默认是超过新生代内存的一半,这个可以配置),那么直接进入老年代。

2,当一个对象在新生代存活了一定次数(默认是15次,这个次数还是有点多呵,可以根据情况配置),就进入老年代。

3,在新生代发生gc时,“内存不足”(这里关系到新生代的内存回收策略,等会会讲到,也会说明为什么会不足),要老年代提供担保时。新生代多出的对象直接进入老年代。

新生代的回收策略: 新生代也分为两个区域:一部分叫做Eden区,乐透区,新生代会首先在这个区域分配对象。另一部分,由两块对等的区域组成,叫做Survivor。为什么会由两部分组成呢。这就不得不从新生代的特性说起了。在新生代请求分配内存最多的地方毫无疑问是方法栈中。通常随着方法的退栈,大多数对象都不是根可及对象了。所以这里的大多数对象都是可以回收的了。那么对这个些对象进行标记并且回收,那么回收率是非常高的。那采用哪种标记回收策略呢?这里主要有3种标记回收策略:

1,标记复制——标记可用对象然后复制到另一块内存区域,没有内存碎片。

2,标记清除——标记可用对象,然后对于不可用对象直接清除,这里必然产生内存碎片。

3,标记整理——标记可用对象,然后把可用对象往一段移动,记录可用对象区域,把可用对象另一端的对象清除掉。没有内存碎片。

显然对于新生代这种大多数对象在很快就可以被回收的情况,采用1,3种策略最合适,因为需要复制或移动的对象很少,同时不会产生内存碎片。GC在新生代通常采用的是标记复制算法。并且基于大多数对象可以被回收的假设,复制的两个区域大小不是对等的,即Eden和Survivor的大小不是对等的。默认配置是8:1。由于Survivor有两块对等的。所以新生代Eden区默认会占到五分之四。当发生GC时,会把Eden区和其中一块Survivor往另外一块Survivor复制。这个时候,另一块Survivor可能不能装下Eden和那一块Survivor的对象,如果这种情况发生就需要老年代担保,并直接进入老年代。新生代就这样来回复制,实现GC回收。在回收的过程中是要暂停内存的分配。所以这里是一个程序停顿点。通常年轻代的回收会很快,所以这里的停顿时间不会太长。但是年轻代的回收频率往往是较高的。当然年轻代的垃圾回收也可以配置单线程,多线程,可控制延时等回收器。

老年代的回收策略:这里主要讲cms的回收策略。至于G1或JDK1.5以前的一些老年代回收策略现在很少使用,这里暂时不解释。cms(concurrent-mark-sweep)从名字上来说,他是并行的老年代回收器。但是这不意味着所有老年代gc的过程都是可以和内存分配并行的。老年代回收分为下面几个过程:

1,初始化标记

2,并发标记

3,重新标记

4,并发清除

对于1,3过程对于内存的分配请求也是停顿的,对于2,4过程可以和内存的分配请求同时进行。老年代是用的标记清除算法,所以必然产生内存碎片。这可以开启内存碎片整理,在多少次GC后进行一次内存碎片整理(这是可以配置的)。这个整理过程是full GC 是让应用程序停顿的。

关于第3个问题——什么时候回收。对于新生代,通常是对象无法分配的时候会发生gc。对于老年代是到达一定的内存使用率发生一次gc。默认配置下的使用率是68%.这也是可以配置项。对于full gc可能在显示调用System.gc()后发生。当然有参数可以关闭显示调用gc。DisableExplicitGC参数设置成true,就不能显示的调用gc了。当然还有其它发生gc的情况,比如是否配置了允许担保失败呀。

关于第4个问题——分代回收是提升java性能的关键,cms的并行回收在一定程度上满足了gc和应用程序同时运行,减少了停顿时间。这也是java语言和虚拟机走向成熟,倍受关注,广泛使用的原因。当然很期待G1回收器的使用。这个并行,可以控制停顿时间,标记整理的老年代收集器,必然是java虚拟器的趋势。也是java语言走向更辉煌的一个期望。

关于方法区的内存回收:

一般来说方法区的内存分配和回收都不会像java堆那样频发。但这也不是真正意义上的永久代,这里还是会发生内存回收的。一般来说对于一个类的回收需要满足一下条件才能被回收(可能不全)

1,该类的所有实例都已经被回收

2,类的加载器已经被回收

3,类对应的class没有任何地方在使用,包括反射也不会用到。

这些条件其实比较苛刻了。在一般的应用程序中可能不会遇到。但是如果频繁使用cglib生成类或动态加载类或热部署jsp等应用程序里面还是会发生。

对于常量的回收,要满足常量没有任何强可及的引用。

关于直接内存的回收:

什么地方会使用到直接内存。一般来说是在NIO中。比如netty是默认是用的直接内存分配,mina在2.0后已经不是默认使用的直接内存了。之前已经强调了,直接内存不是使用的jvm进程的内存,而是使用操作系统的内存。这部分内存也可以在jvm启动参数里面配置。MaxDirectMemorySize。对于这块内存的回收往往要在full gc的时候才能回收。以前公司是有用netty的地方(Hbase的通信)发生了OOM,就是因为老年代的回收频率太低了,在直接内存溢出了也没有发生老年代回收。当然这里可以关闭DisableExplicitGC来解决。不过这也是一个危险的操作,意味着你允许了应用程序显示的调用System.gc(),所以在应用程序里面最好不要这么调用。关闭DisableExplicitGc,应该把相应的并行回收参数也打开,不然这里就是一个真正意义上的full gc,意味着程序是停顿的。至于具体参数可以查看jvm参数配置,这里不贴出来了。对于直接内存的回收还可以显示调用,DirectBuffer的cleaner的clean方法,具体可以参照另一个同学的博客http://blog.csdn.net/xieyuooo/article/details/7547435。里面介绍的很详细。

关于java的gc就简单介绍到这里了,当然java的gc还有很多复杂的应用。java的核心和优势之一就是强大的gc。平时可以多观察下java gc的情况,这对掌握应用程序的性能是非常有帮助的。整个jvm调优都是要基于gc的情况调整的。

如果本文有任何纰漏还请大家多多指正。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值