明耀

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

JVM垃圾回收

程序计数器、虚拟机栈和本地方法栈3各区域随线程而生,随线程而死,所以不需要过多考虑回收的问题,方法或线程结束时,内存自然就随着回收了。

那么什么时候会触发一个对象的回收的呢?

1、    对象没有引用

2、    作用域发生未捕获异常

3、    程序在作用域正常执行完毕

4、    程序执行了System.exit()

5、    程序发生意外终止(被杀进程等)

几种垃圾回收办法

1、 在JDK1.2之前,使用的是引用计数器算法,即当这个类被加载到内存以后,就会产生方法区,堆栈、程序计数器等一系列信息,当创建对象的时候,为这个对象在堆栈空间中分配对象,同时会产生一个引用计数器,同时引用计数器+1,当有新的引用的时候,引用计数器继续+1,而当其中一个引用销毁的时候,引用计数器-1,当引用计数器被减为零的时候,标志着这个对象已经没有引用了,可以回收了!这种算法在JDK1.2之前的版本被广泛使用,但是随着业务的发展,很快出现了一个问题

当我们的代码出现下面的情形时,该算法将无法适应

a)         ObjA.obj = ObjB

b)         ObjB.obj - ObjA

这样的代码会产生如下引用情形 objA指向objB,而objB又指向objA,这样当其他所有的引用都消失了之后,objA和objB还有一个相互的引用,也就是说两个对象的引用计数器各为1,而实际上这两个对象都已经没有额外的引用,已经是垃圾了。

2、根搜索算法
 根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

目前java中可作为GC Root的对象有主要是全局性的,如常量、静态属性,执行上下文( 栈帧中的本地变量表)

1、虚拟机栈中引用的对象(本地变量表)

2、方法区中静态属性引用的对象

3、方法区中常量引用的对象

4、本地方法栈中引用的对象(Native对象)

不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:

(1)强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)

(2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)

(3)弱引用:在GC时一定会被GC回收

(4)虚引用:由于虚引用只是用来得知对象是否被GC,在这个对象呗收集器回收时收到一个系统通知

方法区的数据因为回收率非常小,而成本又比较高,一般认为是“性价比”非常差的,方法区的回收条件非常苛刻,只有同时满足以下三个条件才会被回收!

1、所有实例被回收

2、加载该类的ClassLoader被回收

3、Class对象无法通过任何途径访问(包括反射)

收集后的垃圾,一般通过以下几种算法进行处理

1、    标记-清除算法

标记-清除算法采用从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收。标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片!

2、复制算法

复制算法采用从根集合扫描,并将存活对象复制到一块新的,没有使用过的空间中,这种算法当控件存活的对象比较少时,极为高效,但是带来的成本是需要一块内存交换空间用于进行对象的移动。也就是我们前面提到的

3、    标记-整理算法

 标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。

4、分代收集算法

当前的商业虚拟机的垃圾收集都采用分代收集算法,根据对象存活周期的不同将内存划分为几块,java堆中的新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用“标记——清理”或者“标记——整理”算法进行回收。

垃圾收集器

1、Serial收集器

 Serial收集器是历史最悠久的一个回收器,JDK1.3之前广泛使用这个收集器,目前也是ClientVM下 ServerVM 4核4GB以下机器的默认垃圾回收器。串行收集器并不是只能使用一个CPU进行收集,而是当JVM需要进行垃圾回收的时候,需要中断所有的用户线程,知道它回收结束为止,因此又号称“Stop The World” 的垃圾回收器。

2、    ParNew收集器

ParNew收集器其实就是多线程版本的Serial收集器,其运行示意图如下

 同样有Stop The World的问题,他是多CPU模式下的首选回收器(该回收器在单CPU的环境下回收效率远远低于Serial收集器,所以一定要注意场景),也是Server模式下的默认收集器。

3、    ParallelScavenge

ParallelScavenge又被称为是吞吐量优先的收集器,它是一个新生代收集器,也是使用复制算法,并行的多线程收集器。运行示意图如下

 

ParallelScavenge所提到的吞吐量=程序运行时间/(JVM执行回收的时间+程序运行时间),假设程序运行了100分钟,JVM的垃圾回收占用1分钟,那么吞吐量就是99%。在当今网络告诉发达的今天,良好的响应速度是提升用户体验的一个重要指标,多核并行云计算的发展要求程序尽可能的使用CPU和内存资源,尽快的计算出最终结果,因此在交互不多的云端,比较适合使用该回收器。

4、ParallelOld

ParallelOld是老生代并行收集器的一种,使用标记整理算法、是老生代吞吐量优先的一个收集器。这个收集器是JDK1.6之后刚引入的一款收集器,我们看之前那个图之间的关联关系可以看到,早期没有ParallelOld之前,吞吐量优先的收集器老生代只能使用串行回收收集器,大大的拖累了吞吐量优先的性能,自从JDK1.6之后,才能真正做到较高效率的吞吐量优先。其运行示意图如下

 

 5、SerialOld

SerialOld是旧生代Client模式下的默认收集器,单线程执行;在JDK1.6之前也是ParallelScvenge回收新生代模式下旧生代的默认收集器,同时也是并发收集器CMS回收失败后的备用收集器。其运行示意图如下

 

6、CMS

CMS又称响应时间优先(最短回收停顿)的回收器,使用并发模式回收垃圾,使用标记-清除算法,CMS对CPU是非常敏感的,它的回收线程数=(CPU+3)/4,因此当CPU是2核的实惠,回收线程将占用的CPU资源的50%,而当CPU核心数为4时仅占用25%。他的运行示意图如下

整个过程分为4个步骤,包括:初始标记、并发标记、重新标记、并发清除

其中,初始标记、重新标记这两个步骤仍然需要“stop the world”。初始标记仅仅只标记一下GC Roots能够直接关联到的对象,速度很快,并发标记就算是进行GC Roots Tracing 的过程,重新标记是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。

 

Minor GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存

Major GC 是清理老年代。

Full GC 是清理整个堆空间,包括年轻代和老年代。

posted on 2017-08-07 18:11  明耀  阅读(160)  评论(0编辑  收藏  举报