1.对象存活断定算法java
概念:引用的四种类型算法
- 强引用(StrongReference)
- 具备强引用的对象不会被GC;
- 即使内存空间不足,JVM宁愿抛出
OutOfMemoryError
使程序异常终止,也不会随意回收具备强引用的对象。- 软引用(SoftReference)
- 只具备软引用的对象,会在内存空间不足的时候被GC;
- 软引用经常使用来实现内存敏感的高速缓存。
- 弱引用(WeakReference)
- 只被弱引用关联的对象,不管当前内存是否足够都会被GC;
- 强度比软引用更弱,经常使用于描述非必需对象。
- 虚引用(PhantomReference)
- 仅持有虚引用的对象,在任什么时候候均可能被GC;
- 经常使用于跟踪对象被GC回收的活动;
- 必须和引用队列 (ReferenceQueue)联合使用,当垃圾回收器准备回收一个对象时,若是发现它还有虚引用,就会在回收对象的内存以前,把这个虚引用加入到与之关联的引用队列中。
a.引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任什么时候刻计数器为0的对象就是不可能再被使用的。数组
然而在主流的Java虚拟机里未选用引用计数算法来管理内存,主要缘由是它难以解决对象之间相互循环引用的问题,因此出现了另外一种对象存活断定算法。缓存
b.可达性分析法:经过一系列被称为『GC Roots』的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证实此对象是不可用的。安全
可做为GC Roots的对象:数据结构
- 虚拟机栈中引用的对象,主要是指栈帧中的本地变量
- 本地方法栈中Native方法引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
须要注意的是,在可达性分析算法中被断定不可达的对象还未真的判『死刑』,GC还会在执行finalize()
方法的对象中进行一次筛选,若是对象能在finalize()
中从新与引用链上的任何一个对象创建关联,将被移除出“即将回收”的集合。并发
引申:有关方法区的GC,可分红两部分post
- 废弃常量与回收Java堆中的对象的GC很相似,即在任何地方都未被引用的常量会被GC。
- 无用的类需知足如下三个条件才会被GC:
- 该类全部的实例都已被回收,即Java堆中不存在该类的任何实例;
- 加载该类的
ClassLoader
已经被回收;- 该类对应的java.lang.Class对象没在任何地方被引用,即没法在任何地方经过反射访问该类的方法。
2.垃圾收集算法性能
上一节介绍了JVM会回收哪些对象,接下来介绍JVM会如何回收掉这些对象。线程
a.分代收集算法
接下来依次介绍以上说起的另外三种算法。
b.复制算法
c.标记-清除算法
d.标记-整理算法
3.HotSpot算法实现&垃圾回收器
接下来介绍如何在HotSpot虚拟机上实现对象存活断定算法和垃圾收集算法,并保证虚拟机高效运行。
a.枚举根节点
主流Java虚拟机使用的都是准确式GC,在执行系统停顿以后无需检查全部执行上下文和全局的引用位置,而是经过一些办法直接获取到存放对象引用的地方,在HotSpot中是经过一组称为OopMap的数据结构来实现的,完成类加载后会计算出对象某偏移量上某类型数据、JIT编译时会在特定的位置记录栈和寄存器中是引用的位置。这样GC在扫描时就可直接得知这些信息,并快速准确地完成GC Roots的枚举。
b.安全点(Sefepoint)
上述“特定的位置”被称为安全点,即程序执行时并不是在全部地方都停顿执行GC,只在到达安全点时才暂停,下降GC的空间成本。
c.安全区域(Safe Region)
安全点机制只能保证程序执行时,在不太长的时间内遇到可进入GC的安全点,但在程序不执行时(如线程处于Sleep或Blocked状态)线程没法响应JVM的中断请求,此时就须要安全区域来解决。
到此只是简单介绍了HotSpot如何发起内存回收,而具体的回收动做是由虚拟机所采用的GC收集器决定的,一般虚拟机中每每不止有一种GC收集器,下图展现的是HotSpot虚拟机中存在的七种做用于不一样分代(新生代、老年代)的收集器,其中被连线的两个收集器表示能够搭配使用。
如下是对比图,来源于文章JVM(HotSpot) 垃圾收集器
并行(Parallel):多条垃圾收集线程并行工做,而用户线程仍处于等待状态。 并发(Concurrent):垃圾收集线程与用户线程一段时间内同时工做,用户程序在继续运行,而垃圾收集程序运行于另外一个CPU上。
5.内存分配与回收策略
对象的内存分配广义上是指在堆上分配,主要是在新生代的Eden区上,若是启动了TLAB,将按线程优先在TLAB上分配,少数状况下也可能会分配在老年代中。分配细节仍是取决于所使用的GC收集器组合以及虚拟机中与内存相关的参数的设置。如下介绍几条广泛的内存分配规则。
新生代GC(Minor GC):发生在新生代的垃圾收集动做。较频繁、回收速度也较快。 老年代GC(Major GC/Full GC):发生在老年代的垃圾收集动做。出现Major GC常常会伴随至少一次的Minor GC。速度通常比Minor GC慢10倍以上。
大对象直接进入老年代:对于须要大量连续内存空间的Java对象(如很长的字符串以及数组),若是大于虚拟机设定的-XX:PretenureSizeThreshold
参数值将直接在老年代分配。这样作的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制。
长期存活的对象将进入老年代:虚拟机会给每一个对象定义一个年龄计数器,当对象在Eden出生并通过第一次Minor GC后仍存活且能被Survivor容纳的话,将被移动到Survivor空间中并将对象年龄设为1;当对象在Survivor区中每“熬过”一次Minor GC年龄就+1,直至增长到必定程度(默认为15岁,可经过-XX: MaxTenuringThreshold
设置)就会被晋升到老年代中。
动态对象年龄断定:为了能更好地适应不一样程序的内存情况,虚拟机并不要求必定要达到-XX: MaxTenuringThreshold
设置值才能晋升到老年代,当Survivor空间中相同年龄全部对象大小的总和大于Survivor空间的一半,那么年龄大于或等于该年龄的对象能够直接进入老年代。
空间分配担保:在发生Minor GC以前虚拟机会先检查老年代最大可用的连续空间是否大于新生代全部对象总空间,如果,说明可确保Minor GC是安全的,反之虚拟机会查看-XX:HandlePromotionFailure
设置值是否容许担保失败;若容许,会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小;若大于,将尝试进行一次Minor GC,若小于或者不容许担保失败,将改成进行一次Full GC。
解释:当大量对象在MinorGC后仍然存活的状况时,须要借助老年代进行分配担保,把Survivor没法容纳的对象直接进入老年代,但前提是老年代自己还有容纳这些对象的剩余空间,因为在完成内存回收以前没法预知实际存活对象,只好取以前每次回收晋升到老年代对象容量的平均大小值做为经验值,与老年代的剩余空间进行比较,从而决定是否进行Full GC来让老年代腾出更多空间。