JVM解读(五):JVM垃圾收集

JVM全称是java Virtual Machine(java虚拟机),JVM屏蔽了与各个计算机平台相关的软件和硬件差异。
在接下来的日子里,通过写博客的形式学习JVM,让自己更懂得Java!
本系列文章是对《深入分析javaweb技术内幕》和《深入理解java虚拟机》的总结,欢迎大家一起吐槽,一起进步。
《JVM解读》第一篇:JVM体系结构
《JVM解读》第二篇:JVM类加载器ClassLoader
《JVM解读》第三篇:JVM内存区域
《JVM解读》第四篇:JVM内存溢出异常分析

垃圾收集(Garbage Collection,GC)要考虑的两个问题就是

  • 垃圾检测
  • 检测到的垃圾如何回收

垃圾检测
java内存运行时,程序计数器,虚拟机栈,本地方法栈这三个区域是线程私有的,所以会随着线程的消亡而消亡。而java堆和方法区则不同,只有在程序运行期间才会知道创建哪些对象,这部分内存的分配和回收都是动态的,垃圾回收器所关注的是这部分内存。
不管通过哪种算法来判断对象是否存活,都与引用有关。下面先介绍下引用
引用
传统定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表一个引用。
java后来对引用进行了扩充,将引用分为多种
(1)强引用:指的是在程序代码中,类似Object o=new Object()这类引用。
(2)软引用:用来描述一些还有用但并不是非必须的对象。对于软引用关联的对象,在系统将要发生内存溢出的时候,将会对这些引用放进第二次回收的范围内。java.lang.ref.SoftReference
(3)弱引用:用来描述非必须的对象,但是强度比软引用更弱点。当垃圾回收器工作时,无论当前内存是否足够,都会回收弱引用。java.lang.ref.WeakReference
java.util.WeakHashMap
(4)虚引用:最弱的一种引用。为一个对象设置虚引用关联的唯一目的就是能在这个对象被垃圾回收器回收时受到一个系统通知。java.lang.ref.PhantomReference

引用计数法
算法思想:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时候计数器为0的对象就不可能被使用了
优点:实现简单,判断效率高
缺点:不能解决对象之间相互循环引用的问题。如对象A,B都有字段instance,赋值A.instance=B,B.instance=A,除此之外再也没有别的引用了。这就导致了双方的引用计数器都不为0,GC收集器无法回收他们。
所以java并没有采用这种算法。

可达性分析算法
像java,C#等主流语言实现中都是通过可达性分析来判定对象是否存活。
算法思想:通过一系列的称为”GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时,就证明此对象不可用。如下图
可达性分析算法判定对象是否可回收
图中object5,object6,object7相互相连,但是不能到达GC Roots,所以被判定为回收对象。
java中作为GC Roots的对象有以下几种情况

  • 虚拟机栈中的引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(本地方法)引用的对象。

    对象的死亡的判定
    即使在可达性分析算法中不可达的对象,也不一定是必须死亡,这个时候的对象处于一种缓刑期。真正宣告一个对象的死亡,至少要经历两次标记的过程
    (1)如果对象在进行可达性分析后发现没有与GC Roots相连的引用链,那它将会被第一次标记并且进行一次筛选。筛选的条件是此对象是否有必要执行finalize方法
    (2)当对象没有覆盖finalize()方法或者finalize()已经执行过了,虚拟机将这两种情况视为“没必要执行”。这次就判定对象真正的死刑了。

垃圾收集算法

1.标记—清除算法
算法思想:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。标记过程就是前面说的垃圾判定算法-可达性分析算法。
这是一个基础的算法,后序的算法都是在此基础上进行改进的。
缺点:效率不高,标记和清除 的两个过程都不高;另外就是空间问题,标记后会产生大量不联系的碎片。这就可能导致提前触发了垃圾收集器。
标记-清除算法

2.复制算法

复制算法是为了解决效率问题,以空间换时间的做法。
思路:将内存分为两个部分,每次只使用一块,当这一块用完了,就将这这块还存活的对象复制到另外一块中,然后集中清理这一块。这样使得每次都是对整个半去进行内存回收,内存分配时也不用考虑内存碎片问题。只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。就是每次只能使用一般的空间。
复制算法

3.标记-整理算法
复制算法在对象存活率高的情况下,效率将变低,并且如果我们不想浪费50%的空间的。标记-整理算法适合存活对象少,垃圾多的情况下,年老代会选用这种方法。
算法思想:和标记-清除算法一样,但后续的步骤不是直接对回收对象进行清理,而是让所有存活对象都向一段移动,然后直接清理掉端边界以外的内存。
标记-整理算法

4.分代收集算法
当前虚拟机的垃圾收集都采用”分代收集算法”
思路:根据对象存活周期的不同将内存分为几块。一般把java堆分为新生代和年老代。这样就可以根据各个年代的特点采用最合适的垃圾清理算法。
在新生代中,每次垃圾收集器都发现有大批对象死去,只是少量存活,这就可以采用复制算法,只要做付出少量存活对象的复制成本就可以完成。
在年老代中,因为对象的存活率高,没有额外空间对它进行分配担保,就要采用“标记-清理算法”或“标记-整理算法”。
分代算法
其实在年轻代中还划分了更细的部分,我们先看下这张图
这里写图片描述
JVM将整个堆划分为年轻代,年老代和永久区
(1)年轻代:又分为Eden区和Survivor区,其中新创建的对象都在Eden区中,当Eden满后就会触发minor GC将Eden区任然存活的对象复制到其中一个survivor区中,另外一个survivor区存活的对象也复制到这个survivor区中,保证只有一个Survivor区是空的。
(2)年老代:存放年轻代的survivor满后触发minor GC后任然存活的对象,当Eden满后存入的survivor也存不下时,GC将会将存活对象放入年老代,如果年老代也满了,则将会触发Full GC回收整个堆内存。
(3)Perm区:类的对象,如果一个类被频繁的加载,也可能导致Perm区满,Perm区的垃圾也有Full GC回收。
一般建议年轻代是整个堆的1/4,Survivor是整个年轻代的1/8

方法区回收
java虚拟机没有对方法区做垃圾回收规定,永久代的垃圾收集效率非常低。
永久代的垃圾收集主要回收两部分:废弃常量和无用的类。
如何判断一个类是无用的类呢?

  • 该类所有的实例都已经被回收,java堆中不存在该来的任何实例
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方呗引用,无法在任何地方通过反射访问到该类。

在大量使用反射,JDK动态代理,CGLib等ByteCode框架,动态生成JSP以及OSGI这类频繁自定义的ClassLoader的场景都需要虚拟机具有卸载的功能,防止永久代溢出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值