当前位置: 首页 > news >正文

(二)学习JVM —— 垃圾回收机制

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

(一)学习JVM ——运行时数据区域

(二)学习JVM —— 垃圾回收机制

(三)学习JVM —— 垃圾回收器

(四)学习JVM —— 内存分配与回收策略

对象何时可被回收?

在Java堆中存放着Java中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是确定这些对象哪些还存活着,哪些已经可回收。

可达性分析

Java采用可达性分析来判定对象是否是存活的,这个算法的基本思路就是通过一系列称为"GC Root"的对象作为起始点,从这些节点向下搜索,走过引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则证明这个对象已经可以回收。

Java中可作为GC Root的对象有四种:

  1. 虚拟机栈中引用的对象;
  2. 方法区中静态属性引用的对象;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中JNI(Native方法)引用的对象;

引用的种类

引用的定义是:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。

Java堆引用概念进行了扩展,分为下述4种:

  • 强引用(Strong Reference):只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象;
  • 软引用(Soft Reference):将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会内存溢出;
  • 弱引用(Weak Reference):弱引用只能生存到下一次垃圾回收发生之前。
  • 虚引用(Phantom Reference):虚引用(幻影引用)唯一的目的就是能在这个对象被回收时收到一个系统通知。

回收前的细节

真正标记一个对象为可回收,至少要经历两次标记过程:

在经历可达性分析后,发现没有与GC Root关联,会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。对没有覆盖或者已经调用过finalize()方法的对象,JVM都不会去执行finalize()方法了。

如果对象有必要执行finalize()方法,那么该对象会被放置到F-Queue队列,并在稍后由一个自动建立的,低优先级的Finalizer线程去执行它。

方法区怎么回收?

JVM的共享内存分为堆和方法区,有很多人认为方法区(或永久代)是没有垃圾回收的,JVM规范也确实说过可以不对方法区进行回收,因为性价比较低,一般在堆中,尤其是新生代中,常规应用进行一次垃圾回收可以回收70%~95%的空间,而方法区则不然。

方法区如果进行垃圾收集,主要回收废弃常量和无用的类。

判定常量是否是废弃的常量,是指进入了常量池,但是系统中没有任何一个地方引用了这个字面量,就可以回收。

判定一个类是否是一个无用的类的条件比较苛刻,有3个条件:

  1. 该类所有的实例已经被回收,堆中不存在该类的任何实例;
  2. 加载该类的ClassLoader已经被回收;
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法;

是否对类进行回收,可以用-Xnoclassgc参数进行控制,还可以在Product版虚拟机中,用-verbose:class以及-XX:+TraceClassLoading查看类加载和卸载信息。在FastDebug版虚拟机中用-XX:+TraceClassUnLoading参数查看。

在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证方法区不会溢出。

对象回收的算法

垃圾回收的常用算法有三种,分别是标记&清除算法、复制算法和标记&整理算法。

标记&清除算法

标记&清除(Mark&Sweep)分为两个阶段,标记和清除。首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,该算法的主要弊端在于,清除之后会产生大量不连续的内存碎片,碎片太多会导致以后在运行过程中需要分配较大对象时,无法找到连续的内存空间,而不得不提前出发另一次垃圾收集。

复制算法

复制(Copying)算法,可以将内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完了,就将还存活着的对象复制到另一块,然后把已经使用过的内存一次清理掉。只是这种做法将内存缩小到了原来的一半,成本高了一点。

根据IBM公司专门研究表明,新生代中有98%的对象时朝升西落的,所以,并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一个Survivor空间。

当回收时,将Eden和Survivor中还存活的对象,一次性的复制到另一块Survivor中,然后清理掉Eden和刚才使用的那个Survivor空间。JVM默认采用8:1:1的方式分配Eden和两个Survivor。

当复制过程中发生Survivor不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion),将这些对象直接分配到老年代。

标记&整理算法

标记&整理(Mark&Compact),标记过程与标记清理一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。 

分代回收算法

当前商业虚拟机的垃圾收集都采用分代收集(Generational Collection),根据对象存活周期的不同将内存划分为几块。一般是把Java堆分成新生代和老年代。

新生代对象存活率低,选用复制算法,将内存以8:1:1分配。

老年代对象存活率高,没有额外空间对它进行分配担保,所以采用标记&清理,或标记&整理算法。

HotSpot虚拟机回收算法的实现

前面从理论层面介绍了对象存活的判断方式和垃圾回收算法,而JVM在实现这些算法时,必须对算法的执行效率有严格的考量,才能保证JVM高效运行。

枚举根节点

JVM在进行可达性分析时,并不会真的从每一个GC Root进行引用链检查,现在很多应用仅仅是方法区就有数百兆,如果要逐一检查,必然会消耗很多时间。

另外,可达性分析还体现在GC停顿上,为了确保分析准备,不可以出现在分析过程中对象引用关系还在不断变化的情况,GC停顿还被Sun公司成为称为Stop The World。

HotSpot实现中,使用一组OopMap的数据结构来帮助快速检查引用链,在类加载完成时,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录栈河寄存器中那些位置是引用。这样,GC在扫描时就可以直接得知这些信息了。

但是可能导致引用关系变化的指令非常多,如果为每一条都生成对应的OopMap,那将需要大量额外的空间,这样GC的成本会变得很高。下面要介绍的安全点则解决了这个问题。

安全点

程序执行时并非在所有位置都可以停下来GC,只有在达到安全点(Safepoint)时才能暂停。

安全点的选定不能太多,也不能太少,它的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准来选定的,长时间执行最明显的特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,具有这些功能的指令才会产生安全点。

另一个要考虑的问题是,如何在GC发生时,让所有线程都跑到最近的安全点停顿下来。

目前,基本上所有的虚拟机都采用主动式中断的思想,当GC需要中断线程的时候,轮询一个标志,发现中断标志为true时,就自己中断挂起,轮询标志的地方河安全点是重合的。

安全区域

程序不执行的时候,就是没有分配CPU时间时,线程无法响应JVM的中断请求,这时就需要安全区域(Safe Regin)来解决。

安全区域是指一段代码片段中,引用关系不会发生变化。在这个区域GC都是安全的。

线程进入安全区域时会进行标记,离开时要检查自己是否完成了根节点枚举,如果完成了就继续工作,否则要等到可以安全离开的信号为止。 

(一)学习JVM ——运行时数据区域

(二)学习JVM —— 垃圾回收机制

(三)学习JVM —— 垃圾回收器

(四)学习JVM —— 内存分配与回收策略

转载于:https://my.oschina.net/u/2450666/blog/1612117

相关文章:

  • 搭建私有CA和证书认证
  • Linux rpm 命令参数使用详解
  • 智能合约开发环境搭建及Hello World合约
  • zookeeper安装部署
  • java B2B2C Springcloud多租户电子商城系统- 分布式事务
  • Shell 脚本 100 例《四》
  • Powershell 批量重命名
  • 浙江台州警方侦破特大制售假酒案 涉案金额超4000万元
  • 《SQL必知必会》读书笔记
  • Unity C#编程优化——枚举
  • 正则表达式知识点汇总
  • 山西政协委员建言探索农业托管模式 解决“谁来种地”问题
  • 发现操作系统的数据库出现死锁如何处理
  • 微服务架构到底应该如何选择?
  • 搭建MySQL高可用负载均衡集群
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • android 一些 utils
  • Hexo+码云+git快速搭建免费的静态Blog
  • HTML中设置input等文本框为不可操作
  • python学习笔记 - ThreadLocal
  • 编写高质量JavaScript代码之并发
  • 动手做个聊天室,前端工程师百无聊赖的人生
  • 每天一个设计模式之命令模式
  • 七牛云假注销小指南
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 设计模式走一遍---观察者模式
  • 微信开源mars源码分析1—上层samples分析
  •  一套莫尔斯电报听写、翻译系统
  • 用quicker-worker.js轻松跑一个大数据遍历
  • Java总结 - String - 这篇请使劲喷我
  • 带你开发类似Pokemon Go的AR游戏
  • 摩拜创始人胡玮炜也彻底离开了,共享单车行业还有未来吗? ...
  • #Java第九次作业--输入输出流和文件操作
  • #Linux(权限管理)
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (poj1.2.1)1970(筛选法模拟)
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (算法设计与分析)第一章算法概述-习题
  • (转)h264中avc和flv数据的解析
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • (转)母版页和相对路径
  • (轉)JSON.stringify 语法实例讲解
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .Net6 Api Swagger配置
  • @font-face 用字体画图标
  • @WebService和@WebMethod注解的用法
  • [ vulhub漏洞复现篇 ] Celery <4.0 Redis未授权访问+Pickle反序列化利用
  • [Angular] 笔记 6:ngStyle
  • [Asp.net mvc]国际化
  • [CISCN 2023 初赛]go_session