jvm垃圾回收机制_深入理解JVM的垃圾回收机制

8a5668fb0c384009c3d0ce405fa8599b.png

​如何判断对象已“死”

Java堆中存放着几乎所有的对象实例,垃圾回收器在堆进行垃圾回收前,首先要判断这些对象那些还存活,那些已经“死去”。判断对象是否已“死”有如下几种算法:

1引用计数法

引用计数法描述的算法为:给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已“死”。

引用计数法实现简单,判定效率也比较高,在大部分情况下都是一个比较好的算法。比如Python语言就是采用的引用计数法来进行内存管理的。

但是,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因是引用计数法无法解决对象的循环引用问题。

范例:循环引用问题

/*** JVM参数:-XX:+PrintGC*
*/
public class Test {public Object instance = null;private static int _1MB = 1024 * 1024;private byte[] bigSize = new byte[2 * _1MB];public static void testGC() {Test test1 = new Test();Test test2 = new Test();test1.instance = test2;test2.instance = test1;test1 = null;test2 = null;// 强制JVM进行垃圾回收System.gc();}public static void main(String[] args) {testGC();}
}
程序输出:[GC (System.gc()) 6092K->856K(125952K), 0.0007504 secs]

从结果可以看出,GC日志包含" 6092K->856K(125952K)",意味着虚拟机并没有因为这两个对象互相引用就不回收他们。即JVM并不使用引用计数法来判断对象是否存活。

2可达性分析算法

在上面讲了,Java并不采用引用计数法来判断对象是否已“死”,而采用“可达性分析”来判断对象是否存活(同样采用此法的还有C#、Lisp-最早的一门采用动态内存分配的语言)。

此算法的核心思想:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。以下图为例:

2fbe3bd1d5d6e5baf30b34c7553bde07.png

对象Object5 —Object7之间虽然彼此还有联系,但是它们到 GC Roots 是不可达的,因此它们会被判定为可回收对象。

在Java语言中,可作为GC Roots的对象包含以下几种:

1、虚拟机栈(栈帧中的本地变量表)中引用的对象。

2、方法区中静态属性引用的对象

3、方法区中常量引用的对象

4、本地方法栈中(Native方法)引用的对象

在JDK1.2以前,Java中引用的定义很传统: 如果引用类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。这种定义有些狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态。

我们希望能描述这一类对象: 当内存空间还足够时,则能保存在内存中;如果内存空间在进行垃圾回收后还是非常紧张,则可以抛弃这些对象。很多系统中的缓存对象都符合这样的场景。

在JDK1.2之后,Java对引用的概念做了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种,这四种引用的强度依次递减。

1、强引用: 强引用指的是在程序代码之中普遍存在的,类似于"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾回收器永远不会回收掉被引用的对象实例。

2、软引用: 软引用是用来描述一些还有用但是不是必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出之前,会把这些对象列入回收范围之中进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用。

3、弱引用: 弱引用也是用来描述非必需对象的。但是它的强度要弱于软引用。被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾回收器开始进行工作时,无论当前内容是否够用,都会回收掉只被弱引用关联的对象。在JDK1.2之后提供了WeakReference类来实现弱引用。

4、虚引用: 虚引用也被称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK1.2之后,提供了PhantomReference类来实现虚引用。

生存or死亡?

即使在可达性分析算法中不可达的对象,也并非"非死不可"的,这时候他们暂时处在"缓刑"阶段。要宣告一个对象的真正死亡,至少要经历两次标记过程: 如果对象在进行可达性分析之后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者finalize()方法已经被JVM调用过,虚拟机会将这两种情况都视为"没有必要执行",此时的对象才是真正"死"的对象。

如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个叫做F-Queue的队列之中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它(这里所说的执行指的是虚拟机会触发finalize()方法)。finalize()方法是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象在finalize()中成功拯救自己(只需要重新与引用链上的任何一个对象建立起关联关系即可),那在第二次标记时它将会被移除出"即将回收"的集合;如果对象这时候还是没有逃脱,那基本上它就是真的被回收了。

范例:对象自我拯救

public class Test {public static Test test;public void isAlive() {System.out.println("I am alive :)");}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("finalize method executed!");test = this;}public static void main(String[] args)throws Exception {test = new Test();test = null;System.gc();Thread.sleep(500);if (test != null) {test.isAlive();}else {System.out.println("no,I am dead :(");}// 下面代码与上面完全一致,但是此次自救失败test = null;System.gc();Thread.sleep(500);if (test != null) {test.isAlive();}else {System.out.println("no,I am dead :(");}}
}

从上面代码示例我们发现,finalize方法确实被JVM触发,并且对象在被收集前成功逃脱。

但是从结果上我们发现,两个完全一样的代码片段,结果是一次逃脱成功,一次失败。这是因为,任何一个对象的finalize()方法都只会被系统自动调用一次,如果相同的对象在逃脱一次后又面临一次回收,它的finalize()方法不会被再次执行,因此第二段代码的自救行动失败。

2

回收方法区

方法区(永久代)的垃圾回收主要收集两部分内容:废弃常量无用类

回收废弃常量和回收Java堆中的对象十分类似。以常量池中字面量(直接量)的回收为例,假如一个字符串"abc"已经进入了常量池中,但是当前系统没有任何一个String对象引用常量池中的"abc"常量,也没有其他地方引用这个字面量,如果此时发生GC并且有必要的话,这个"abc"常量会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

判定一个类是否是"无用类"则相对复杂很多。

类需要同时满足下面三个条件才会被算是"无用的类"

1.该类的所有实例都已经被回收(即在Java堆中不存在任何该类的实例)

2.加载该类的ClassLoader已被回收

3.该类对应的Class对象没有任何其他地方被引用,无法在任何地方通过反射访问该类的方法

JVM可以对同时满足上述3个条件的无用类进行回收,也仅仅是“可以”而不是必然。在大量使用反射、动态代理等场景都需要JVM具备类卸载的功能来防止永久代的溢出。

2

垃圾回收算法

1标记-清除算法

“标记-清除”算法是最基础的收集算法。算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象(标记过程参见1.2可达性分析)。后续的收集算法都是基于这种思路并对其不足加以改进而已。

“标记-清除”算法的不足主要有两个:

效率问题:标记和清除这两个过程的效率都不高

空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集。

2复制算法(新生代回收算法)

“复制”算法是为了解决“标记-清除”的效率问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等的复杂情况,只需要移动堆顶指针,按顺序分配即可。此算法实现简单,运行高效。算法的执行流程如下图:

ed90be6a0b9ed37e0cfcd2ec37b0ef8f.png

现在的商用虚拟机(包括HotSpot)都是采用这种收集算法来回收新生代

新生代中98%的对象都是"朝生夕死"的,所以并不需要按照1 : 1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存者)空间,每次使用Eden和其中一块Survivor(两个Survivor区域一个称为From区,另一个称为To区域)。当回收时,将Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。

当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保。

HotSpot默认Eden与Survivor的大小比例是8 : 1,也就是说Eden:Survivor From : Survivor To = 8:1:1。所以每次新生代可用内存空间为整个新生代容量的90%,而剩下的10%用来存放回收后存活的对象。

HotSpot实现的复制算法流程如下:

1、当Eden区满的时候,会触发第一次Minor gc,把还活着的对象拷贝到Survivor From区;当Eden区再次出发Minor gc的时候,会扫描Eden区和From区,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden区和From区清空。

2、当后续Eden区又发生Minor gc的时候,会对Eden区和To区进行垃圾回收,存活的对象复制到From区,并将Eden区和To区清空

3、部分对象会在From区域和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还存活,就存入老年代。

f4d727eb2242e5c2325bd87d3fcefccf.png

3标记整理算法(老年代回收算法)

复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。因此在老年代一般不能使用复制算法。

针对老年代的特点,提出了一种称之为“标记-整理算法”。标记过程仍与“标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象向一端移动,然后直接清理掉端边界以外的内存。流程图如下:

88406f8f278926fce151980b8eb0ffbb.png

4分代收集算法

当前JVM垃圾收集都采用的是"分代收集(Generational Collection)"算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。

一般是把Java堆分为新生代和老年代。在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法。

面试题: 请问了解Minor GC和Full GC么,这两种GC有什么不一样吗?

Minor GC又称为新生代GC : 指的是发生在新生代的垃圾收集。因为Java对象大多都具备朝生夕灭的特性,因此Minor GC(采用复制算法)非常频繁,一般回收速度也比较快。

Full GC 又称为老年代GC或者Major GC : 指发生在老年代的垃圾收集。出现了Major GC,经常会伴随至少一次的Minor GC(并非绝对,在Parallel Scavenge收集器中就有直接进行Full GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。

阅读原文:深入理解JVM的垃圾回收机制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/571819.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EF sqlite3报错 System.Data.Entity.Core.EntityException: 在提供程序连接上启动事务时出错。有关详细信息,请参阅内部异常。...

问题:sqlite3使用ef框架操作数据库报错 问题原因:数据库文件没有访问权限 结局方案:可以将数据库文件所在的文件夹的访问权限添加Everyone用户权限。 错误:"System.Data.Entity.Core.EntityException: 在提供程序连接上启动事…

oracle job定时报错,Oracle Job定时任务的使用详解

Oracle中的job能为你做的就是在你规定的时间格式里执行存储过程,定时执行一个任务 。下面是一个小案例,定时每15分钟向一张表插入一条数据一、步骤11.创建一张测试表-- Create tablecreate table A8(a1 VARCHAR2(500))tablespace DSP_DATApctfree 10init…

python调用ping命令_在Python中调用Ping命令,批量IP的方法

#!/usr/bin/env python #coding:UTF-8Author: jefferchen163.com 可在命令行直接带目的IP,也可将IP列表在文本文件中。 pingip.py -d DestIP DestIP示例: a)单个: 192.168.11.1 b)多个: 192.168.11.1;172.16.8.1;176.13.18.2 c)网段: 192.168…

thinkPHP伪静态,如何去掉index.php呢?

如何去掉index.php呢?1.httpd.conf配置文件中加载了mod_rewrite.so模块 //在APACHE里面去配置#LoadModule rewrite_module modules/mod_rewrite.so把前面的警号去掉2.AllowOverride None 讲None改为 All //在APACHE里面去配置 (注意其他地方的AllowOverride也统统设置为…

chown -r oracle:oinstall /oracle,CentOS7安装Oracle12c图文详解

root身份安装依赖包: yum -y install binutils compat-libcap1 compat-libstdc-33 compat-libstdc-33*.i686 elfutils-libelf-devel gcc gcc-c glibc*.i686 glibc glibc-devel glibc-devel*.i686 ksh libgcc*.i686 libgcc libstdc libstdc*.i686 libstdc-devel li…

在苹果笔记本如何使用python_mac下如何将python2.7改为python3

1.查看当前电脑python版本 python -V // 显示2.7.x 2.用brew升级python brew update python 3.如果安装成功,去系统目录下回看到两个版本的python cd usr/local/Cellar/ //到此目录下 cd python/ //进入python目录下 查看已安装的python版本,如果有2.x 和…

[BZOJ1834][ZJOI2010]network 网络扩容 最大流+费用流

1834: [ZJOI2010]network 网络扩容 Time Limit: 3 Sec Memory Limit: 64 MB Submit: 3330 Solved: 1739 [Submit][Status][Discuss]Description 给定一张有向图,每条边都有一个容量C和一个扩容费用W。这里扩容费用是指将容量扩大1所需的费用。求: 1、 …

oracle备份密码文件,[数据库]Oracle数据库备份dmp文件,使用cmd命令导入导出步骤,以及忘记Oracle密码_星空网...

Oracle数据库备份dmp文件,使用cmd命令导入导出步骤,以及忘记Oracle密码2013-07-300dmp文件导入导出步骤1.创建表空间--datafile 的路径随便给,但是后面的文件命名用dbfCREATE TABLESPACE DTBSLOGGINGDATAFILE D:\OracleDateFile\DTBS.DBF SIZE 32MAUTOEX…

python需要花钱下载吗_用Python下载知乎视频,非常实用

原标题:用Python下载知乎视频,非常实用Python下载知乎视频。 # -*- coding: utf-8 -*- """ 下载知乎视频: 依赖: pip install requests mac 安装 ffmpeg: brew install ffmpeg """ import re impor…

php instanceof 基类,PHP强制对象类型之instanceof操作符

一、简介在php(做为现在的主流开发语言)中实现强制对象类型有时可能非常重要。如果缺少了它,或是因为缺乏这方面的知识——基于不正确的编程假设,或者仅仅是由于懒惰,那么你会在特定的Web应用程序中看到你所不希望的结果。特别是当用php(做为…

msyql备份还原

MySQL备份和还原,都是利用mysqldump、mysql和source命令来完成的。 1.Win32下MySQL的备份与还原 1.1 备份 开始菜单 | 运行 | cmd |利用“cd \Program Files\MySQL\MySQL Server 5.0\bin”命令进入bin文件夹 | 利用“mysqldump -u 用户名 -p databasename >exportfilename”…

python相比于excel的优势_对照Excel使用Python进行数据分析,更快掌握

Excel和Python,作为数据分析的主流工具,在从效率提升到数据商业化的整个过程中,都起到了重要作用。不管是在Excel中通过鼠标点选实现,亦或是利用Python通过代码实现,数据分析中的很多基础功能都是相通的。 在数据量级大…

oraoledb.oracle 12c,关于OraOLEDB.Oracle找不到驱动问题的一种可能解决方案

如果安装Oracle的时候没有把Oracle Provider for OLE DB,这个组件安装上,那么就会导致在使用程序的时候无法使用Oracle客户端驱动问题,弥补的办法就是重新下载客户端程序。以下介绍win 2008 x64 Oracle 11g x64情况搭建安装驱动程序首先得下…

python二维图颜色函数_Python scipy的二维图像卷积运算与图像模糊处理操作示例

本文实例讲述了Python scipy的二维图像卷积运算与图像模糊处理操作。分享给大家供大家参考,具体如下: 二维图像卷积运算 一 代码 import numpy as np from scipy import signal, misc import matplotlib.pyplot as plt image misc.ascent()#二维图像数组…

linux命令行大全 笔记,《Linux命令行大全》读书笔记

8种机械键盘轴体对比本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?shell就是一个程序,它接受从键盘输入的命令,然后把命令传递给操作系统执行。当使用图像用户界面时,我们需要一个叫做终端仿真器…

dedecms 添加,编辑文章时 文章标题有字数限制的解决办法。

百度上说 解决方法有两步: 一、先要在系统参数下的其他选项中修改文档标题最大长度(如修改为150), 后台系统 - 其它选项 - 文档标题最大长度 默认的是60改为150, 二、修改默认参数后,需要手工修改数据表。 …

python绘制散点图、如何选两列作为横坐标_在matplotlib散点图(水平杆图)中从x=0到数据点绘制水平线...

考虑以下情节:由此函数生成:def timeDiffPlot(dataA, dataB, savetoNone, legNone): labels list(dataA["graph"]) figure(figsizescreenMedium) ax gca() ax.grid(True) xi range(len(labels)) rtsA dataA["running"] / 1000.0…

linux命令基础知识 管道流,linux基础知识-I/O重定向,管道(示例代码)

系统设定默认输出设备:标准输出,STDOUT, 1默认输入设备:标准输入, STDIN, 0标准错误输出:STDERR, 2标准输入:键盘标准输出和错误输出:显示器I/O重定向:Linux:>: 覆盖输出[[email protected] …

自动轮播与按钮的联动

将自动轮播与按钮结合起来,就是多写了好几行,挺好看的不过,留下 z1; l.eq(0).css("background-color","orange"); function move(){ if(z!3){ bo.stop(); bo.animate({marginLeft:z*(-680)"px"}, 500, functio…

python运行命令_Python中执行外部命令

有很多需求需要在Python中执行shell命令、启动子进程,并捕获命令的输出和退出状态码,类似于Java中的Runtime类库。 subprocess模块的使用: Python使用最广泛的是标准库的subprocess模块,用来替换os.system(),os.spawn*(),os.popen…