垃圾收集器算法
垃圾收集器在对堆进行回收时,首先要判断对象是否还存活。数组
判断对象是否存货的算法:安全
给对象添加一个引用计数器,每当一个地方引用他,程序计数器就加一,但引用失效时程序计数器减一,计数器为0的对象就是再也不使用的对象。多线程
优势:实现简单,断定效率高并发
缺点:很难解决对象之间循环引用的问题(例如两个对象互相引用,这样程序技术器永远不会为0)框架
算法的基本思想思路是经过一系列成为“GC ROOTS”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC ROOTS没有任何引用链相连说明这个对象是不可用的。布局
JAVA中能够做为GC ROOTS的对象性能
断定为不可达对象被标记一次并进行筛选,刷选条件为此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者虚拟机已经调用过这个方法,都视为没有必要执行。网站
视为有必要执行,那么这个对象会被放到一个F_Queue的队列中,由虚拟机创建、优先级低的finalizer线程去执行。(并不承诺等待它结束,避免发生死循环或者很慢形成其余对象等待,致使内存回收系统崩溃)。GC对F_QUEUE队列中的对象进行第二次标记,若是没有从新与引用链上的对象关联则被真正回收,不然将被移除“即将回收的集合”。spa
方法区回收:
在方法去中进行垃圾回收性价比比较低。
永久代主要回收两部分的内容:
知足这三个条件仅仅是能够回收,而不是必然要回收,是否必要回收,经过JVM参数进行设置。
在大量使用反射,动态代理,CGLib等字节码框架,动态生成JSP以及OSGI这类频繁自定义ClassLoader的场景都须要JVM具有卸载的功能,以保证永久代不会溢出。
思想:首先标记全部须要回收的对象,在标记完成后统一进行回收。
缺点:
思想:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块用完,就将还存活着的对象复制到另外一块,而后再把已使用过得内存空间一次清理掉。
优势:这样只须要对半个区进行内存回收,特不用考虑内存碎片问题,只要移动堆顶指针,按顺序分配内存,实现简单运行高效
缺点:将内存缩小了原来的一半
商业JVM都用这个算法来回收新生代,并不须要按照1:1来划份内存。而是将内存分为一块较大的Eden空间和两块较小的Survivor空间。Eden和Survivor的比例为8:1,当回收时一次性将Eden和Survivor中存活的对象复制到另外一块Survivor。也就是每次新生代可用内存为整个新生代内存的90%,只有10%的会被浪费。咱们没有办法保证每次回收有不超过10%的对象还存活着。当Survivor内存不够时须要其余内存(老年代)作担保进行分配担保。在对象存活率较高时就进行了比较多的复制操做,因此老年代不会选择这个算法。
过程和标记清理同样,只不事后续不是直接对可回收对象进行清理,而是让全部存活对象向一端移动,直接清除掉端边界之外的内存。
根据对象存活周期的不一样分为几块,通常把Java堆分为新生代和老年代。这样能够根据各个年代的特色采用适合的算法。新生代中,每次都有大量的对象死去,只有少许存活就采用复制算法,只须要付出少许存活对象复制成本。老年代对象存活率高,没有额外的空间进行分配担保使用标记清除或标记整理算法来尽心回收。
手机算法是内存回收的方法论,垃圾收集器是内存回收的具体实现。
JVM对垃圾收集器没有明确的规范,因此各个JVM的垃圾收集器可能有较大的差异。
HotSpot中所包含的垃圾收集器
是一个单线程收集器,不是它只会使用一个CPU或一个县城去执行垃圾回收的工做。在它执行垃圾回收时必须暂停其余全部的线程,知道它收集结束。
优势:简单高效,对于限定单个cpu的环境来讲,Serial收集器因为没有线程交互的开销,专心作垃圾回收天然能够得到更高的单线程收集效率。因此Serial对于运行在Client模式下的虚拟机是一个很好的选择。
二、ParNew收集器
是Serial收集器的多线程版本,包括收集算法、回收策略、STOP THE WORD等。
除了Serial收集器外只有ParNew可以与CMS收集器配合工做。
三、Paraller Scavenge收集器
也是使用了复制算法,又是并行的多线程收集器,可是Paraller Scavenge收集器的目标是达到一个可控的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),而其余的关注点则是尽量缩短垃圾收集时用户线程的停顿时间。停顿时间越短越适合与用户交互的成率,良好的响应速度可以提升用户体验。而高吞吐能够高效的利用CPU的时间,尽快完成程序的运算任务,主要适合在后台运算不须要太多交互的任务。
Paraller Scavenge没法与CMS配合工做
PS收集器参数
用于精确控制吞吐量
-XX:MaxGCPauseMillis:分别是控制最大垃圾收集停顿时间,GC停顿时间是以牺牲吞吐量以及新生代空间换取的所以不是越小越好。
-XX:GCTimeRadio:直接设置吞吐量大小,是垃圾收集时间占总时间的比例。默认值为99,就是容许最大1%的垃圾收集时间。
-XX:+UseAdaptiveSizePolicy:这是一个开关参数,打开这个参数,就不须要手工指定新生代的大小,Eden和Survivor的比例,晋升老年代对象的年龄。虚拟机会根据当前系统的运行状况收集性能监控信息,动态调整参数以提供最适合的停顿时间或者最大吞吐量。
四、Serial Old收集器
Serial Old是老年代版本,也是一个单线程收集器,使用标记整理算法。这个主要是给Client模式下的虚拟机使用。
五、Parallel Old收集器
是Parallel Scanvage的老年代版本,使用多线程和标记整理算法。
在注意吞吐量以及CPU资源敏感的场合,能够优先考虑使用Parallel Scavenge和Parallel Old。
六、CMS收集器
基于标记清除算法,以获取最短回收停顿时间为目标。目前很大一部分JAVA集中在互联网站或B/S系统的服务端上,这尤为重视服务响应速度,但愿系统停顿时间短。给用户带来较好的体验。
运做过程分为
初始标记:标记GC ROOTS可以关联到的对象
并发标记:进行GC ROOTS Tracing 的过程
从新标记:为了修正并发标记期间,因用户程序继续运做而致使标记产生变更的哪一部分的对象标记记录,这阶段的停顿时间比初始标记稍长,但远低于并发标记。
并发清除:
初始标记和从新标记仍须要STOP THE WORLD,整个过程并发标记以及并发处理过程收集器程序和用户程序能够一块儿工做。整体上讲内存回收和用户线程能够并发执行。
优势:并发收集,低停顿
缺点:
一、CMS收集器对CPU资源十分敏感,
二、没法处理浮动垃圾
三、收集结束会产生大量的空间碎片
GI收集器
是一款面向服务端的垃圾收集器
优势:
一、并行与并发
二、分代收集
三、空间整合
四、可预测的停顿
使用G1收集器,JAVA堆的内存布局就与其余的不一样。它将JAVA堆划分为多个大小相等的独立区域(Region),虽然仍是分了新生代和老年代,可是新生代和老年代再也不物理隔离,都是一部分不须要连续的Region的集合。
运做大体划分为
自动内存管理最终归结为自动化解决两个问题:
一、对象优先在Eden上分配
大多数状况下,对象分配到新生代的Eden中,当Eden没有足够的空间时,虚拟机会发起一次MinorGC。
二、大对象直接进入老年代
大对象是指,须要大量连续的内存空间,典型的大对象就是很长的字符串数组或者Char数组。
常常出现大对象就会致使内存还有很多空间时就要提早触发垃圾收集来获取足够的连续内存空间。
三、长期存活的对象直接进入老年代
虚拟机给每个对象定义了一个对象年龄计数器,对象在Eden出生通过第一次的MinorGC,而且能被Survivor容纳,将被移动到Survivor空间,对象年龄设为1,对象在Survivor区中每熬过一次,年龄就增长1,年龄增长到必定程度(默认15),就会被晋升到老年区中。对象晋升到老年区的阈值,能够经过参数设置-XX:MaxTenuringThreshold。
四、动态对象年龄判断
为了能更好的适应不一样程序的内存状况,虚拟机并非要等到年龄必须达到了MaxTeuringThreshlod才会进入老年代。若是在Survivor空间中相同年龄的对象大小总和大于Surivivor空间的一半,年龄大于或等于该对象的直接能够进入老年代
五、空间分配担保
在发生Minor GC以前,JVM会检查老年代最大的连续空间内存是否大于新生代全部对象总空间。若是条件成立,那么Minor GC能够确保是安全的。若是不成立,则JVM会查看HandlePromotiomFailure设置的值是否容许担保失败。容许则会检查老年代连续可用的内存空间是否大于历次晋升到老年代的对象大小的平均值,若是大于则尝试进行一次Minor GC,尽管仍是可能有风险。若是小于或者不容许担保,则改成进行一次Full GC。
当大量对象在Minor GC后还存活着,就须要老年代进行分配担保,前提是老年代自己有容纳这些对象的空间。一共多少对象存活在内存回收之间是不清楚地,只要取之前每一次晋升到老年代对象容量的平均值做为经验值,与老年代剩余的空间进行比较,决定是否执行Full GC让老年代腾出更多的空间。若是某次Minor GC存活后的对象忽然增长,远远高于平均值,只好再进行一次Full GC,避免Full GC执行过于频繁。
(在JDK6后就再也不使用HandlePromotiomFailure,只要老年代的连续空间大于新生代总大小或者历次晋升的平均大小就会进行Minor GC,不然进行Full GC)