二 JAVA垃圾收集器 垃圾收集简史 垃圾收集提供了内存管理的机制使得应用程序不需要在关注内存如何释放内存用完后垃圾收集会进行收集这样就减轻了因为人为的管理内存而造成的错误比如在C++语言里出现内存洩露时很常见的Java语言是目前使用最多的依赖于垃圾收集器的语言但是垃圾收集器策略从世纪年代就已经流行起来了比如SmalltalkEiffel等编程语言也集成了垃圾收集器的机制 常见的垃圾收集策略 所有的垃圾收集算法都面临同一个问题那就是找出应用程序不可到达的内存块将其释放这里面得不可到达主要是指应用程序已经没有内存块的引用了而在JAVA中某个对象对应用程序是可到达的是指这个对象被根(根主要是指类的静态变量或者活跃在所有线程栈的对象的引用)引用或者对象被另一个可到达的对象引用 Reference Counting(引用计数) 引用计数是最简单直接的一种方式这种方式在每一个对象中增加一个引用的计数这个计数代表当前程序有多少个引用引用了此对象如果此对象的引用计数变为那么此对象就可以作为垃圾收集器的目标对象来收集 优点 简单直接不需要暂停整个应用 缺点 需要编译器的配合编译器要生成特殊的指令来进行引用计数的操作比如每次将对象赋值给新的引用或者者对象的引用超出了作用域等 不能处理循环引用的问题 跟蹤收集器 跟蹤收集器首先要暂停整个应用程序然后开始从根对象扫描整个堆判断扫描的对象是否有对象引用这里面有三个问题需要搞清楚 .如果每次扫描整个堆那么势必让GC的时间变长从而影响了应用本身的执行因此在JVM里面采用了分代收集在新生代收集的时候minor gc只需要扫描新生代而不需要扫描老生代 .JVM采用了分代收集以后minor gc只扫描新生代但是minor gc怎么判断是否有老生代的对象引用了新生代的对象JVM采用了卡片标记的策略卡片标记将老生代分成了一块一块的划分以后的每一个块就叫做一个卡片JVM采用卡表维护了每一个块的状态当JAVA程序运行的时候如果发现老生代对象引用或者释放了新生代对象的引用那么就JVM就将卡表的状态设置为髒状态这样每次minor gc的时候就会只扫描被标记为髒状态的卡片而不需要扫描整个堆具体如下图 .GC在收集一个对象的时候会判断是否有引用指向对象在JAVA中的引用主要有四种Strong referenceSoft referenceWeak referencePhantom reference ◆ Strong Reference 强引用是JAVA中默认采用的一种方式我们平时创建的引用都属于强引用如果一个对象没有强引用那么对象就会被回收 public void testStrongReference(){ Object referent = new Object(); Object strongReference = referent; referent = null; Systemgc(); assertNotNull(strongReference); } ◆ Soft Reference 软引用的对象在GC的时候不会被回收只有当内存不够用的时候才会真正的回收因此软引用适合缓存的场合这样使得缓存中的对象可以尽量的再内存中待长久一点 Public void testSoftReference(){ String str = test; SoftReference<String> softreference = new SoftReference<String>(str); str=null; Systemgc(); assertNotNull(softreferenceget()); } ◆ Weak reference 弱引用有利于对象更快的被回收假如一个对象没有强引用只有弱引用那么在GC后这个对象肯定会被回收 Public void testWeakReference(){ String str = test; WeakReference<String> weakReference = new WeakReference<String>(str); str=null; Systemgc(); assertNull(weakReferenceget()); } ◆ Phantom reference MarkSweep Collector(标记清除收集器) 标记清除收集器最早由Lisp的发明人于年提出标记清除收集器停止所有的工作从根扫描每个活跃的对象然后标记扫描过的对象标记完成以后清除那些没有被标记的对象 优点 解决循环引用的问题 不需要编译器的配合从而就不执行额外的指令 缺点 .每个活跃的对象都要进行扫描收集暂停的时间比较长 Copying Collector(复制收集器)复制收集器将内存分为两块一样大小空间某一个时刻只有一个空间处于活跃的状态当活跃的空间满的时候GC就会将活跃的对象复制到未使用的空间中去原来不活跃的空间就变为了活跃的空间复制收集器具体过程可以参考下图 优点 只扫描可以到达的对象不需要扫描所有的对象从而减少了应用暂停的时间 缺点 .需要额外的空间消耗某一个时刻总是有一块内存处于未使用状态 .复制对象需要一定的开销 MarkCompact Collector(标记整理收集器)标记整理收集器汲取了标记清除和复制收集器的优点它分两个阶段执行在第一个阶段首先扫描所有活跃的对象并标记所有活跃的对象第二个阶段首先清除未标记的对象然后将活跃的的对象复制到堆得底部标记整理收集器的过程示意图请参考下图Markcompact策略极大的减少了内存碎片并且不需要像Copy Collector一样需要两倍的空间 JVM的垃圾收集策略 GC的执行时要耗费一定的CPU资源和时间的因此在JDK以后JVM引入了分代收集的策略其中对新生代采用MarkCompact策略而对老生代采用了MarkSweep的策略其中新生代的垃圾收集器命名为minor gc老生代的GC命名为Full Gc 或者Major GC其中用Systemgc()强制执行的是Full Gc Serial Collector Serial Collector是指任何时刻都只有一个线程进行垃圾收集这种策略有一个名字stop the whole world它需要停止整个应用的执行这种类型的收集器适合于单CPU的机器 Serial Copying Collector 此种GC用XX:UseSerialGC选项配置它只用于新生代对象的收集以后XX:MaxTenuringThreshold来设置对象复制的次数当eden空间不够的时候GC会将eden的活跃对象和一个名叫From survivor空间中尚不够资格放入Old代的对象复制到另外一个名字叫To Survivor的空间而此参数就是用来说明到底From survivor中的哪些对象不够资格假如这个参数设置为那么也就是说只有对象复制次以后才算是有资格的对象这里需要注意几个个问题 ◆ From Survivor和To survivor的角色是不断的变化的同一时间只有一块空间处于使用状态这个空间就叫做From Survivor区当复制一次后角色就发生了变化 ◆ 如果复制的过程中发现To survivor空间已经满了那么就直接复制到old generation ◆ 比较大的对象也会直接复制到Old generation在开发中我们应该尽量避免这种情况的发生 Serial MarkCompact Collector 串行的标记整理收集器是JDK update之前默认的老生代的垃圾收集器此收集使得内存碎片最少化但是它需要暂停的时间比较长 Parallel Collector Parallel Collector主要是为了应对多CPU大数据量的环境Parallel Collector又可以分为以下两种 Parallel Copying Collector 此种GC用XX:UseParNewGC参数配置它主要用于新生代的收集此GC可以配合CMS一起使用以后Parallel MarkCompact Collector此种GC用XX:UseParallelOldGC参数配置此GC主要用于老生代对象的收集 Parallel scavenging Collector 此种GC用XX:UseParallelGC参数配置它是对新生代对象的垃圾收集器但是它不能和CMS配合使用它适合于比较大新生代的情况此收集器起始于jdk 它比较适合于对吞吐量高于暂停时间的场合Serial gc和Parallel gc可以用如下的图来表示 Concurrent Collector Concurrent Collector通过并行的方式进行垃圾收集这样就减少了垃圾收集器收集一次的时间这种GC在实时性要求高于吞吐量的时候比较有用此种GC可以用参数XX:UseConcMarkSweepGC配置此GC主要用于老生代和Perm代的收集 |