如何判断一个对象为垃圾对象
引用计数算法
- 给对象添加一个引用计数器,每当有一个地方引用它时,计数器的值就加1,当引用失效时,计数器的值减1,当计数器的值为0时,代表该对象是无用的对象.
- 引用计数器算法,实现简单,判断效率也很高,但是不能解决循环引用的问题.
可达性分析算法
- 主要是通过一系列的“GC ROOts”进行判断,从“GC Roots”开始往下搜索,搜索走过的路径称为引用链,当一个对象到“GC Roots”不在连通时,证明对象不可用.
- GC Roots对象主要包括以下几种对象:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中引用的对象.
引用的分类
- 强引用:就是指在程序代码中普遍存在new Object()这种形式的对象,只要强引用存在,一定不会被垃圾回收器进行回收.
- 软引用:描述一下有用但是非必需的对象,对于软引用,在系统将要发生内存溢出异常之前,将会对这些对象进行回收.
- 弱引用:用来描述非必需对象的,但是他的强度比软引用更弱一下,他会在下一次垃圾回收的时候进行回收.
- 虚引用:称为幽灵引用或者是幻音引用,他是最弱的一种引用关系,他不会对对象的生存时间构成影响,也无法通过虚引用来取得一个对象的实例.唯一的作用就是在对象被回收以后做一些操作,例如:对象在回收后,会向系统发一个通知.
finalize()方法
- 即使对象与GC Roots没有关联,这时候对象也不是非死不可.
- 真正宣告一个对象的死亡需要进行两次标记的过程,发现与GC Roots没有关联时进行一次标记,并且对这样的对象进行一次筛选,即是否可以执行finalize()方法.
- 对象覆盖了finalize()方法,并且该方法没有被虚拟机执行过,认为需要执行finalize()方法,该方法只能被虚拟机执行一次.
- 执行finalize()方法,这个对象会被放在一个队列里面,并且启动一个低优先级的finalize线程判断对象是否可以被回收.
- finalize线程会对堆了中的对象进行第二次标记,如果这对象重新与GC Roots进行关联,则逃脱死亡.
回收方法区
- 永久带的垃圾回收主要回收两部分内容:废弃的常量、无用的类.
- 类需要满足3个条件才能算为无用的类:该类的所有实例都已经被回收、加载该类的ClassLoader已经被回收、java.lang.class对象没有任何地方被引用,无法在任何地方通过反射访问到该对象.
- 新版本对永久的有优化,这里介绍的是老版本,给大家作为参考.
垃圾收集算法
标记清除算法
- 最基础的算法是标记清除算法,该算法分为两个阶段:标记阶段和清除阶段,首先标记出所要回收的对象,在标记完成以后统一回收所有被标记的对象.
- 这种算法的不足:效率问题,标记和清除两个过程笑了都不高,另一个是空间问题.标记和清除以后会产生大量不连续的空间碎片.
复制算法
- 为了解决标记清除算法的效率,复制算法可以将内存空间分为大小相等的两块,每次只使用其中的一块,当这块内存用完了就将活着的对象复制到另一块上面,然后再把以使用的空间一次清除掉.
- 优势:实现简单,运行高效,只是这种算法的缺点是:使用空间缩小为原来的一半.
- 现在虚拟机,对新生带的回收大部分采用复制算法.
- 新生代中的对象大部分不会存活很长时间,,所以对空间划分的比例并不需要按照1:1进行划分,一半eden区和survivor区的比例为8:1.每次使用一次eden和一块survivor区,当eden区和survivor区使用完毕以后,将存活的对象复制到另一块survivor区.
- 当survivor区不够时,需要老年代进行担保.
- 当对象的存活率比较高时,复制算法可能会复制大量的对象,效率比较低.
标记整理算法
- 标记整理算法,分为标记和整理两个步骤,也是先对不需要的对象进行标记,但是不是直接对对象进行清除,而是将存活的对象向空间的一边移动,使内存空间没有空间碎片的产生.
HotSpot虚拟机算法的实现
枚举跟节点
- GC停顿:枚举根节点必须保证一致性,一致性的意思就是:整个分析期间整个执行系统中对象的引用关系不发生变化,所以虚拟机必须暂停所有的java线程,称为Stop The World(STW).
- 虚拟机并不是在停顿的时候检查所有对象的引用关系,虚拟机知道哪些地方存放着对象的引用,在HotSpot虚拟机中,使用一组OOPMAP的数组来实现这一功能.
安全点
- 可能导致引用关系变化,或者说OOPMAP内容变化的指令非常多,如果为每条指令都生成对应的OOPMAP,那么会需要大量的存储空间,这样GC的成本会很高.
- HotSpot虚拟机只是在特定的位置记录这些信息,这些位置称为安全点.安全点的选取是以程序是否可以长期运行的特征进行选定的.
- 如何在GC发生的时候,让所有线程都到最近的安全点上停顿下来,可以分为主动式中断和抢先式中断.抢先式中断:不需要线程去主动配合,在GC发生时,把所有线程进行中断,如果不在安全点上,让他们跑到安全点上.主动式中断:是当GC发生时,不直接对线程进行操作,仅仅设置一个标志,每个线程执行时主动去轮询这个标志发现中断标志为真,就自己中断挂起.
安全区域
- 当线程处于Sleep状态或者Blocked状态,这是线程无法响应中断请求,到安全点停下来,这就需要安全区域来解决.
- 安全区域是指一段代码之中的引用关系不会发生变化,在这里任何时候进行GC都是安全的,可以把安全区域看作扩展的安全点.
- 在线程执行到安全区域中的代码时,首先标志已经进入了安全区域,那样在这段时间里jvm要发生GC时,就不用管标识在安全区域线程,当它要离开安全区域的时候需要检查是否完成了根节点的枚举.
垃圾收集器
serial收集器
- serial收集器是最基本的收集器。
- serial是一个单线程的收集器,他单线程的意思并不仅仅说明她只会使用一个cpu或者一条收集线程去完成垃圾回收工作,更重要的是:它进行垃圾收集,它会暂停所有的线程,直到垃圾回收结束。(Stop The World(STW))
- 它的唯一的优点就是实现简单、
ParNew收集器
- ParNew收集器是serial收集器的多线程版本,收集器的所有控制参数,收集算法,STW,对象分配管泽,收集策略与serial一样。
- 除了serial收集器以外,只有ParNew能与CMS收集器进行配合工作。
Parallel Scavenge收集器
- Parallel Scavenge收集器是一个新生代的收集器,使用复制算法,并行的多线程收集器。
- Parallel Scavenge收集器是一个吞吐量优先的收集器。
- 吞吐量就是cpu用于运行用户代码时间/(用户代码时间+垃圾收集的时间)。
serial Old收集器
- serial Old是serial的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
- serial Old唯一的用途是与Parallel Scavenge收集器搭配使用,另一种用途就是作为CMS收集器的后备方案。
Parallel Old收集器
- Parallel Old是Parallel Scavenge的老年代版本。
- 使用多线程和标记-整理算法。
- Parallel Old收集器出现后“吞吐量优先”收集器有了比较冥府其实的应用组合。
CMS收集器
- CMS收集器是一种以获取最短回收停顿时间为目标的收集器。
- CMS收集器是基于标记-清除算法实现的。
- 它的这个运作过程分为4步:初始标记、并发标记、重新标记、并发清楚。
- 初始标记和重新标记这两个步骤仍然需要Stop The World.初始标记,仅仅是标记一下GC Roots能直接关联到的对象。并发标记:是对GC Roots Tracing的过程。重新标记:是为了修正并发标记期间因用户程序继续运作而标记产生变动的那部分对象记录。并发标记和并发清楚过程收集器线程可以与用户线程一起工作。
- 优势:并发收集、低停顿。
- CMS的缺点:CMS收集器对cpu资源非常敏感。CMS无法处理浮动垃圾。CMS是基于标记-清楚算法实现的,会出现空间碎片。
G1收集器
- G1收集器与其他收集器具备以下特点:并行与并发、分代收集、空间整合、可预测的停顿。
- 使用G1收集器,它将整个java堆划分为多个大小相等的独立的区域,虽然保留着新生代和老年代的概念,但是新生代和老年代不再是物理隔离的了,他们都是一部分Region的集合。
- G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划的避免在整个java堆中进行安全区域的垃圾收集。G1跟踪各个Region里面垃圾回收的价值大小,在后台维护一个优先级列表,每次根据允许的收集时间优先回收价值最大的Region。
- 在G1中,Region之间对象引用以及其他收集器中新生代与老年代之间的对象引用,虚拟采用Remembered set来避免完全堆扫描。G1中每一个Region都有一个与之对应的Remembered set,当检查reference引用的对象处于不同的Region中通过CardTable把相关的引用信息记录到被引用对象所属的Region的Remembered set中。
- G1收集器运作大致分为:初始标记、并发标记、最终标记、筛选回收。
- 初始标记:仅仅是标记下GC Roots能直接关联到的对象。并发标记是从GC Roots开始对堆中对象进行可达性分析找出存活的对象。最终标记:是为了修正正在并发标记期间因用户程序继续运作而导致标记发生变动的那部分标记,虚拟机将这段时间对象变化记录在线程的Remembered set logs里面。
- 帅选回收阶段首先对各个Region回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。
理解GC日志
- 33.125:[GC Defnew:3324k-152(3712k)0.0025925 Secs] 3324k->152k(11904k) 0.003680 sess]
- 0.0025925 是回收发生需要的时间,3324k堆内存的使用空间,152k:回收后堆使用的空间,11904k总的堆的内存空间。33.125是虚拟机启动到现在经历的时间(秒数)。GC Defnew :GC发生的区域。3324k-152k(11904)该区域未发生回收使用的内存->回收后使用的(总的内存).
内存分配与回收策略
- 以下介绍每一种都是一种空间分配的策略,我们可以根据实际情况,进行设置,或者有些参数是默认的。
- 对象的内存分配,网大方向讲,就是在堆上分配,对象主要分配在新生代的eden区上,如果启动了本地线程分配缓冲将会在TLAB上分配。
- 对象在新生代eden区中分配,当eden区中没有足够空间进行分配,虚拟机将发起一次Minor GC.
- 有参数设置,令大于参数设置的对象可以直接进入老年代,这样做的目的是避免eden区及两个survior区之间发生打乱的内存复制。
- 虚拟机给每个对象定义了一个对象年龄计数器,如果对象在eden出生并且经历过一次Minor GC仍然存活,并且能被Surivor容纳的话对象的年龄为1,对象在Survior区中每熬过一次Minor,GC年龄加1,默认15岁,会晋升到老年代。
- 如果在Survior空间中相同年龄所有对象大小的综合大于Survivor空间的一半,年龄大于或者等于该年龄的对象,就可以直接进入老年代。
空间分配担保
- 在发生Minor GC之前,虚拟机会检查老年代最大可用的连续的存储空间是否是大于新生代所有对象总空间,如果这个条件成立,则MinorGC可以确保安全。如果不成立,会检查是否允许担保失败,如果允许会继续检查老年代最大可用的连续空间是否大于力气晋升老年代对象的平均大小,如果大于则尝试进行一次Minor GC。
我是菜鸟,希望大家多多留言讨论~谢谢!
我的笔记是看完深入理解java虚拟机的体会,是本好书,推荐给大家.