JVM的GC机制
JVM的GC机制
1. 什么对象会被回收
引用计数法:如果一个对象被引用一次,则记录引用次数加一,如果引用取消,则减一,当减到0时,需要被回收。
问题:循环引用,A引用B,B引用A,除此之外,已经无法访问他们。
可达性分析算法:从GC根开始,找到GC根直接或间接引用的对象并标记,没有标记的便是需要回收的。
2. 什么可以作为GC ROOT
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 同步锁持有的变量
- 跨代引用的对象
3. 垃圾收集算法
3.1 三个假说
- 大部分对象是朝生夕灭,存活时间很短。
- 越多次数跨过垃圾回收的对象越难被回收。
- 跨代引用很少。
3.2 堆区域划分
- 新生代:新创建的对象和跨过垃圾回收次数小于某个值的对象在这个区域。
- 老年代:跨过垃圾回收次数大于某个值的对象在这个区域,默认是15。
- 有些收集器不使用经典分代,而将内存分为等大的Region。
3.3 三个标记算法
标记-清除算法:从根开始遍历,将遍历到的对象做标记,没有做标记的被清除。
问题:清除之后存在内存碎片,严重影响内存利用率。
标记-复制算法:将内存按照比例分开,一部分闲置称A,另一部分存放对象称B。一次标记结束后,将存活的对象复制到闲置区域A,将B清空。
- 大多新生代收集器使用此技术。
- Appel式回收:将新生代分为一个Eden区和两个Survior区,比例为8:1,分配内存只分配到Eden和一个Survior。HotSpot使用此技术.
- 逃生门:当Survior放不下Eden中的存活对象,将这些对象直接放到老年代。
问题:浪费空间、当大量对存活时复制浪费的时间长(故老年代不使用该方法)。
标记-整理算法:将标记存活着的对象朝内存空间的一段移动,清除掉边界之外的对象。
问题:需要更新引用,操作复杂。
3.4 重要的算法
3.4.1 GC Roots枚举
GC Roots大部分存在于栈帧的局部变量表中,而局部变量表中可能存放着一些基本数据类型和引用类型,如果要遍历全部来找出引用类型来作为GC Roots的话,效率过低。
当前主流的虚拟机都是准确式垃圾收集,也就是说,虚拟机可以直接知道栈中的变量是基本数据类型还是引用数据类型。
譬如内存中有一个32bit的整数123456,虚拟机可以直接判断出他是一个整数123456,还是它是指向123456的内存地址。
HotSpot是使用OopMap来解决这个问题,在即时编译的过程中,会在特定的位置记录下栈里的哪些地方是引用。在GC Roots枚举的时候,查找OopMap便能快速找到GC Roots了。
3.4.2 安全点和安全区域
OopMap记录着引用的信息,如果每执行一次语句就更新一次OopMap,则会导致效率低下,所以虚拟机使用了一个叫安全点的技术,安全点中所有线程都将挂起,来方便OopMap更新和Gc Roots枚举,在安全点之外不会更新OopMap,OopMap累积到安全点再一次性更新。
如何确定安全点?
虚拟机以“是否具有让程序长时间执行的特征”为标准选择安全点,准确的说就是:1. 方法调用 2. 循环跳转 3. 异常跳转。
如何让所有线程都跑到安全点挂起?
抢先式中断:
虚拟机要垃圾回收的时候将全部线程中断,如果有线程不在安全点上,则让这个线程执行,让他也走到安全点。(如果这个时间实例化了个超大对象怎么办?)
主动式中断:
虚拟机不会主动中断线程,而是设置一个标志位,所有线程执行过程中不断轮询这个标志位,如果这个标志为真就在最近的安全点挂起,标志位和安全点是重合的,并且加上所有要创建对象和分配内存的地方(好像能解决上面这个问题了)。
安全区域
有些线程Sleep,他们不能走到安全点挂起,其他线程也不能等他们醒来,所以设置安全区域,在安全区域内所有引用关系将不会变化,安全区域就是拉长的安全点。
3.4.3 记忆集和卡表
上面提到GC Roots包含着跨代引用的对象,如果要搜集新生代,如何找到跨代引用的对象,莫非要遍历整个老年代?这个问题通过记忆集的技术来解决。
记忆集记录着从非收集区指向收集区域的指针的集合的数据结构。在垃圾回收的时候,便能通过记忆集来找出可以作为GC Root的跨代引用的对象。
卡表:
记忆集有精读之分,有些能精确到具体地址,有些只能精确到一块内存区域,HotSpot就是精确到一块内存区域,这种记忆集称为卡表。HotSpot的卡表是个数组,数组中的每个元素对应一个内存块,称为卡页,如果一个卡页的值是1,则说明该内存块中包含着跨代指针,将他们加入GC Roots中一起扫描。
3.4.4 写屏障
写屏障可以解决何时更新卡表的问题,写屏障简单而言是个AOP切面,当更新了引用时,会通过写屏障自动更新卡表信息。
3.4.5 三色标记
GC Roots的枚举会暂停所有线程,而现在的许多收集器在标记过程中是不需要暂停线程的,可是并发标记会带来漏标和错标,一旦错标,将会导致程序正在使用对象被回收,导致程序崩溃,为解决此问题,引入三色标记来解决。
- 黑色:该对象已经被标记过了,且该对象下的属性也全部都被标记过了。(程序所需要的对象)
- 灰色:该对象已经被标记过了,但该对象下的属性没有全被标记完。(GC需要从此对象中去寻找垃圾)
- 白色:该对象没有被标记过。(对象垃圾)
模拟漏标
当扫描结束后,所有非垃圾节点都变成了黑色,这时如果某个引用取消,则被引用的成垃圾,可仍然是黑色,属于漏标。
判定错标的两个条件
赋值器插入了一条或者多条从黑色对象到白色对象的新引用。
赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
为何需要条件1?
- 如果不是插入黑色到白色而是插入灰色到白色,这样下一轮扫描就会扫描灰色,必定会把新插入的白色对象也标记上。
- 如果不是插入黑色到白色而是插入白色1到白色2,分两种情况:
- 假设白色1不是垃圾,则它迟早会被标记,那么白色2也会被标记。
- 假设白色1是垃圾,那如何找到白色1?假设不存在。
为何需要条件2?
- 如果不是删除灰色到白色,而是删除黑色到白色,此假设不存在,黑色后面都是灰色。
- 如果不是删除灰色到白色,而是删除白色到白色,分两种情况:
- 假设白色1不是垃圾,那么所有灰色对象必会有一个间接或直接引用他。
- 假设白色1是垃圾,那如何找到白色1?假设不存在。
解决错标
增量更新:为了打破条件1。当赋值器插入了一条或者多条从黑色对象到白色对象的新引用时,将黑色对象变成灰色。
原始快照:为了打破条件2。赋值器删除了全部从灰色对象到该白色对象的直接或间接引用时,将改变前的引用关系快照保存,待并发扫描结束后,在扫描一遍改变前的快照(如果只满足了条件2,不满足条件1,这样做是不是会有可能产生浮动垃圾)。
4. 一些经典的垃圾收集器
4.1 CMS收集器
用于老年代的收集器。
运作过程:
- 初始标记
- 并发标记
- 重新标记
- 并发清除
过程详解
- 初始标记:GC Roots枚举,需要停顿所有线程,由于OopMap,速度很快。
- 并发标记:从GC Roots开始,遍历所有关联到的对象,无需停顿,于用户线程并发执行,使用三色标记算法,对于引用关系的改变,采取增量更新的方法解决。
- 重新标记:将修正增量更新的改变修正。
- 并发清除:清除垃圾。
缺点
- 并发标记是和用户线程一起执行,会占用处理器,导致应用程序变慢。
- 使用的是标记-清除算法,会产生内存碎片。
- 会产生浮动垃圾,浮动垃圾过多,将导致Full GC的出现。
4.2 G1收集器
G1收集器面向整个堆内存进行回收,衡量标准不是分代,而是将堆内存分为等大的Region,哪个Region的回收价值高,回收那个Region。
5. 垃圾回收的时机
- Eden区满了,会进行一次新生代的收集。
- 新生代垃圾回收前,判断老年代的连续空间 < Eden每次收集后存活对象的平均值,进行老年代收集。
- 使用CMS收集器时,老年代的空间被占用了92%,进行老年代收集。
- 新生代垃圾回收后,存活的对象 > 老年代空间,进行老年代收集。
6. 内存分配策略
- 新创建的对象分配在Eden区。
- 新创建的大对象直接分配在老年代(所以要避免创建短命大对象)。
- 长期存活的对象进入老年代。
- 新生代收集后,Survior放不下,直接进入老年代(所以要适当调整Eden和Survior的比例,来确保朝生夕灭的对象每次收集都能在Survior中放下)。
- 动态年龄判定,相同年龄的对象所占用的Survior空间大于Survior的一半,所有等于或大于这个年龄的对象都进入老年代。
JVM的GC机制的更多相关文章
- JVM的 GC机制和内存管理
GC机制:java垃圾回收机制,垃圾收集器线程(Garbage Collection Thread)在 JVM 处于空闲循环式,会自动回收无用的内存块. 垃圾收集算法:1.引用计数 2.根搜索 3 ...
- JVM的GC机制及JVM的调优方法
内存管理和垃圾回收是JVM非常关键的点,对Java性能的剖析而言,了解内存管理和垃圾回收的基本策略非常重要. 1.在程序运行过程当中,会创建大量的对象,这些对象,大部分是短周期的对象,小部分是长周期的 ...
- 关于JVM的GC机制
GC优点: 1.提高生产率,不用逐行检查内存是否释放. 2.Java安全策略的一部分,不会使用户错误释放内存而导致JVM崩溃. GC算法基本两点: 1.检测出垃圾对象. 2.回收垃圾对象,释放相应堆空 ...
- 深入JVM系列(二)之GC机制、收集器与GC调优
一.回想JVM内存分配 须要了解很多其它内存模式与内存分配的,请看 深入JVM系列(一)之内存模型与内存分配 1.1.内存分配: 1.对象优先在EDEN分配 2.大对象直接进入老年代 3.长期存活的 ...
- 深入JVM系列(二)之GC机制、收集器与GC调优(转)
一.回顾JVM内存分配 需要了解更多内存模式与内存分配的,请看 深入JVM系列(一)之内存模型与内存分配 1.1.内存分配: 1.对象优先在EDEN分配2.大对象直接进入老年代 3.长期存活的对象 ...
- JVM GC机制
垃圾收集主要是针对堆和方法区进行. 回收机制: 现在的JVM基本都使用分代回收机制,把堆中内存区域分为新生代,老年代. 新生代: Eden(80%) Survivor0(10%) Survivor1( ...
- JVM内存管理及GC机制
一.概述 Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露 ...
- JVM GC 机制与性能优化
目录(?)[+] 1 背景介绍 与C/C++相比,JAVA并不要求我们去人为编写代码进行内存回收和垃圾清理.JAVA提供了垃圾回收器(garbage collector)来自动检测对象的作用域),可自 ...
- JVM内存模型及GC机制
一.JVM简介 1.1什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各 ...
随机推荐
- hugo + nginx 搭建博客记录
作为一个萌新Gopher,经常逛网站能看到那种极简的博客,引入眼帘的不是花里胡哨的图片和样式,而是黑白搭配,简简单单的文章标题,这种风格很吸引我.正好看到煎鱼佬也在用这种风格的博客,于是卸载了我的wo ...
- C# 8.0和.NET Core 3.0高级编程 分享笔记二:编程基础第一部分
基础部分被我分为了2篇,因为实在太多了,但是每一个知识点我都不舍得删除,所以越写越多,这一篇博客整理了4个夜晚,内容有点多建议慢慢看.本章涵盖以下主题: 介绍C# 理解C#的基础知识 使用变量 处理空 ...
- 关键字abstract和static总结
1. abstract:意为抽象,在Java中可以修饰方法或者类 (1)修饰方法,这个方法是抽象方法,无方法体,这个类一定是抽象类,这个类的子类必须实现这个抽象方法: (2)修饰类,这个类一定是抽象 ...
- NTP配置(广播模式并且无认证)
1.服务器端 1.1服务器本地同步自己的时钟,否则无法对外提供NTP服务 [NTP]ntp refclock-master 3 [NTP]ntp refclock-master 127.127.1.1 ...
- 2018年一名合格的web前端程序员应该会哪些技术
有朋友让小编说一说web前端在未来几年的发展趋向,对于这个问题,恕小编无能为力,web前端技术日新月异,更新非常快,谁也不能预料未来会发生些什么 小编也只能说在2018年,react native和j ...
- GDB常用命令整理
(gdb) break xxx (gdb) b xxx 在源代码指定的某一行设置断点,其中 xxx 用于指定具体打断点的位置. (gdb) run (gdb) r 执行被调试的程序,其会自动在第一个断 ...
- 高版本(8以上)tomcat不支持rest中的delete和put方式请求怎么办
出现问题 当我们去访问delete方式和put方式: 后来才知道tomcat8以上是不支持delete方式和put方式 解决方法: 在跳转目标的jsp头文件上改为(加上了isErrorPage=&qu ...
- 记一次.Net5接入支付宝SDK的小插曲
由于业务需求,在项目里面要接入支付宝的支付功能,于是在github上找到了支付宝的官方sdk:https://hub.fastgit.org/alipay/alipay-easysdk 先说问题: 在 ...
- 微信小程序云开发-云函数-调用初始云函数获取openid
一.调用初始云函数获取openid的两种方法 1.传统的success和fail 2.ES6的.then和.catch 3.编译结果 说明:初始云函数,是指刚创建完成的云函数.默认系统写的代码.
- 算法优化---素数(质数)(Java版)
4.1优化算法-----输出素数 最简代码请直接移步文末 原代码:https://www.cnblogs.com/Tianhaoblog/p/15077840.html 对应优化如下 优化一:在遍历内 ...