您的位置:首页 > 其它

JVM——GC

2016-04-22 11:44 211 查看

对象存活判定

To be, or not to be,that is the question……

在垃圾回收之前,进行的比较重要的事情就是对象“死活”的判定,要做到不能冤枉一个“好对象”,也绝不放过一个“死对象”,在现实生活中也是十分困难的,我们来看看在JVM中是怎么样做到的。

引用计数器算法

给每个对象添加一个引用计数器,当增加引用时,引用计数器+1,当删除引用时,计数器-1,当计数器=0的时候,说明这个对象已经没有引用了,可以视为删除的目标。

优点:实现简单,效率高效

缺点:对象之间存在互引用,A引用B,B同时也引用A,但是这整个已经没用了,但是也是不能清理,因为计数器!=0.在实际的使用中,使用较少。

可达性分析算法

基本思想:算法通过一个叫“GC Root”的根去向下访问其他对象,访问的路径叫做引用链,如果某个对象不能到达“GC Root”(没有任何引用链相连),那么这个对象就是未被使用的对象,就是可以回收的。如下图



5 6 7就是可以回收的。这种算法在实际中很常用。

引用的分类

Java中引用的定义:如果reference类型的数据中存放的数值代表着另外一块内存的地址,那么这个内存就叫做引用。这样来看,一个对象就只有,被引用和没引用两种状态。对于一些而不是那么重要的对象,如果内存充裕,则没有什么,如果内存紧张,需要进行GC的之后,内存还是不够,可以将非重要对象抛弃,而这种情况下,明显是无法判断出哪些是重要对象,哪些是不重要对象,从而使得重要对象无法创建,非重要对象却占着空间。为了解决这种状况,java中将引用类型进行了扩展。

引用类别:强引用,弱引用,软引用,虚引用。

强引用:在java中,强引用普遍存在,通过Object obj = new Object()这样申明的引用都是强引用,GC在任何情况下都不会回收这种引用的对象。

软引用:用来描述一些还有用但是非必须的对象。对于软引用关联的对象,系统发生OutOfMemoryError之前,会把它们列入回收的对象,对其进行二次回收,如果内存还是不够,抛出OutOfMemoryError,通过SoftReference类来实现软引用。

弱引用:也是用来描述非必需对象,但是它的强度比软引用更弱一些;被引用的对象只能生存到下一次垃圾回收之前;当GC时,无论内存够否,都会回收掉这类引用的对象。WeakReference类实现。

虚引用:幽灵引用或者幻影引用,一个对象有虚引用完全对其生存时间没有影响,也无法通过一个虚引用来获取一个对象的实例,设置其唯一的作用就是在系统回收对象前,可以收到系统通知。PhantomReference类来实现。

对象死不死?

对象在被可达性算法分析为没有引用链与GC root相连之后,GC并没有马上去回收它,而是先进行一次标记,意味着你要准备接收死刑了,在第一次标记之后,会对对象的finalize方法进行调用,如果对象中覆盖了finalize方法,还可以在其中进行自救,自救的方式就是与GC root上的对象发生直接关系,让自己有到达GC root的引用链,如果没有在finalize中进行自救,那么就行第二次标记,宣告死亡。

方法区的垃圾回收

一般在新生代的堆中进行垃圾回收,可以率可以达到70%~90%,但是在方法区回收垃圾效率很低,方法区中垃圾回收主要收集不用的常量和无用的类;

废弃常量判定:没有变量使用的常量,就可以回收,

无用的类判定:类的所有实例都被回收了,加载类的classLoader已经被回收了,该类的对应的java.lang.Class未被任何地方使用,不能通过反射机制来使用类的变量和方法。

垃圾收集算法

标记-清理

标记:如上所述,最少两次标记,不在赘述。

清理:将标记的对象进行清理。

缺点:效率不高,而且在清理过后,会有很对内存碎片,不利于大对象的分配。

停止-复制

将内存空间按容量分为两块,每次只使用其中的一块,每次进行垃圾回收,只需要将还存活的对象复制到另外一块空的内存,然后把当前块清空即可。

优点:简单高效

缺点:将内存空间大了对折

标记-整理

类似于标记-清除,但后续步骤不是直接进行清理,而不是先将存活对象移向一端,然后再进行清理,这样可以保证不会产生内存碎片。

分代收集算法

只是将java堆分为了新生代和老年代,根据各自的特点,新生代使用“停止-复制”算法,老年代使用“标记-整理”或者“标记-清理”算法。

内存分配与回收策略

新生代:一个Eden,两个survivor,现在Eden上面进行分配,如果Eden上面内存不足,出发一次Minor GC(发生在新生代的GC),将Eden上存活的对象复制到其中一个survivor,清空Eden,完成gc。

老年代:大对象直接分配在老年代上,长期存活的对象就被转移至老年代,没经历一次Minor GC,对象的年龄就长一岁,当到达一定年龄(默认是15岁)的时候,就会将对象转移到老年代;还有就是相同年龄的对象占用了survivor空间的一半以上,无须等到年龄满足,系统会自动将>=该年龄的对象移至老年区;

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

当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC。

内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。

执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。

质疑常规的认知,所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就 是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。

所以 Minor GC 的情况就相当清楚了——每次 Minor GC 会清理年轻代的内存。

Major GC 是清理老年代。

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

首先,许多 Major GC 是由 Minor GC 触发的,所以很多情况下将这两种 GC 分离是不太可能的。另一方面,许多现代垃圾收集机制会清理部分永久代空间,所以使用“cleaning”一词只是部分正确。

Major GC VS Full GC

大家应该关注当前的 GC 是否停止了所有应用程序的线程,还是能够并发的处理而不用停掉应用程序的线程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: