网站admin密码忘记了怎么办,两个wordpress联通,网络营销方法分析,wordpress表格参考资料#xff1a;深入理解Java虚拟机#xff1a;JVM高级特性与最佳实践#xff08;第3版#xff09;周志明 1、分代回收策略 分代的垃圾回收策略#xff0c;是基于这样一个事实#xff1a;不同的对象的生命周期是不一样的。因此#xff0c;不同生命周期的对象可以采取… 参考资料深入理解Java虚拟机JVM高级特性与最佳实践第3版周志明 1、分代回收策略 分代的垃圾回收策略是基于这样一个事实不同的对象的生命周期是不一样的。因此不同生命周期的对象可以采取不同的收集方式以便提高回收效率。 分代垃圾回收采用分治的思想进行代的划分把不同生命周期放在不同代上不同代采用最适合它的垃圾回收方法进行回收。 1.1 代际划分 1.2 垃圾回收
1.2.1 年轻代新生代
年轻代会划分出Eden区域与两个大小对等的Survivor区域。其比例一般为811这是因为根据统计95%的对象朝生夕死存活时间极短。 当新对象生成并且在Eden申请空间失败时就会触发GC清理Eden中的非存活对象并且把存活的对象移动到Survivor区接下来整理两个Survivor区域。 这种方式不会影响到年老代因为大部分对象都是从Eden区开始的所以Eden区垃圾回收十分频繁需要快速、高效的算法一般采用复制算法。 1.2.2 年轻代收集Minor GC/Young GC 当新对象在Eden区域内存分配失败时就会触发年轻代的垃圾回收称之为“Minor GC”。 每个对象都有一个年龄这个年龄就是指对象经历过Minor GC的次数。 如图一所示对象刚分配到Eden时年龄为“0”当Minor GC被触发时所有存活的对象会被拷贝到其中一个Survivor区域中同时年龄增长“1”最后清除Eden区域非可达对象。 如图二所示当第二次Minor GC被触发时、JVM会通过Mark算法 标记算法找出Eden区域和Survivor1区域存活的对象并将他们拷贝到新的Survivor2区域同时年龄增长加“1”最后清除Eden区域和Survivor1区域非可达对象。 如图三所示当对象年龄足够大年龄通过JVM参数设置并且Minor GC再次发生他就会从Survivor内存区域升级到年老代中。
1.2.3 年老代 年老代是用来存放长时间存活的对象的内存区域。 当对象在新生代中经历了多次垃圾回收仍然存活时它们会被晋升到年老代。 年老代的垃圾回收频率较低并且每次回收的成本较高。
在年老代选择的垃圾回收算法取决于JVM采用的什么垃圾回收器
1.2.4 年老代收集Major GC/Old GC 当Minor GC发生时又有对象从Survivor区域升级到Tenured区域但是Tenured区域没有空间容纳新的对象此时就会触发年老代的垃圾回收。 年老代的垃圾回收算法取决于JVM选择的垃圾回收器。
2、 垃圾收集算法Mark
2.1 标记-清除算法Mark-Sweep
过程
算法分为标记、清除两个过程。 首先标记出所有需要回收的对象/标记存活的对象。 接下来清除未被标记的对象。
标记清除算法是最基础的算法后续收集算法基本上都是以标记-清除算法为基础。
标记-清除算法的主要缺点有两个 执行效率不稳定如果 Java堆中有大量对象并且其中大部分需要回收这时必须进行大量的标记清除动作导致两个过程执行效率随对象数量增加而增大。 空间碎片化严重会产生大量不连续的空间碎片碎片太多分配较大对象会导致没有足够的连续内存从而发生垃圾回收。
标记-清除算法执行过程如图所示 2.2 标记-复制算法Mark-Copy
为了解决标记-清除算法面对大量可回收对象时执行效率低的问题它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
过程 当其中一块内存用完时将存活的对象复制到另一块上面。 然后清除使用过的这块内存。
标记-复制算法的缺点 如果内存大多对象都是存活的这种算法会产生大量的内存复制间的开销。 复制算法的代价就是将可用内存缩小了原来的一半空间浪费较大。
标记-复制算法的执行过程 2.3 标记-整理算法Mark-Compact
针对于年老代对象的存亡特征提出了标记-整理算法其标记过程和“标记-清除”算法一样但是后续不是清除可回收对象而是让所有存活的对象都向内存空间一端移动。 标记-清除算法和标记-整理算法本质差异在于前者是一种非移动式的回收算法而后者是移动的。 优点这种方式避免碎片内存的产生又不需要两块相同的内存 缺点压缩操作需要进行局部对象移动 3、HotSpot算法的实现细节
3.1 根节点枚举 所谓“一致性”就是整个枚举期间不会出现枚举过程中整个根节点集合的对象引用在不断发生变化。 主流Java虚拟机都是使用的准确式垃圾收集。当用户线程停下时不需要一个不漏的检查所有执行上下文和全局引用位置。
在HotSpot的解决方案中 使用一组称为OopMap的数据结构来达到目的。 一旦类加载完成HotSpot就会将对象内什么偏移量上是什么类型的数据计算出来。 在即时编译中也会在特定位置安全点记录栈和寄存器里哪些位置是引用。
这样收集器扫描就可以直接得到这些信息而不需要一个不漏的从方法区等待GC Roots查找。
3.2 安全点 导致OopMap内容变化的指令非常多如果为每个指令生成对应的OopMap将需要大量的额外存储空间。 安全点决定了用户执行时不能在代码指令流的任意位置停下来进行GC必须强制到达安全点才能执行GC。 安全点一般选取在“是否具有长时间执行特征”的地方例如方法调用、循环跳转、异常跳转。
如何让垃圾收集发生时所有线程都跑到最近的安全点然后停顿 抢先式中断在GC发生时系统把用户线程全部中断如果发现有中断的线程不在安全点上就恢复这条线程执行让它直到跑到安全点上中断。现在几乎没有虚拟机采用这种方式 主动式中断需要中断线程时不直接操作线程而是简单的设置一个标志位各个线程执行时会不停的主动询问这个标志。一旦这个标志位为真就在自己最近的安全点上主动中断挂起。
3.3 安全区域
安全点似乎已经完美解决了停顿用户线程让虚拟机进行垃圾回收状态。但是当程序“不执行”时安全点就没有作用。 典型场景用户线程处于Sleep状态/Blocked状态这时线程无法响应虚拟机中断请求不能走到安全的地方去中断挂起自己。 安全区是指确保在某一代码片段中引用关系不会发生变化。因此这个区域任意地方发生垃圾回收都是安全的。 当用户线程执行安全区的代码首先会标记自己已经进入了安全区域这段时间虚拟机发起GC就不会管已声明自己在安全区的线程。 当线程需要离开安全区时它会先检查虚拟机是否完成根节点枚举或者GC过程中其他需要暂停用户线程的阶段。如果完成那么线程就会继续执行否则就会一直等待直到收到离开安全区域的信号为止。
3.4 记忆集与卡表
在分代收集理论中为了解决对象跨代引用所带来的问题垃圾收集器在新生代中建立了名为记忆集的数据结构。 记忆集是一种用于记录从非收集区指向收集区域指针集合的抽象数据结构它用于缩减GC Roots扫描范围。 在垃圾收集的场景中收集器只需要通过记忆集判断出某一块非收集区域是否存在指向收集区域的指针就行了并不需要了解这些跨代指针的全部细节。
设计实现记忆集时可以选择不同的记录粒度来节省记忆集的存储和维护成本 第三种卡精度是用一种“卡表”的方式去实现记忆集也是目前最常用实现记忆集的形式。 HotSpot虚拟机卡表的实现 卡表最简单的形式可以是一个字节数组。 字节数组CARD_TABLE的每个元素都对应着其标识的内存区域中一块特定大小的内存块。 一个卡页中通常存在不止一个对象只要卡页有一个对象存在跨代指针就将对应的卡表数组元素标记为1称“元素变脏”。 在垃圾收集时只需要筛选卡表中变脏的元素就能得到哪些卡页内存包含跨域指针把它们加入GC Roots中扫描。
3.5 写屏障
我们使用记忆集缩减GC Roots扫描的范围问题但是还没解决卡表元素如何维护什么时候变脏谁来让它变脏 什么时候变脏——有其他分代区域的对象引用了本区域对象时对应卡表元素就应该变脏。
HotSpot通过写屏障来维护卡表状态帮助虚拟机跟踪对象引用的变化。 应用写屏障后虚拟机就会为赋值操作生成相应指令一旦收集器在写屏障增加了更新卡表的操作无论更新是不是年老代对新生代对象的引用每次对引用更新就会产生额外开销。但是这个开销远小于扫描这个年老代的开销。 卡表在高并发下还存在“伪共享”的问题。 现代CPU缓存系统是以缓存行为单位存储的 多线程下修改互相独立的变量时这些变量如果共享同一个缓存行就会彼此影响导致性能降低。