JAVA对象生命周期(三)-对象的销毁
从引用说起
Object object = new Object();
- 假设这句代码出现在方法体中,"Object object” 这部分将会反映到Java栈的本地变量中,作为一个reference类型数据出现。
- “new Object()”这部分将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定。
- 另外,在java堆中还必须包括能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些数据类型存储在方法区中。
- reference类型在java虚拟机规范里面只规定了一个
指向对象的引用地址
,并没有定义这个引用应该通过那种方式去定位,访问到java堆中的对象位置,因此不同的虚拟机实现的访问方式可能不同,主流的方式有两种:使用句柄和直接指针。
指针直接引用
reference变量中直接存储的就是对象的地址,而java堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据。
句柄引用
java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。
优缺点
这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就虚拟机而言,它使用的是第二种方式(直接指针访问)
如何判断对象死亡
堆中放着几乎所有的对象实例,回收前第一步就要判断对象是否死亡
引用计数法
给对象增加一个引用计数器,每当有一个地方引用他,计数器就加1:当引用失效后,计数器就减1.任何时候计数器为0的对象就是可回收对象。
- 优点:判定效率很高
- 缺点:不会完全准确,因为如果出现两个对象相互引用的问题就不行了
/**
* testGC()方法执行后会不会被GC? 不会!!!!
*
* @author TongWei.Chen 2017-09-05 11:15:53
*/
public class ReferenceCountingGC {
public Object instance = null;
public static void testGC() {
//step 1
ReferenceCountingGC objA = new ReferenceCountingGC();
//step 2
ReferenceCountingGC objB = new ReferenceCountingGC();
//相互引用
//step 3
objA.instance = objB;
//step 4
objB.instance = objA;
//step 5
objA = null;
//step 6
objB = null;
//假设在这行发生CG,objA和objB是否能被回收? 不能!!!!
System.gc();
}
public static void main(String[] args) {
testGC();
}
}
可达性分析法
通过一系列称为“GC Roots”的对象作为七点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到GCRoots没有任何引用链相链时,则证明此对象是不可用的。
可以作为GC Roots的对象包括以下几点
1、虚拟机栈(栈帧中的本地变量表)中引用的对象。
2、方法区中的类静态属性引用的对象或者常量引用的对象。
3、本地方法栈中JNI(就是native方法)引用的对象。
垃圾收集算法
标记-清除算法
最基础的收集算法是“标记-清除”(Mark-Sweep)算法,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
标记-清除算法是最基础的收集算法,其他的收集算法都是基于这种思路并对其不足进行改进而得到的。
- 不足:
- 效率问题,标记和清除两个过程的效率都不高;
- 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
复制算法
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
- 不足:这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。
复制算法——优化
- 现在的商业虚拟机都采用这种收集算法来回收新生代
- 新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。
当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。
HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。
98%的对象可回收只是一般场景下的数据,没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)
有关年轻代的JVM参数
- -XX:NewSize和-XX:MaxNewSize
用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。
- -XX:SurvivorRatio
用于设置Eden和其中一个Survivor的比值,这个值也比较重要。
- -XX:+PrintTenuringDistribution
这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。
- -XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold
用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。
标记-整理算法
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
- 标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
分代收集算法
上面在讲复制算法优化的时候也提到了年轻代使用的垃圾收集算法。
- 当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,根据对象存活周期的不同将内存划分为几块。
- 把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
几种常见的垃圾回收器
串行:Serial 和Serial Old组合收集
- 串行垃圾回收器在进行垃圾回收时,它会持有所有应用程序的线程,冻结所有应用程序线程,使用单个垃圾回收线程来进行垃圾回收工作。
- 串行垃圾回收器是为单线程环境而设计的,如果你的程序不需要多线程,启动串行垃圾回收。
- 串行收集器是最古老,最稳定以及高效的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)
- 使用方法:-XX:+UseSerialGC 串联收集
串行:ParNew收集器+Serial Old组合收集
- ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩
- ParNew是并行收集器,不是并发收集器。并行收集器只是串行的多线程版本而已,此时用户线程仍然处于等待状态。并发是指用户线程和垃圾收集线程可以同时执行,在不通的cpu上。
- 使用方法:-XX:+UseParNewGC ParNew收集器
并行:Parallel Scavenge收集器+Serial Old(ps marksweep)组合收集
- 跟ParNew类似,是一种吞吐量优先收集器,即目标是达到一个可控制的吞吐量
吞吐量 = 运行用户代码时间 / (运行用户代码时间) + 垃圾收集时间
- 新生代复制算法、老年代标记-压缩
- 使用方法:-XX:+UseParallelGC Parallel Scavenge收集器
并行:Parallel Scavenge收集器+Parallel Old组合收集
- Parallel Old是Parallel Scavenge收集器的老年代版本
- 使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供
- 使用方法:-XX:+UseParallelOldGC Parallel Scavenge Old收集器
并发:ParNew+标记扫描CMS(Concurrent Mark Sweep)+Serial Old收集器
- 以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。
- CMS收集器是基于“标记-清除”算法实现的
- 4个步骤,包括:
- 初始标记(CMS initial mark):暂停所有其他线程,并记录下直接和roots相连的对象,速度很快;
- 并发标记(CMS concurrent mark):同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象,因为用户线程可能会不断的更新引用域,所以GC无法保证可达对象分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记(CMS remark):就是为了修正并发标记期间因为用户程序继续运行而导致标记变动的那一部分对象,这个阶段时间会比初始标记时间长,比并发标记时间短。
- 并发清除(CMS concurrent sweep):开启用户线程,同时GC线程开始对标记的区域进行清扫。
- Serial Old作为cms收集器出现Concurrent Mode Failure失败后的后备收集器使用
- 使用方法:-XX:+UseConcMarkSweepGC cms收集器
- 优点:并发收集、低停顿。
- 缺点:
- 对cpu资源敏感
- 无法处理大量的浮动垃圾
- “标记清除算法”产生大量空间碎片、并发阶段会降低吞吐量
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)
并发:G1收集器
G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS收集器相比G1收集器有以下特点:
- 空间整合:G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
- 可预测停顿:这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了
- 分代收集:上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。
- 可预测的停顿:这是G1相对CMS的另一大优势,除了可以降低停顿时间外,还能建立可预测的停顿时间模型,让使用者明确指定在一个为ms的时间片段内。
- G1还维护了一个优先队列,每次根据允许的时间,优选选择回收价值最大的Region(这也是G1名字的由来)。
- 收集步骤
- 标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)
- 并发标记:从GC Roots中对堆对象进行可达性分析,找出存活对象
- 最终标记:修改并发期间变动的标记记录
- 筛选回收:根据用户指定的停顿时间制定回收计划
- 复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。唯一和串行垃圾回收器不同的是,并行垃圾回收器是使用多线程来进行垃圾回收工作的。
G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。和CMS类似,G1收集器收集老年代对象会有短暂停顿。
G1收集器的核心思想是在CMS基础上增加了在有限的时间内尽可能高的收集效率。
几种垃圾收集器的组合
总结
垃圾收集的核心还是从“标记-清理”算法基础上的各种优化版本,每一种算法的都是从时间和空间两个角度出发来达到最高效率,请根据个人应用特性来选择最适合的垃圾收集器,好啦本文就说这么多,希望大家多思考多练习,欢迎留言讨论。
参考
https://blog.csdn.net/high2011/article/details/80177473
https://blog.csdn.net/u011130752/article/details/50886939
https://www.cnblogs.com/grey-wolf/p/9217497.html
https://www.sohu.com/a/217151448_812245
JAVA对象生命周期(三)-对象的销毁的更多相关文章
- Hibernate的三种状态及对象生命周期
理解Hibernate的三种状态,更利于理解Hibernate的运行机制,这些可以让你在开发中对疑点问题的定位产生关键性的帮助. 三种状态 临时状态(Transient):在通过new关键字, ...
- ASP.NET Core Web API下事件驱动型架构的实现(二):事件处理器中对象生命周期的管理
在上文中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发.订阅和处理的流程.这种实现太简单了,百十行代码就展示了一个基本工作原理.然而,要将这样的解决方案运用到实际生产环境,还有很 ...
- Python学习手册之内部方法、操作符重载和对象生命周期
在上一篇文章中,我们介绍了 Python 的类和继承,现在我们介绍 Python 的内部方法.操作符重载和对象生命周期. 查看上一篇文章请点击:https://www.cnblogs.com/dust ...
- JAVAEE_Servlet_03_Servlet对象生命周期
Servlet的对象声明周期 * 什么是对象生命周期? - 生命周期表示一个JAVA对象从创建到销毁的过程是一个生命周期 * Servlet对象生命周期 1. Servlet对象创建 无参构造 2. ...
- Servlet对象生命周期(四)
一.Servlet对象生命周期 一下图片说明上图第7点 destroy()方法是在停止tomcat服务器时执行 https://pan.baidu.com/s/1mgTabWW#list/path=% ...
- .Net组件程序设计之对象生命周期
.Net组件程序设计之对象生命周期 .NET 垃圾回收 IDisposable() Using语句 .NET 垃圾回收 是CLR管理着垃圾回收器,垃圾回收器监控着托管堆,而我们使用的对象以及系统启动是 ...
- Ninject之旅之三:Ninject对象生命周期
摘要 DI容器的一个责任是管理他创建的对象的生命周期.他应该决定什么时候创建一个给定类型的对象,什么时候使用已经存在的对象.他还需要在对象不需要的时候处理对象.Ninject在不同的情况下管理对象的生 ...
- iOS视图控制对象生命周期
iOS视图控制对象生命周期-init.viewDidLoad.viewWillAppear.viewDidAppear.viewWillDisappear.viewDidDisappear的区别及用途 ...
- IOS 视图控制对象生命周期-init、viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear等的区别及用途
iOS视图控制对象生命周期-init.viewDidLoad.viewWillAppear.viewDidAppear.viewWillDisappear.viewDidDisappear的区别及用途 ...
- 【转】【iOS知识学习】_视图控制对象生命周期-init、viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear等的区别及用途
原文网址:http://blog.csdn.net/weasleyqi/article/details/8090373 iOS视图控制对象生命周期-init.viewDidLoad.viewWillA ...
随机推荐
- bcc工具的简要学习
摘要 继续补充假期落下的内容. 其实有很多知识需要学习, 自己掌握的还是偏少一些. bcc的全貌 # 注意 bcc 需要较高的内核. 3.10 系列的内核基本不可用. argdist drsnoop ...
- css水平居中的5种几种方式
元素水平居中的第一种方式 子元素不需要宽度也可以 <div class="box"> <div class="son"> 我是内容 &l ...
- 【K哥爬虫普法】房产数据刑吗?爬虫多年没踩过缝纫机,劝你找找自己原因!
我国目前并未出台专门针对网络爬虫技术的法律规范,但在司法实践中,相关判决已屡见不鲜,K哥特设了"K哥爬虫普法"专栏,本栏目通过对真实案例的分析,旨在提高广大爬虫工程师的法律意识,知 ...
- 设计模式学习-使用go实现解释器模式
解释器模式 定义 优点 缺点 适用范围 代码实现 参考 解释器模式 定义 解释器模式(interpreter):给定一种语言,定义它的文法的一种表示,并定一个解释器,这个解释器使用该表示来解释语言中的 ...
- NLP领域任务如何选择合适预训练模型以及选择合适的方案【规范建议】【ERNIE模型首选】
1.常见NLP任务 信息抽取:从给定文本中抽取重要的信息,比如时间.地点.人物.事件.原因.结果.数字.日期.货币.专有名词等等.通俗说来,就是要了解谁在什么时候.什么原因.对谁.做了什么事.有什么结 ...
- python快速入门【一】-----基础语法
python入门合集: python快速入门[一]-----基础语法 python快速入门[二]----常见的数据结构 python快速入门[三]-----For 循环.While 循环 python ...
- 人工智能创新挑战赛:助力精准气象和海洋预测Baseline[3]:TCNN+RNN模型、SA-ConvLSTM模型
"AI Earth"人工智能创新挑战赛:助力精准气象和海洋预测Baseline[3]:TCNN+RNN模型.SA-ConvLSTM模型 1.气象海洋预测-模型建立之TCNN+RNN ...
- 百度飞桨:ERNIE 3.0 、通用信息抽取 UIE、paddleNLP的安装使用[一]
相关文章: 基础知识介绍: [一]ERNIE:飞桨开源开发套件,入门学习,看看行业顶尖持续学习语义理解框架,如何取得世界多个实战的SOTA效果?_汀.的博客-CSDN博客_ernie模型 百度飞桨: ...
- Flask Echarts 实现历史图形查询
Flask前后端数据动态交互涉及用户界面与服务器之间的灵活数据传递.用户界面使用ECharts图形库实时渲染数据.它提供了丰富多彩.交互性强的图表和地图,能够在网页上直观.生动地展示数据.EChart ...
- .NET 大数据实时计算--学习笔记
摘要 纯 .Net 自研大数据实时计算平台,在中通快递服务数百亿包裹,处理数据万亿计!将分享大数据如何落地以及设计思路,技术重难点. 目录 背景介绍 计算平台架构 项目实战 背景介绍 计算平台架构 分 ...