当前位置: 首页 > 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高可用负载均衡集群
  • python3.6+scrapy+mysql 爬虫实战
  • Angular 2 DI - IoC DI - 1
  • extjs4学习之配置
  • java2019面试题北京
  • JavaScript类型识别
  • java正则表式的使用
  • MySQL QA
  • Rancher-k8s加速安装文档
  • Theano - 导数
  • vue-cli3搭建项目
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • yii2权限控制rbac之rule详细讲解
  • 给Prometheus造假数据的方法
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 力扣(LeetCode)21
  • 用Visual Studio开发以太坊智能合约
  • 转载:[译] 内容加速黑科技趣谈
  • 组复制官方翻译九、Group Replication Technical Details
  • ​​快速排序(四)——挖坑法,前后指针法与非递归
  • #git 撤消对文件的更改
  • #pragma multi_compile #pragma shader_feature
  • $$$$GB2312-80区位编码表$$$$
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • (2022 CVPR) Unbiased Teacher v2
  • (4) PIVOT 和 UPIVOT 的使用
  • (java)关于Thread的挂起和恢复
  • (第27天)Oracle 数据泵转换分区表
  • (官网安装) 基于CentOS 7安装MangoDB和MangoDB Shell
  • (十五)使用Nexus创建Maven私服
  • (四)模仿学习-完成后台管理页面查询
  • (转)ObjectiveC 深浅拷贝学习
  • *1 计算机基础和操作系统基础及几大协议
  • .NET CLR基本术语
  • .NET I/O 学习笔记:对文件和目录进行解压缩操作
  • .NET Micro Framework初体验(二)
  • .net6+aspose.words导出word并转pdf
  • @angular/cli项目构建--Dynamic.Form
  • [ vulhub漏洞复现篇 ] ECShop 2.x / 3.x SQL注入/远程执行代码漏洞 xianzhi-2017-02-82239600
  • [ vulhub漏洞复现篇 ] Hadoop-yarn-RPC 未授权访问漏洞复现