@@@@@@,垃圾收集算法
(复制算法回收速度快对应Eden回收,标记整理速度慢对应老年代回收)
1,标记清除算法:首先标记处所有需要回收的对象,在标记完成后统一回收,后面所有的算法都是基于此的优化,缺点是标记和清除的效益不高,此外会产生大量的内存碎片。毕竟很多大大小小的内存不连续。
2,复制算法:将内存按容量大小划分为大小相等的块,每次只使用其中的一块,当一块用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次性清理掉,剩下的内存是连续的,这样可以避免内存碎片,但是可以使用内存缩小为原来一般,代价过高。
3,标记整理算法:复制算法在对象存活率较高的时候效益会变得低下并且会浪费内存空间,根据老年代的特点,有人提出了另外一种标记整理算法,标记整理算法是在标记清除的基础上让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存,这样可以大大减少内存碎片。
总而言之如果存活对象朝生夕死就用复制算法,适用于Eden区回收,如果对象存活率高就用标记整理算法,适用于老年代回收。
@@@@@@新生代和老年代划分的依据以及永久代的内存划分
(1)对象优先在Eden分配:大多数情况下,新建的对象分配在Eden区中,当EDen区没有足够空间进行分配的时候,虚拟机将发起一次Minor Gc(新生代GC 回收频繁,回收速度快)。MinorGc会采用复制算法将EDen里面还存活的对象复制到Eden区的另一块Survivor区,同时把Eden和第一块Survivor清空,如果Survivor空间不够就需要进行空间分配担保。
(2)大对象直接进入老年代:所谓的大对象是指需要大量连续内存空间的java对象,最典型的就是很长的字符串以及数组,虚拟机提供了一个 -xx:pretenureSizeThreshold参数,令大于这个设置值的对象直接分配在老年代,当老年代内存不够的时候会触发老年代Gc(老年代GC也叫FUll GC 速度慢,采用的是标记整理算法)。
(3)长期存活的对象将进入老年代:虚拟机为每个对象定义了一个对象年龄(Age),每次回收的时候如果发现有GCRoots引用,年龄就会+1.当年龄大于15岁(默认)的时候就会从Eden晋升到老年代中。
(4)动态对象年龄判定:如果在Survivor空间中,对象的年龄大于其中某一半以上的,那么可以直接进入老年代。
(5)空间分配担保:在发生MinorGc之前。虚拟机会先检查老年代最大可用连续空间是否足够给Eden腾出复制的空间,如果这个条件成立,那么可以安心的复制Eden到老年代,然后回收掉Eden里面所有的垃圾,因为Eden回收是按照复制算法的。如果空间不足够,虚拟机会查看是否允许担保失败,如果允许,就会尝试一次MonorGc,如果不允许就会进行一次FullGc。其实思想就是找老年代要空间,因为一旦新生代发生GC,无论回收是否成功都会发生一次复制,如果新生代空间不足,那么就必须要腾出空间给新生代回收。
(6)关于永久代(方法区)
废弃常量:没有任何地方引用到这个常量。
无用的类:该类的所有实例都已经被回收,加载该类的classloader已经被回收,该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。大量的使用反射,动态代理,自定义ClassLoader的场景都需要虚拟机具备类写在功能,以保证永久代不会溢出。
@@@@@垃圾收集器的分类(发展史)
------------------以下三种都是采用复制算法的垃圾收集器。新生代垃圾收集器
(1)Serial收集器:jdk1.3之前新生代收集的唯一选择。特点是在垃圾收集的时候必须停止其他工作线程,直到收集垃圾结束。在单个cpu环境来说,没有线程交互开销,专心收集垃圾效益可观。
(2)ParNew收集器:其实就是Serial收集器的多线程版本。可以与CmS收集器配合工作。
(3)ParallelScavenge收集器:重点关注垃圾收集占到整个程序运行的比例,越低越好。
-------------------下面看看标记整理算法的垃圾收集器,老年代垃圾收集器
(1)SeialOld收集器:配合ParallelScavenge,专门收集老年代的。
(2)ParalleOld:ParallelScavenge的老年代版本。
-----------------------下面看看最先进的收集器。基于标记清除算法的:
(1)cms收集器:以获得最短回收停顿时间为目标的收集器,响应速度快,良好的用户体验,属于跨时代的新型垃圾收集器,过程如下:
初始标记---并发标记---重新标记--并发清除。其中初始标记只是标记一下GCroots能直接关联到的对象,速度很快,并发标记阶段就是进行GCroots (追溯)Tracing的过程,由于CMS收集器内存回收过程是和用户线程一起并发执行的,所以停顿的时间很短,用户几乎感受不到。他的特点就是并发收集,低停顿。但是也有其缺点:对CPU资源敏感,虽然不会导致用户线程停顿,但是对CPU资源占用比较多。并且无法处理浮动垃圾,所谓浮动垃圾就是指清理垃圾的时候由于用户线程还在运行,会有新的垃圾产生,这些垃圾只能等到下一次垃圾回收再去清除,同时CMS垃圾收集会产生内存碎片。当内存不够用的时候就会产生FUllGc。
(2)G!收集器:当今最前沿的收集器,面向服务端,特点如下,并行与并发,充分利用cpu资源。分代收集,空间整合,不会产生内存碎片,可预测的停顿,可以建立可预测停顿时间模型。工作过程如下:初始标记--并发标记---最终标记--筛选回收。
@@@@@@@@@新生代和老年代里面的哪些垃圾可以被顺利回收?
可达性分析算法:无论是新生代Eden还是老年代或者是永久代。通过一系列称为GC ROOTS的对象作为起始点,往下搜索引用链,如果上持有的引用为null,就能被回收(称之为垃圾)。有三种类型可以作为GC ROOTS对象
(1)虚拟机栈(栈帧中的本地变量表)中引用的对象
(2)方法区中类静态属性引用的对象
(3)方法区中常量引用的对象。
(4)本地方法栈中引用的对象。
可以作为GCROOTS的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈桢中的本地变量表)中,现在很多应用仅仅方法区就有数百MB,如果要逐个检查,必然会小号很多时间,并且检查的时候,必须确保一致性(是指整个分析期间整个执行系统就像被冻结在某个时间点上,不可以出现分析过程中对象引用还在不断变化的情况),这也是导致GC进行时必须停顿所有线程的一个重要原因。为了快速遍历gcroots对象,在hotSpot的实现中,是使用了一组称为OopMap的数据结构来快速的完成Gcroots枚举。
还有一个很重要的知识点safepoint后面研究。
@@@@@@@再谈引用(如何在不是垃圾的情况下依然回收)
引用分以下几种
(1)强引用:最普遍的,只要部位NULL就不能被回收
(2)软引用:在系统将要发生内存溢出异常之前,会把这些对象进行回收,如果回收以后内存还是不够,会导致内存溢出异常。
(3)弱引用:内存回收的时候无论当前内存是否足够,都会回收,
(4)虚引用:唯一目的就是在这个对象被收集器回收时受到一个系统通知。
Reference && ReferenceQueue
SoftReference,WeakReference,PhantomReference拥有共同的父类Reference,看一下其内部实现:
Reference的构造函数最多可以接受两个参数:Reference(Treferent, ReferenceQueue queue)
referent:即Reference所包装的引用对象
queue:此Reference需要注册到的引用队列
ReferenceQueue本身提供队列的功能,ReferenceQueue对象同时保存了一个Reference类型的head节点,Reference封装了next字段,这样就是可以组成一个单向链表。
ReferenceQueue主要用来确认Reference的状态。Reference对象有四种状态:
1,active:GC会特殊对待此状态的引用,一旦被引用的对象的可达性发生变化(如失去强引用,只剩弱引用,可以被回收),GC会将引用放入pending队列并将其状态改为pending状态
2,pending:位于pending队列,等待ReferenceHandler线程将引用入队queue
3,enqueue:ReferenceHandler将引用入队queue
4,inactive:引用从queue出队后的最终状态,该状态不可变
Reference与ReferenceQueue之间是如何工作的呢?
Reference里有个静态字段pending,同时还通过静态代码块启动了Reference-handler thread。当一个Reference的referent被回收时,垃圾回收器会把reference添加到pending这个链表里,然后Reference-handler
thread不断的读取pending中的reference,把它加入到对应的ReferenceQueue中。
当reference与referenQueue联合使用的主要作用就是当reference指向的referent回收时,提供一种通知机制,通过queue取到这些reference,来做额外的处理工作。
[if !supportLineBreakNewLine]
[endif]
[if !supportLineBreakNewLine]
[endif]
@@@@@@对象被回收的时候最后的垂死挣扎
对象释放流程
对象状态:
Reachable可达
unfinalized没有执行过Finalize()方法,还没放入F-Queue队列没有准备执行。
unReachable不可达
Finalizable可执行Finalize()方法,放入了F-Queue队列等待执行。
finalized 该对象的finalize()方法已经被执行了。
对象初始状态Reachable+Unfinalized 可达+没有执行过Finalize()方法
1,对象unReachable,进入流程2.
2,如果没有重写Finalize()方法,进入流程9。
3,如果重写了Finalize()方法,并且unfinalized,进入流程6.
4,如果重写了Finalize()方法,并且finalized,进入流程9.
6,这个对象被放入一个F-Queue的队列中,状态变成Finalizable,由虚拟机创建一个优先级低点的Finalizer线程去执行其Finalize()方法。
7,执行Finalize()方法,状态变成finalized,如果发现对象没有自救,进入流程9。如果自救了,就会变成Reachable,状态也不会变成Reclaimed。等待下一次Gc。
8,如果对象不可达并且已经执行了Finalize()方法,就进入9
9,该对象变成Reclaimed状态,等待销毁。
注:当对象状态变成unReachable+finalized对象就可以顺利的销毁或者直接进入了Reclaimed状态也可以销毁。对象最多重生一次。