您好,欢迎来到网暖!
?
当前位置:网暖 » 站长资讯 » 建站基础 » 网络技术 » 文章详细 订阅RssFeed

Java程序员必备技能内存管理机——垃圾标记

来源:网络整理 浏览:181次 时间:2019-12-06
正文

1、怎么找到存活对象?

通过上篇文章我们知道,JVM创建对象时会通过某?#22336;?#24335;从内存中划分一块区域进行分配。那么当我们服务器源源?#27426;?#30340;接收请求的时候,就会频繁的需要进行内存分配的操作,但是我们服务器的内存确是非常有限的呢!所以对不再使用的内存进行回收再利用?#32479;?#20102;JVM肩负的重任了! 那么,摆在JVM面前的问题来了,怎么判断哪些内存不再使用了?怎么合理、高效的进行回收操作?既然要回收,那第一步就是要找到需要回收的对象!

1.1、引用计数法

实现思路:给对象添加一个引用计数器,每当有一个地方引用它,计数器加1。当引用失效,计数器值减1。任何时刻计数器值为0,则认为对象是不再被使用的。举个小栗子,我们有一个People的类,People类有id和bestFriend的属性。我们用People类来造两个小人:

 People p1 = new People(); People p2 = new People();

通过上篇文章的知识我们知道,当方法执行的时候,方法的局部变量表和堆的关系应该是如下图的(注意堆中对象?#20998;?#32418;色括号内的数字,就是引用计数器,这里只是举栗,实际实现可能会有差异):

Java程序员必备技能内存管理机——垃圾标记

造出来的p1和p2两个人,我想让他们互为最好的朋友,于是代码如下:

 People p1 = new People(); People p2 = new People(); p1.setBestFriend(p2); p2.setBestFriend(p1);

对应的引用关系?#21152;?#35813;如下(注意引用计数器值的变化):

Java程序员必备技能内存管理机——垃圾标记

然后我们再做一些处理,去除变量和堆中对象的引用关系。

 People p1 = new People(); People p2 = new People(); p1.setBestFriend(p2); p2.setBestFriend(p1); p1 = null; p2 = null;

这时候引用关系图就变成如下了,由于p1和p2对象还相互引用着,所以引用计数器的值还为1。

Java程序员必备技能内存管理机——垃圾标记

优点:实现简单,效率高。

缺点:很难解决对象之间的相互循环引用。且开销较大,频繁的引用变化会带来大量的额外运算。在谈实现思路的时候有这样一句话“任何时刻计数器值为0,则认为对象是不再被使用的”。但是通过上面的例子我们可以看到,虽然对象已经不再使用了,但计数器的值仍然是1,所以这两个对象?#25442;?#34987;标记为垃圾。

现状:主流的JVM都没有选用引用计数法来管理内存。

1.2、可达性分析

实现思路?#21644;?#36807;GC Roots的对象作为起始点,从这些节点向下搜索,搜索走过的路径成为引用链,当一个对象到GC Root没有任?#25105;?#29992;链相连时,则证明对象是不可用的。如下图,红色的几个对象由于没有跟GC Root没有任?#25105;?#29992;链相连,所以会进行标记。

Java程序员必备技能内存管理机——垃圾标记

优点:可以很好的解决对象相互循环引用的问题。

缺点:实现比较复杂;需要分析大量数据,消耗大量时间;

现状:主流的JVM(如HotSpot)都选用可达性分析来管理内存。

2、标记死亡对象

通过可达性分析可以对需要回收的对象进行标记,是否标记的对象?#27426;?#20250;被回收呢?并不是呢!要真正宣告一个对象的死亡,至少要经历两次的标记过程!

2.1、第一次标记

在可达性分析后发现到GC Roots没有任?#25105;?#29992;链相连时,被第一次标记。并且判断此对象是否必要执行finalize()方法!如果对象没有覆盖finalize()方法或者finalize()已经被JVM调用过,则这个对象就会认为是垃圾,可以回收。对于覆盖了finalize()方法,且finalize()方法没有被JVM调用过时,对象会被放入一个成为F-Queue的队列中,等待着被触发调用对象的finalize()方法。

2.2、第二次标记

执行完第一次的标记后,GC将对F-Queue队列中的对象进行第二次小规模标记。也就是执行对象的finalize()方法!如果对象在其finalize()方法中重新与引用链上任?#25105;?#20010;对象建立关联,第二次标记时会将其移出"即将回收"的集合。如果对象没有,?#37096;?#20197;认为对象已死,可以回收了。

finalize()方法是被第一次标记对象的逃脱死亡的最后一次机会。在jvm中,一个对象的finalize()方法?#25442;?#34987;系统调用一次,经过finalize()方法逃脱死亡的对象,第二次?#25442;?#20877;调用。由于该方法是在对象进行回收的时候调用,所以可以在该方法中实现资源关闭的操作。但是,由于该方法执行的时间是不确定的,甚至,在java程序不正常退出的情况下该方法都不?#27426;?#20250;执行!所以在正常情况下,尽量避免使用!如果需要"?#22836;?#36164;源",可以定义显式的终止方法,并在"try-catch-finally"的finally{}块中保证及时调用,如File相关类的close()方法。下面我们看一个在finalize中逃脱死亡的栗子吧:

public class GCDemo { public static GCDemo gcDemo = null; public static void main(String[] args) throws InterruptedException { gcDemo = new GCDemo(); System.out.println("------------对象刚创建------------"); if (gcDemo != null) { System.out.println("我还活得好好的!"); } else { System.out.println("我死了!"); } gcDemo = null; System.gc(); System.out.println("------------对象第一次被回收后------------"); Thread.sleep(500);// 由于finalize方法的调用时间不确定(F-Queue线程调用),所以休眠?#25442;?#20799;?#32321;?#26041;法完成调用 if (gcDemo != null) { System.out.println("我还活得好好的!"); } else { System.out.println("我死了!"); } gcDemo = null; System.gc(); System.out.println("------------对象第二次被回收后------------"); Thread.sleep(500); if (gcDemo != null) { System.out.println("我还活得好好的!"); } else { System.out.println("我死了!"); } // 后面无论多少次GC都?#25442;?#20877;执行对象的finalize方法 } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("execute method finalize()"); gcDemo = this; }}

执行结果如下,具体就不多?#36947;玻?#19981;明白的就自己动手去试试吧!

Java程序员必备技能内存管理机——垃圾标记

3、枚举根节点

通过上面可达性分析我们了解了?#24515;?#20123;GC Root,了解了通过这些GC Root去搜寻并标记对象是生存还是死亡的思路。但是具体的实现就是那张图显示的那么简单吗?当然不是,因为我们的堆是分代收集的,那GC Root连接的对象可能在新生代,?#37096;?#33021;在老年代,新生代的对象可能会引用老年代的对象,老年代的对象?#37096;?#33021;引用新生代。如果直接通过GC Root去搜寻,则每次都会遍历整个堆,那分代收集就没法实现了呢!并且,枚举整个根节点的时候是需要线程停顿的(保证一致性,不能出现正在枚举 GC Roots,而程序还在跑的情况,这会导致 GC Roots ?#27426;?#21464;化,产生数据不一致导致统计不准确的情况),而枚举根节点又比较耗时,这在大并发高访问量情况下,?#22336;种?#23601;会导致系?#31243;被荊?#21861;意思呢,下面一张图感受一下:

Java程序员必备技能内存管理机——垃圾标记

如果是进行根节点枚举,我们先要全栈扫描,找到变量表中存放为reference类型的变量,然后找到堆中对应的对象,最后遍历对象的数据(如属性等),找到对象数据中存放为指向其他reference的对象……这样?#30446;?#38144;无疑是非常大的!

为解决上述问题,HotSpot 采用了一种 ?#30333;?#30830;式GC” 的技术,该技术主要功能就是让虚拟机可以准确的知道内存中某个位置的数据类型是什么,比如某个内存位置到底是一个整型的变量,还是对某个对象的reference,这样在进行 GC Roots枚举时,只需要枚举reference类型的即可。那怎么让虚拟机准确的知道哪些位置存在的是reference类型数据呢?OopMap+RememberedSet!

OopMap记录了栈上本地变量到堆上对象的引用关系,在GC发生时,线程会运行到最近的一个安全点停下来,然后更新自己的OopMap,记下栈上哪些位置代表着引用。枚举根节点时,递归遍历每个栈帧的OopMap,通过栈中记录的被引用对象的内存地址,即可找到这些对象( GC Roots )。这样,OopMap就避免了全栈扫描,加快枚举根节点的速度。

OopMap解决了枚举根节点耗时的问题,但是分代收集的问题依然存在!这时候就需要另一利器了- RememberedSet。对于位于不同年代对象之间的引用关系,会在引用关系发生时,在新生代边上专门开辟一块空间记录下来,这就是RememberedSet!所以“新生代的 GC Roots ” + “ RememberedSet存储的内容?#20445;?#25165;是新生代收集时真正的GC Roots(G1 收集器也使用了 RememberedSet 这种技术)。

3.1、安全点

HotSpot在OopMap的帮助下可以快速且准确的完成GC Roots枚举,但是在运行过程中,非常多的指令都会导致引用关?#24403;?#21270;,如果为这些指令都生成对应的OopMap,需要?#30446;?#38388;成本太高。所以只在特定的位置记录OopMap引用关系,这些位置称为安全点(Safepoint)。如何在GC发生时让所有线程(不包括JNI线程)运行到其所在最近的安全点上再停顿下来?这里有两?#22336;?#26696;:

1、?#32769;?#24335;中断:不需要线程的执行代码去主动配合,当发生GC时,先强制中断所有线程,然后如果发现某些线程?#21019;?#20110;安全点,那么将其唤醒,直至其到达安全点再次将其中断。这样一直等待所有线程都在安全点后开始GC。

2、主动式中断:不强制中断线程,只是简单地设置一个中断标记,各个线程在执行时主动轮询这个标记,一旦发现标记被改变(出现中断标记)时,就将自己中断挂起。目前所有商用虚拟机全部采用主动式中断。

安全点既不能太少,以至于 GC 过程等待程序到达安全点的时间过长,也不能太多,以至于 GC 过程带来的成本过高。安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生安全点(在主动式中断中,轮询标志的地方和安全点是重合的,所以线程在遇到这些指令时都会去轮询中断标志!)。

3.2、安全区域

使用安全点似乎已经完美解决如何进入GC的问题了,但是GC发生的时候,某个线程正在睡觉(sleep),无法响应JVM的中断请求,这时候线程一旦醒来就会继续执行了,这会导致引用关系发生变化呢!所以需要安全区域的思路来解决这个问题。线程执行进入安全区域,首先标识自己已经进入安全区域。线程?#25442;?#37266;离开安全区域时,其需要检查系统是否已经完成根节点枚举(或整个GC)。如果已经完成,就继续执行,否则必须等待,直到收到可以安全离开Safe Region的信号通知!

推荐站点

  • 腾讯腾讯

    腾讯网(www.QQ.com)是中国浏览量最大的中文门户网站,是腾讯公?#23601;?#20986;的集新?#21028;?#24687;、互动社区、娱乐产品和基础服务为一体的大型综合门户网站。腾讯网服务于全球华人用户,致力成为最具传播力和互动性,权威、主流、时尚的互联网媒体平台。通过强大的实时新闻和全面深入的信息资讯服务,为中国数以亿计的互联网用户提供富有创意的网上新生活。

    www.qq.com
  • 搜狐搜狐

    搜狐网是全球最大的中文门户网站,为用户提供24小时不间断的最新资讯,及搜索、?#22987;?#31561;网络服务。内容包括全球热点?#24405;⑼环?#26032;闻、时事评论、热播影视剧、体育赛事、行业动态、生活服务信息,以及论?#22330;?#21338;客、微博、我的搜狐等互动空间。

    www.sohu.com
  • 网易网易

    网易是中国领先的互联网技术公司,为用户提供免费邮箱、游戏、搜索引擎服务,开设新闻、娱?#24103;?#20307;育等30多个内容?#26723;潰?#21450;博客、视频、论坛等互动交流,网聚人的力量。

    www.163.com
  • 新浪新浪

    新浪网为全球用户24小时提供全面及时的中文资讯,内容覆盖国内外?#29615;?#26032;闻?#24405;?#20307;坛赛事、娱乐时?#23567;?#20135;业资讯、实用信息等,设有新闻、体育、娱?#24103;?#36130;经、科技、房产、汽?#26723;?0多个内容?#26723;潰?#21516;时开设博客、视频、论坛等?#26434;?#20114;动交流空间。

    www.sina.com.cn
  • 百度一下百度一下

    百度一下,你就知道

    www.baidu.com
?
奇妙马戏团试玩
上下分麻将有哪些 微信公众赚钱豆瓣 第一棒球比分直播 麻将来了6000底分 后三组选包胆什么玩法 北京赛车走势图表 西甲雪缘园 12选5技巧稳赚高手 贵州十一选五手机板走势图 湖北11选5百宝彩 山东菏泽麻将规则 11选5 190即时指数足球手机版 梦幻手游合宠赚钱嘛 广西快3综合走势图 甘肃快3