关于gc中对象回收算法的认识
之前学习java时,笔者看到很多学习资料说,gc判断object存活与否的算法是:给对象添加一个引用计数器,每当有一处地方引用它时,计数器值就加1,当引用失效时,计数器值就减1,当对象计数清零时,对象就会被gc回收。但等笔者开始学习jvm虚拟机后,才明白实际上gc并不是用这种算法实现的,理由如下:
package gc; public class ReferenceCountingGC {
public Object instance = null;
private static final int occupy_1MB=*;
private byte[] bigSize = new byte[*occupy_1MB]; public static void testGC(){
ReferenceCountingGC obj1=new ReferenceCountingGC();
ReferenceCountingGC obj2=new ReferenceCountingGC();
obj1.instance=obj2;
obj2.instance=obj1; obj1=null;
obj2=null; System.gc();
} public static void main(String [] args){
ReferenceCountingGC.testGC();
}
}
[GC (System.gc()) [PSYoungGen: 10854K->480K(38400K)] 10854K->488K(125952K), 0.0014556 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 480K->0K(38400K)] [ParOldGen: 8K->402K(87552K)] 488K->402K(125952K), [Metaspace: 3072K->3072K(1056768K)], 0.0125161 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 38400K, used 333K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
eden space 33280K, % used [0x0000000795580000,0x00000007955d34a8,0x0000000797600000)
from space 5120K, % used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
to space 5120K, % used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
ParOldGen total 87552K, used 402K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
object space 87552K, % used [0x0000000740000000,0x00000007400648c8,0x0000000745580000)
Metaspace used 3079K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 339K, capacity 388K, committed 512K, reserved 1048576K
可以看到,obj1和obj2互相引用,但他们还是被回收机制给回收了,可见gc底层并不是用引用计数算法实现的。
下面介绍gc底层实现所采用的算法,可达性分析算法(Reachability Analysis),通过一系列被称为“GC Roots”的对象节点作为起始点,当GC root无法到达一个对象时,就可以判断此对象不可用,从而判断此对象可以被回收。
GC Roots对象包括下面几种:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象。
2.方法区中类静态属性引用的对象。
3.方法区中常量引用的对象。
4.本地方法栈中JNI(即一般说的Native方法)引用的对象。
在判断对象可以被回收后,待回收的对象并不会立刻被gc回收掉,而是会进入一个名为F-queue的队列,在这里,对象有最后一次拯救自己的机会(当然也可能没有,这不好说,后面会解释),虚拟机会自动建立一个FInalize线程(此线程优先级很低),线程会调用finaliz()方法去执行F-queue中所有的对象,如果对象成功与引用链建立上联系,那么就可以免于死亡的命运。下面我写一段示意代码,用this关键字将finaliz()方法引用到对象中,使其建立联系。
package gc; public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOCK=null; public void isAlive(){
System.out.println("I am alive XD");
} @Override
protected void finalize() throws Throwable{
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGC.SAVE_HOCK=this;
} public static void main(String [] args)throws Throwable{
SAVE_HOCK=new FinalizeEscapeGC(); SAVE_HOCK=null;
System.gc();
//因为Finalize线程的优先级很低,因此笔者将线程挂起1s,等待Finalize线程的启动。
Thread.sleep(1000);
if (SAVE_HOCK!=null){
SAVE_HOCK.isAlive();
}else{
System.out.println("i am dead");
} SAVE_HOCK=null;
System.gc();
Thread.sleep(1000);
if (SAVE_HOCK!=null){
SAVE_HOCK.isAlive();
}else{
System.out.println("i am dead");
}
}
}
finalize method executed!
I am alive XD
i am dead
可以看到,我们第一次手动启动gc后,对象依旧存活,那为什么一样的代码,第二次对象就死亡了呢?这是因为同一个对象只会执行一次finalize()方法,当第二次面临gc时,就会直接被回收掉,这是从优化内存效率的角度而作出的设计。
虽然上面我们成功拯救了对象,但在实际情况下,我们不建议这么做。首先,有太多办法阻止对象被回收了,比如合理的使用try{};catch{},根本不需要在回收阶段再去解决这个问题。其次,Finalize线程的优先级很低,在示例代码中,我们是将线程挂起了1s来等待Finalize线程被开启,在实际开发中,这几乎不可能实现,会严重的影响系统的运行效率,再者,gc并不是一定要执行finalize()方法,因为如果有一个方法执行起来很慢,或者直接阻塞了线程,那岂不是会有内存泄露的隐患。因此,gc并没有保证一定会等待finaliz()执行完再去回收对象。
在笔者看过的很多技术书籍中,都认为方法区(Method Are,永生代(permanent generation)概念在JDK1.8里已被Oracle移除)是没有gc机制的。但事实上,这么说是错误的。在Java虚拟机规范中,明确了虚拟机可以不在方法区内运行gc。这是因为在方法区内运行gc的效率很低,因为在jvm的新生代(Young generation)中,执行一次gc至少可以回收70~90的内存,大多数对象都是朝生夕死的,而方法区的回收效率远远低于堆的回收效率。因此,从效率考虑,一般不会在方法区内运行gc。
但这并不意味着我们不能在方法区内回收对象,事实上,方法区中的废弃常量和无用的类还是有必要回收的(比如常见的String字符串池问题),尤其是频繁自定义类加载器的场景下,虚拟机必须要具有类卸载的功能,以保证方法区的内存不会溢出。判断常量是否废弃很简单,而判断对象是否无用的条件就有些许苛刻了,必须要同时满足三个条件:
1.该类必须没有存活着的实例,也就是说,所有该类的实例都被gc从堆上回收了。
2.加载该类的类加载器(ClassLoader)已经被回收了。
3.该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
当类被判断为无用类后,虚拟机就可以对类执行回收了。不过可以并不意味着虚拟机一定要这么去做,事实上,HotSpot虚拟机提供了-Xnoclassgc参数来让用户控制是否回收,也可以用-verbose:class以及-XX+TraceClassLoading、-XX:+TraceClassUnLoading查看类加载和卸载信息,其中-verbose.class和-XX:+TraceClassLoading可以在Product版的虚拟机中使用,而-XX:+TraceClassUnLoading需要FastDebug版的虚拟机去支持。
在JDK1.8里,永生代的已经被Oracle公司永久的删除了,取而代之的是类元数据(Class MetaData),在本地的内存中进行分配,并显式管理元数据的空间。
从OS请求空间,然后分成块(piece);
类加载器从它的块中分配元数据的空间(一个块被绑定到一个特定的类加载器);
当类没有被加载到ClassLoader时,它的块就被回收,内存空间就会被释放出来以供再次使用;
元数据使用由mmap分配的空间,而不是由malloc分配的空间;
并且Hotspot虚拟机在JDK1.8中提供给用户两个新的参数来管理内存,这两个参数是:"-XX:maxMetaspaceSize:指定类元数据区的最大内存大小","-XX:MetaspaceSize:指定类元数据区的内存阈值--超过将触发垃圾回收机制"。
感谢周志明先生所著的深入理解Java虚拟机,笔者深受周志明先生的启发,故写下本文总结一下jvm中gc的知识。
关于gc中对象回收算法的认识的更多相关文章
- JVM总括二-垃圾回收:GC Roots、回收算法、回收器
JVM总括二-垃圾回收:GC Roots.回收算法.回收器 目录:JVM总括:目录 一.判断对象是否存活 为了判断对象是否存活引入GC Roots,如果一个对象与GC Roots没有直接或间接的引用关 ...
- JVM垃圾回收机制之对象回收算法
前言 在前面的文章中,介绍了JVM内存模型分为:堆区.虚拟机栈.方法区.本地方法区和程序计数器,其中堆区是JVM中最大的一块内存区域,在Java中的所有对象实例都保存在此区域,它能被所有线程共享. 在 ...
- 浅谈PHP5中垃圾回收算法
原文链接:http://www.cnblogs.com/leoo2sk/archive/2011/02/27/php-gc.html PHP是一门托管型语言,在PHP编程中程序员不需要手工处理内存资源 ...
- JVM中垃圾回收算法
GC 算法与种类 GC的概念 Garbage Collection 垃圾收集1960年 List 使用了GCJava中,GC的对象是堆空间和永久区 引用计数法 老牌垃圾回收算法通过引用计算来回收垃圾使 ...
- 小师妹学JVM之:GC的垃圾回收算法
目录 简介 对象的生命周期 垃圾回收算法 Mark and sweep Concurrent mark sweep (CMS) Serial garbage collection Parallel g ...
- java面试一日一题:java中垃圾回收算法有哪些
问题:请讲下在java中有哪些垃圾回收算法 分析:该问题主要考察对java中垃圾回收的算法以及使用场景 回答要点: 主要从以下几点去考虑, 1.GC回收算法有哪些 2.每种算法的使用场景 3.基于垃圾 ...
- 【C# .Net GC】垃圾回收算法 应用程序线程运行时,
触发垃圾回收算法的条件 触发垃圾回收的条件 当满足以下条件之一时将发生垃圾回收: 操作系统报告低内存请看(将触发第2代垃圾回收). 这是通过 OS 的内存不足通知或主机指示的内存不足检测出来. 由托管 ...
- java中垃圾回收算法讲解
判断对象是否存活的方法: 一.引用计数算法(Reference Counting) 介绍:给对象添加一个引用计数器,每当一个地方引用它时,数据器加1:当引用失效时,计数器减1:计数器为0的即可被回 ...
- JVM GC-----垃圾回收算法
说到Java,一定绕不开GC,尽管不是Java首创的,但Java一定是使用GC的代表.GC就是垃圾回收,更直接点说就是内存回收.是对内存进行整理,从而使内存的使用尽可能大的被复用. 一直想好好写一篇关 ...
随机推荐
- REST接口
全名是Representational State Transfer REST是设计风格而不是标准 建议将JSON格式作为标准响应格式 -------------------------------- ...
- 第二十篇ORM查询与SQL语句
ORM查询与SQL语句 多表操作 创建模型 实例:我们来假定下面这些概念,字段和关系 作者模型:一个作者有姓名和年龄. 作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息.作者详情 ...
- JAXB工具
在JDK6之后,都自带了JAXB工具,所以在jdk类库与tomcat WEBAPP类库之间,会造成冲突 https://blog.csdn.net/iteye_13776/article/detail ...
- vue 父子传值,子页面没有实时刷新的问题
在做高德地图的时候,发现列表点击编辑的时候,地图不能实时更新: <el-form-item label="门店坐标:" :label-width="formLabe ...
- UVA - 1605 Building for UN (联合国大楼)
题意:一个联合国大楼每层都有数量相等大小相同的格子,将其分配给n个国家,使任意两个不同的国家都相邻(同层有公共边或相邻层的同一个格子). 分析:可以设计一个只有两层的大楼,第一层每个国家占一行,第二层 ...
- SpringBoot 系列教程之事务不生效的几种 case
SpringBoot 系列教程之事务不生效的几种 case 前面几篇博文介绍了声明式事务@Transactional的使用姿势,只知道正确的使用姿势可能还不够,还得知道什么场景下不生效,避免采坑.本文 ...
- spring+springMVC+mybatis , 项目启动遇坑
github上找的框架组合例子 结合自己的数据库作为新项目开发. 但是项目启动时,tomcat启动失败: 检查不出错误. 于是改换maven引入jetty插件来启动项目, 结果在未改动的任何代码的情况 ...
- HDU_2255 二分图最佳完美匹配 KM匈牙利算法
一开始还没看懂这个算法,后来看了陶叔去年的PPT的实例演示才弄懂 用一个lx[]和ly[]来记录X和Y集合中点的权值,有个定理是 lx[i]+ly[j]==w[i][j](边权值) 则该点是最佳匹配, ...
- Hour of Code|京东云邀您一起,“码”上行动
"如果我并不希望成为一名程序员,那么为什么需要学习编程呢?" 相信很多人对于现在鼓励从小就学习编程的趋势都在心里问过这样的一个问题.在回答这个问题前,先和大家分享一个小故事吧. 1 ...
- Android群英传神兵利器读书笔记——第三章:Android Studio奇技淫巧
这篇文章篇幅较长,可以使用版权声明下面的目录,找到感兴趣的进行阅读 3.1 Android Studio使用初探 Project面板 Stucture面板 Android Monitor Keymap ...