之前学习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中对象回收算法的认识的更多相关文章

  1. JVM总括二-垃圾回收:GC Roots、回收算法、回收器

    JVM总括二-垃圾回收:GC Roots.回收算法.回收器 目录:JVM总括:目录 一.判断对象是否存活 为了判断对象是否存活引入GC Roots,如果一个对象与GC Roots没有直接或间接的引用关 ...

  2. JVM垃圾回收机制之对象回收算法

    前言 在前面的文章中,介绍了JVM内存模型分为:堆区.虚拟机栈.方法区.本地方法区和程序计数器,其中堆区是JVM中最大的一块内存区域,在Java中的所有对象实例都保存在此区域,它能被所有线程共享. 在 ...

  3. 浅谈PHP5中垃圾回收算法

    原文链接:http://www.cnblogs.com/leoo2sk/archive/2011/02/27/php-gc.html PHP是一门托管型语言,在PHP编程中程序员不需要手工处理内存资源 ...

  4. JVM中垃圾回收算法

    GC 算法与种类 GC的概念 Garbage Collection 垃圾收集1960年 List 使用了GCJava中,GC的对象是堆空间和永久区 引用计数法 老牌垃圾回收算法通过引用计算来回收垃圾使 ...

  5. 小师妹学JVM之:GC的垃圾回收算法

    目录 简介 对象的生命周期 垃圾回收算法 Mark and sweep Concurrent mark sweep (CMS) Serial garbage collection Parallel g ...

  6. java面试一日一题:java中垃圾回收算法有哪些

    问题:请讲下在java中有哪些垃圾回收算法 分析:该问题主要考察对java中垃圾回收的算法以及使用场景 回答要点: 主要从以下几点去考虑, 1.GC回收算法有哪些 2.每种算法的使用场景 3.基于垃圾 ...

  7. 【C# .Net GC】垃圾回收算法 应用程序线程运行时,

    触发垃圾回收算法的条件 触发垃圾回收的条件 当满足以下条件之一时将发生垃圾回收: 操作系统报告低内存请看(将触发第2代垃圾回收). 这是通过 OS 的内存不足通知或主机指示的内存不足检测出来. 由托管 ...

  8. java中垃圾回收算法讲解

      判断对象是否存活的方法: 一.引用计数算法(Reference Counting) 介绍:给对象添加一个引用计数器,每当一个地方引用它时,数据器加1:当引用失效时,计数器减1:计数器为0的即可被回 ...

  9. JVM GC-----垃圾回收算法

    说到Java,一定绕不开GC,尽管不是Java首创的,但Java一定是使用GC的代表.GC就是垃圾回收,更直接点说就是内存回收.是对内存进行整理,从而使内存的使用尽可能大的被复用. 一直想好好写一篇关 ...

随机推荐

  1. Java Integer Addition Subtration Overflow 整数加减溢出

    leetCode有道题Reverse Integer,因为int的最大值为2的31次方减一,最小值为-2的31次方. 我一开始的代码将res递归加放在try中,以为溢出会有异常,然而并没有. 因为出传 ...

  2. javaweb 最简单的分页技术

    原文来自于https://www.cnblogs.com/xwlych/p/6017833.html 个人由加了一点注释,他的代码我运行不起来,弄了好一会 bean包  User.java packa ...

  3. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 字体图标(Glyphicons):glyphicon glyphicon-barcode

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  4. NumPy 基于已有数据创建数组

    原文:Python Numpy 教程 章节 Numpy 介绍 Numpy 安装 NumPy ndarray NumPy 数据类型 NumPy 数组创建 NumPy 基于已有数据创建数组 NumPy 基 ...

  5. spring boot集成mybatis(1)

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

  6. 【剑指Offer】面试题52. 两个链表的第一个公共节点

    题目 输入两个链表,找出它们的第一个公共节点. 如下面的两个链表: 在节点 c1 开始相交. 示例 1: 输入:intersectVal = 8, listA = [4,1,8,4,5], listB ...

  7. 996.ICU 爆发,互联网从业者难逃“高薪陷阱”

    从 3 月 27 日开始,截止本文发稿,GitHub 上面的项目 996.ICU 的 Star 数量已经超过 18 万,这场由程序员发动的轰轰烈烈的公开反对 996 工作制的运动,早已突破互联网圈层而 ...

  8. 【pwnable.kr】 shellshock

    pwnable从入门到放弃,第五题. ssh shellshock@pwnable.kr -p2222 (pw:guest) 这题主要涉及了一个关于bash的CVE漏洞. 首先还是下载源代码审计一下, ...

  9. 数据库连接池DBCP的使用

    一.直接使用代码链接(一般企业开发不用这种方式) 1.导入JAR 把jar包拷贝到lib文件夹里面然后右击 build path一下 2.建一个jdbc.proprtties文件 driverClas ...

  10. Nim游戏(尼姆博弈)

    这里是尼姆博弈的模板,前面的博弈问题的博客里也有,这里单列出来. 有N堆石子.A B两个人轮流拿,A先拿.每次只能从一堆中取若干个,可将一堆全取走,但不可不取,拿到最后1颗石子的人获胜.假设A B都非 ...