GC作为Java知识体系里的一个面试热点 经常是众多程序猿常常需要复习的内容 借助这次活动 特将我总结的GC知识点分享给大家 大家共同进步
GC 垃圾回收虚拟机栈是线程独占的 也就是说随着线程初始而初始 消亡而消亡 当线程被销毁后 虚拟机栈上的内存自然会被回收 即虚拟机栈上的内存空间不在GC范围。
GC的主要作用是回收程序中 主要是堆中 不再使用的内存。对对象而言 如果没有任何变量去引用它 那么该对象就不可能被程序访问 因此可以认为它是垃圾信息 可以被回收。垃圾回收器使用有向图来记录和管理堆内存中的所有对象 通过该有向图可以识别哪些变量是可达的 哪些是不可达的 没有引用变量引用即为不可达的 所有不可达的均要被回收。
检查对象是否存活引用计数法 引用计数作为一种简单但是效率较低的方法 其实现原理如下 在堆中每个对象都有一个引用计数器 当对象被引用过时 引用计数器加1 当引用失效时减1 由于这种方法无法解决相互引用的问题 因此JVM没有采用这个算法。分析下面这段代码使用引用计数法可能出现的问题。当采用引用计数算法时:
第一步 GcObject实例1被obj1引用 所以它的引用数加1 为1 第二步 GcObject实例2被obj2引用 所以它的引用数加1 为1 第三步 obj1的instance属性指向obj2 而obj2指向GcObject实例2 故GcObject实例2引用加1 为2 第四步 obj2的instance属性指向obj1 而obj1指向GcOjbect实例1 故GcObject实例1引用加1 为2 到此前4步 GcOjbect实例1和GcOjbect实例2的引用数量均为2 此时结果图如下 第五步 obj1不再指向GcOjbect实例1 其引用计数减1 结果为1 第六步 obj2不再指向GcOjbect实例2 其引用计数减1 结果为1。到此 可以发现GcObject实例1和实例2的计数引用都不为0 如果采用引用计数算法的话 这两个实例所占的内存将得不到释放 这便产生了内存泄露。
GC主要对堆中的对象进行回收 方法区、栈不被GC所管理 因而选择这些区域内的对象作为GCroots。GC会收集那些不是GC roots且没有被GC roots引用的对象。
常用的垃圾回收算法标记清除 利用JVM维护的对象引用图 从根节点开始遍历对象的引用图 同时标记遍历到的对象 当遍历结束时 未被标记的对象就是目前已不被使用的对象 可以被回收。如下例子假设运行到step3后运行step4之前 进行了一次垃圾回收。首先 找出所有的根对象 标志其是否可回收 这个是通过将同步块索引的一位设为0来完成的 这个阶段为标记阶段。在运行到step3后 obj1和obj2和obj3就是这里的3个根 其中根obj2在栈中已经被pop出去了 因此栈中只有两个根对象obj1和obj3 如图所示
因为根obj1均引用了object对象1 所以object对象1的同步块索引的一位被置为1 以标记其不是垃圾 然后检查根obj3的对象引用情况 发现它也引用了object对象1 当它刚要标志其同步索引块的一位时 发现object对象1已经被标记了 则不重新标记它。需要注意的是 在标记object对象1时 发现它引用了其他的对象 假设为a 那么对象a也对被标记。标记过程会持续 依次检查完所有的根。若对象在进行可达性分析后发现没有与GC roots相连接的引用链 那么它将会被第一次标记并进行一次筛选 筛选的条件是该对象是否有必要执行finalize()方法 当对象没有重写finalize()方法或者finalize()方法已经被虚拟机调用过 虚拟机将这两种情况都视为没必要执行。若该对象被判定为有必要执行finalize方法 则这个对象会被放在一个F-Queue队列 finalize方法是对象逃脱死亡命运的最后一次机会 稍后GC将对F-queue中的对象进行第二次小规模的标记 若对象要在finalize中成功拯救自己 只要重新与引用链上的任何一个对象建立关联即可 那么在第二次标记时他们将会被移出即将回收集合。
如果在被标记后直接对对象进行清除 会带来另一个新的问题——内存碎片化。如果下次有比较大的对象实例需要在堆上分配较大的内存空间时 可能会出现无法找到足够的连续内存而不得不再次触发垃圾回收。
分代收集
新生代 复制回收算法 老年代 标记整理或标记清除。这里讨论的虚拟机是基于Jdk1.7之后的HotSpot虚拟机。下图展示了jdk7中作用于不同分代的收集器 如果两个收集器之间存在连线就代表它们可以搭配使用。
新生代垃圾收集器serial收集器 单线程收集器 单线程的意义不仅仅说明它只会使用一个cpu或者一条垃圾收集线程去完成垃圾收集工作 更重要的是在它进行垃圾收集时 必须暂停其他所有工作线程 Stop The World 直到它收集结束。Stop The World这项工作实际上是由虚拟机在后台自动发起和自动完成的 在用户不可见的情况下把用户正常工作的线程全部停掉。下图是Serial收集器运行示意图。直到现在Serial仍然是虚拟机运行在client模式下默认新生代收集器 它简单而高效 单个CPU环境下没有线程交互的开销 采用复制回收算法。浮动垃圾 并发清理阶段用户线程还在运行 这段时间就可能产生新的垃圾 新的垃圾在此次GC无法清除 只能等到下次清理。
优点
并行与并发 G1能充分利用多CPU、多核环境下的硬件优势 使用多个CPU来缩短Stop-The-World停顿的时间 部分其他收集器原本需要停顿Java线程执行的GC动作 G1收集器仍然可以通过并发的方式让Java程序继续执行。分代收集 与其他收集器一样 分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆 但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、 熬过多次GC的旧对象以获取更好的收集效果。空间整合 与CMS的标记清理算法不同 G1从整体来看是基于标记整理算法实现的收集器 从局部 两个Region之间 上来看是基于复制算法实现的 但无论如何 这两种算法都意味着G1运作期间不会产生内存空间碎片 收集后能提供规整的可用内存。 这种特性有利于程序长时间运行 分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。可预测的停顿 这是G1相对于CMS的另一大优势 降低停顿时间是G1和CMS共同的关注点 但G1除了追求低停顿外 还能建立可预测的停顿时间模型 能让使用者明确指定在一个长度为M毫秒的时间片段内 消耗在垃圾收集上的时间不得超过N毫秒。在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代 而G1不再是这样。使用G1收集器时 Java堆的内存布局就与其他收集器有很大差别 它将整个Java堆划分为多个大小相等的独立区域 Region 虽然还保留有新生代和老年代的概念 但新生代和老年代不再是物理隔离的了 它们都是一部分Region 不需要连续 的集合。
G1收集器的运作大致可划分为以下几个步骤 初始标记、并发标记、最终标记、筛选回收 首先对各个Region的回收价值和成本进行排序 根据用户所期望的GC停顿时间来制定回收计划 。
每个region的大小都是2的倍数 通过设置堆的大小和region的个数 默认2048 计算得出,每个region可能属于eden 也可能属于old 且每类区域空间是不连续的 这种将O区划分成多块的理念源于 当并发后台线程寻找可回收的对象时、有些region区包含可回收的对象要比其他区块多很多。虽然在清理这些区块时G1仍然需要暂停应用线程、但可以用相对较少的时间优先回收包含垃圾最多区块。这也是为什么G1命名为Garbage First的原因 第一时间处理垃圾最多的区块。
G1中提供了三种模式垃圾回收模式 young gc、mixed gc 和 full gc 在不同的条件下被触发。
在cms中 如果添加了以下参数
-XX:CMSInitiatingOccupancyFraction 80 -XX: UseCMSInitiatingOccupancyOnly
当老年代的使用率达到80%时 就会触发一次cms gc。相对的 mixed gc中也有一个阈值参数 -XX:InitiatingHeapOccupancyPercent 当老年代大小占整个堆大小百分比达到该阈值时 会触发一次mixed gc。mixed gc的执行过程有点类似cms 主要分为以下几个步骤
对象主要分配在新生代的Eden区上 如果启动了本地线程分配缓冲 将按线程优先在TLAB上分配 少数情况下也可能直接分配在老年代中 分配的规则并不是百分之百固定的 其细节取决于当前使用的是哪一种垃圾收集器组合还有虚拟机中与内存相关参数的设置。下面是最普遍的内存分配规则
?-XX PrintGCDetails?? 告诉虚拟机在发生垃圾收集行为时打印内存回收日志 并且在进程退出的时候输出当前的内存各区域分配情况。
大对象直接进入老年代 大对象是指需要大量连续内存空间的java对象 最典型的大对象就是那种很长的字符串以及数组 大对象对虚拟机的内存分配来说就是一个坏消息 尤其是朝生夕灭的短命大对象 写程序的时候应当避免 经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来存放它们。?-XX PretenureSizeThreshold? 令大于这个设置值的对象直接在老年代分配 这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制
在发生Minor GC之前 虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间 如果这个条件成立 那么Minor GC可以确保是安全的。 如果不成立 则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。 如果允许 那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小 如果大于 将尝试着进行一次Minor GC 尽管这次Minor GC是有风险的 如果小于 或者HandlePromotionFailure设置不允许冒险 那这时也要改为进行一次Full GC。
内存泄露内存泄漏大家都不陌生了 简单粗俗的讲 就是该被释放的对象没有释放 一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。内存泄露是指一个不再被程序使用的对象或者变量还在内存中占有存储空间。一般来说 内存泄露主要有两种情况 一是堆中申请的空间没有被释放 二是对象已不再被使用 但仍然在内存中保留着。垃圾回收机制的引入可以有效的解决第一种情况 而对于第二种情况 垃圾回收机制无法保证不再使用的对象会被释放。Java中引起内存泄露的原因主要有以下几个方面的内容
静态集合类 例如HashMap和Vector 如果这些容器是静态的 由于它们的生命周期与程序一致 那么容器中的对象在结束之前将不会被释放 从而造成内存泄露。如下例 各种连接 比如数据库连接、网络连接以及IO连接 若不关闭相应的连接 会造成内存泄露。监听器 通常一个应用中会用到多个监听器 但在释放对象的同时往往没有相应的删除监听器 从而造成内存泄露。变量不合理的作用域 变量定义的作用范围大于其使用范围 很有可能造成内存泄露。单例模式 若单例模式中包含对对象的引用。由于单例对象以静态变量的方式存储 因此它在JVM的整个生命周期中都存在 若它含有对其他对象的引用 则会造成其他对象的类不能被回收。
minor GC、major GC、full GC区别新生代GC Minor GC 指发生在新生代的垃圾收集动作 因为Java对象大多都具备朝生夕灭的特性 所以Minor GC非常频繁 一般回收速度也比较快。
老年代 GC Major GC 指发生在老年代的GC 。MajorGC的速度一般会比Minor GC慢10倍以上。
Full GC是针对整个堆来说的 出现full gc的时候经常伴随着至少一次的minor gc 但并非绝对的
堆内存划分为Eden、Survivor和Tenured/Old 空间。虚拟机给每个对象定义了一个对象年龄计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活 并且能被 Survivor容纳的话 将被移动到Survivor空间中 并将对象年龄设为 1。对象在Survivor区中每熬过一次Minor GC 年龄就增加 1 岁 当它的年龄增加到一定程度 默认为15岁 时 就会被晋升到老年代中。
商业虚拟机 将内存分为一块较大的eden空间和两块较小的survivor空间 默认比例是8:1:1 即每次新生代中可用内存空间为整个新生代容量的90% 每次使用eden和其中一个survivour。当回收时 将eden和survivor中还存活的对象一次性复制到另外一块survivor上 最后清理掉eden和刚才用过的 survivor 若另外一块survivor空间没有足够内存空间存放上次新生代收集下来的存活对象时 这些对象将直接通过分配担保机制进入老年代。
触发full gc的情况
System.gc()方法的调用 此方法会建议jvm进行full gc 老年代空间不足 老年代空间只有在新生代对象转入或创建大对象、大数组时才会出现不足的现象 当执行Full GC后空间仍然不足 则抛出如下错误 ?java.lang.OutOfMemoryError: Java heap space? 为避免以上两种状况引起的Full GC 调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组 永久代空间不足 当系统中要加载的类、反射的类和调用的方法较多时 Permanet Generation可能会被占满 在未配置为采用CMS GC的情况下也会执行Full GC。如果经过Full GC仍然回收不了 那么JVM会抛出如下错误信息 ?java.lang.OutOfMemoryError: PermGen space?。为避免Perm Gen占满造成Full GC现象 可采用的方法为增大Perm Gen空间或转为使用CMS GC 统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间 Full gc还会回收方法区和堆外内存。jstat通常用来分析系统的垃圾回收情况。
jstat -gcutil pid 2000 每隔2秒输出一次结果
其中S0、S1 代表两个Survivor区 E 代表 Eden 区 O Old 代表老年代 P Permanent 代表永久代 YGC Young GC 代表Minor GC YGCT代表Minor GC耗时 FGC Full GC 代表Full GC耗时 GCT代表Minor Full GC共计耗时。
在Python语言中有如下3种方法: 成员方法 类方法(classmethod) 静态方法(staticm...
Docker生成新镜像版本的两种方式 There are two ways Docker can generate new m...
本文整理自直播《Hologres 数据导入/导出实践-王华峰(继儒)》 视频链接: https:/...
前提条件 请您在购买前确保已完成注册和充值。详细操作请参见 如何注册公有云管...
信息化2.0时代提出开展智慧教育创新发展行动。2019年2月,中共中央、国务院印发...
从 10.0.0 版开始,异步迭代器就出现在 Node 中了,在本文中,我们将讨论异步迭...
摘要 元旦期间 订单业务线 告知 推送系统 无法正常收发消息,作为推送系统维护者...
建站 什么 虚拟主机 够用?这要看搭建的是什么类型的网站。比如个人博客类型的网...
【51CTO.com快译】 数据可视化工具不断发展,提供更强大的功能,同时改善可访问...
2021年3月24日,主题为《数据的世界,世界的数据》的星环科技2021春季新品发布会...