一、如何判断对象是否还在存活
主流的Java虚拟机没有使用这种方法管理内存, 因为它很难解决循环依赖
通过一系列的称为”GC? Roots“的对象作为起始点, 从这些节点开始向下搜索, 搜索所走过的路径称为引用链, 当一个对象到GC Roots没有与任何引用链相连时, 则证明该对象是不可用的。
作为GC Roots的对象包括以下几种:虚拟机栈中引用的对象、 方法区中类静态属性引用的对象、方法区中常量引用的对象以及本地方法栈中JNI引用的对象。
二、引用:
如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址, 就称这块内存代表着一个引用。
代码之中普遍存在的, 如Object obj = new Object(), 只要强引用还在, GC就永远不会回收该内容。
描述有用但非必须的对象。 对于软引用关联着的对象, 在系统将要抛出内存异常之前, 会将这些对象列进回收范围进行二次回收。 如果这次回收还没有足够的内存, 才会抛出异常。(SoftReference)
弱引用也用来描述非必须的对象。被若引用关联的对象只能活到下次垃圾回收发生之前。 当垃圾收集器工作时, 无论当前内存是否足够, 都会回收掉只被弱引用关联的对象。
又称为幽灵引用或者幻影引用。 一个对象是否有虚引用的存在, 丝毫不会影响对象的生存时间, 也不能通过虚引用获得对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被垃圾收集器回收时收到一个系统通知。
三、对象标记之后就会回收吗
四、方法区的回收
五、垃圾收集算法
1、标记-清除算法:
首先是标记出所有需要回收的对象, 在标记完成后回收所有被标记的对象。
缺点:
2、复制算法:
描述:将可用内存按容量划分为两块, 每次只是用其中的一块, 当这一块内存用完了, 就将还存活的对象复制到另外一块内存, 然后再把已使用过的内存空间一次性的清理掉。
优点:不用考虑内存碎片
缺点:内存缩小为原来的一半
应用:目前的商业虚拟机都采用这种收集算法来回收新生代,具体如下:
将内存分为一块较大的Eden空间和两块较小的Survivior空间, 每次使用Eden和其中一个Survivior空间。
当回收时, 将Eden区和Survivior中还存活着的对象一次性的复制到另外一块Survivior空间上, 最后清理Eden区和另一块Survivior区。
Hotspot 虚拟机默认Eden和Survivior的大小比例是8:1
当另外一块Survivior空间没有足够的空间存放上一次新生代存活的对象时, 这些对象将直接通过分配担保机制进入老年代。
3、标记-整理算法:
标记之后, 让所有存活的对象都向一端移动, 然后直接清理掉端边界以外的内存。
4、分代收集算法:
当前虚拟机的垃圾收集都采用”分代收集“算法。一般是把Java堆分为新生代和老年代;
新生代: 每次垃圾收集时都会有大批对象死去, 只有少量存活, 该区域采用复制算法。
老年代:对象存活率较高, 而且没有额外空间对她进行分配担保, 就必须使用”标记-清理“或者”标记-整理“算法进行回收。
六、HotSpot 的算法
1、枚举根节点:
GC停顿:
可达性分析要确保在一个一致性的快照中进行, 确保分析过程中引用关系不再变化。 GC进行时, 必须停顿所有的Java执行线程。 Stop the World
如何枚举根节点:
GC Roots主要在全局性的引用, 如常量、类静态属性, 与执行上下文中, 如果逐个检查, 必然消耗大量的时间。
使用OopMap: HotSpot使用一组OopMap来记录对象的引用, 加快GC Roots的枚举。
2、安全点:
背景:? ? ? ? ? 如果每个指令都生成对应的OopMap, 那会需要大量的额外空间, 这样GC的成本将会变得非常高。
解决办法:? ? 只在特定位置记录对象的引用情况, 这些特点的位置我们称之为安全点。
安全点的选定条件:是否具有让程序长时间执行的指令 (原因)
如何保证GC时, 让所有线程都跑到最近的安全点上再停顿下来:
?
GC需要中断线程的时候, 不直接对线程操作, 而是简单的设置一个标志, 而是在执行到安全点时轮训该标志, 如果标志为真就自己中断挂起。
GC发生时, 首先把所有的线程全部中断, 如果发现有线程中断的地方不在安全点上, 就恢复线程, 让他”跑“到安全点上。
现在几乎没有虚拟机采用这种方式来暂停线程以响应GC事件
3、安全区域:
背景: 安全点机制保证了程序执行时, 在不太长时间就会遇到可进入GC的安全点, 如果程序没有执行呢, 比如出于sleep或者blocked状态,
这时候线程无法响应jvm的中断请求, Jvm也显然不太可能等待线程被重新分配CPU时间。
安全区域:在一段代码之中, 引用关系不会发生变化, 在这个区域中任意地方开始GC都是安全的
实现: 当线程执行到安全区域后, 首先会标示自己进入了安全区域, 那样, 当在这段时间内发生GC时, 就不用管这样的线程了,当线程要离开
该区域时, 要检查系统是否已经完成了根节点枚举, 如果没完成, 它就必须等待直到收到可以安全离开安全区域的信号。
七,垃圾收集器
目前新生代垃圾收集器有Serial,ParNew,Parallel Scavenge; 老年代收集器有CMS,Serial Old,Parallel Old;G1这款垃圾收集器既能用于新生代又能用于老年代。
1,Serial收集器:
描述:? ? 单线程收集器;他进行垃圾收集时,必须暂停所有的工作线程, 直到它收集结束;新生代采取复制算法暂停所有用户线程, 老年代采取标记-整理算法暂停所有用户线程。
现状:? ? 目前为止, 依然是虚拟机运行在Client模式下的默认新生代收集器。收集几十兆甚至一两百兆的新生代, 停顿时间完全可以控制在几十毫秒最多一百毫秒以内。
2,ParNew收集器:
描述:? ? 其实就是Serial的多线程版本;使用多线程进行垃圾收集;新生代采取复制算法暂停所有用户线程,老年代采用标记-整理算法暂停所有用户线程。
现状:? ? 许多运行在Server模式下的虚拟机默认的新生代收集器;一个与性能无关的原因是:除了Serial收集器外, 目前只有它能与CMS收集器配合使用。
注:
3,Parallel Scavenge收集器:
描述:? ? Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。所谓吞吐量就是cpu用于运行用户代码的时间与CPU总消耗时间的比值, 即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
参数:? ? 最大垃圾收集停顿时间设置:-XX:MaxGCPauseMillis, 设置值是一个大于0的毫秒数, 收集器将尽可能地保证内存回收花费的时间不超过设定值。
GC停顿时间的缩短是以牺牲吞吐量和新生代空间来换取的,可能会把新生代调小一些, 以使在规定的时间内可以完成垃圾回收; 也可能为了减小停顿时间而增大GC频率。
-XX:+GCTimeRatio:设置一个大于0且小于100的整数值, 也就是垃圾收集时间占总时间的比率,如果设置成x, GC时间的占比就是 1/(1+x)
-XX:+UseAdaptiveSizePolicy: 这是一个开关参数, 打开这个参数后, 不需要手工指定新生代大小,Eden和Survior区的比例,晋升老年代对象年龄等细节参数。 虚拟机会根据当前系统的运行情况动态调整这些参数以提供最合适的停顿时间或者最大吞吐量。
与ParNew收集器的区别:可以设置吞吐量和最大停顿时间; 具有自适应调节策略。
4,Serial Old收集器:
描述:Seria