关于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就是垃圾回收,更直接点说就是内存回收.是对内存进行整理,从而使内存的使用尽可能大的被复用. 一直想好好写一篇关 ...
随机推荐
- 七十六、SAP中数据库的查询用法之 COUNT(总数),SUM(求和),AVG(求平均),GROUP BY(分组)
一.我们来查看一个sbook的数据库 二.查看这个表的内容如下 三.表数据如下 四.代码如下 五.结果如下 *&---------------------------------------- ...
- 141-PHP类的抽象方法和继承实例(一)
<?php abstract class ren{ //定义人类 //定义成员属性 protected $name=''; protected $age=0; //定义成员方法 public f ...
- 操作系统类型&操作系统结构&现代操作系统基本特征
五大类型操作系统 (1). 批处理操作系统 用户脱机使用计算机 用户提交作业之后直到获得结果之前就不再和计算机打交道. 作业提交的方式可以是直接交给计算中心的管理操作员,也可以是通过远程通讯线路提交. ...
- (java) webdriver 启动firefox driver时,加载firebug的扩展
去网上下载一个firebug.xpi(对应版本, 我的ff是17,可以使用firebug-1.11.4.xpi,最好使用非firefox浏览器下载,不然提示你直接安装到firefox) @Before ...
- kubernter相关内容
1. Kubernetes 第一章:互联网架构的演变 随着1946年世界上第一台电子计算机的问世网络就随之出现了,只不过当初只是为了解决多个终端之间的连接,这就是局域网的雏形.后来,随着美国国防部高级 ...
- mp4流化
MP4需要流化 不然会频繁seek 对于http形式的播放而言 苦不堪言 ffmpeg -i g:/media/err.mp4 -movflags +faststart -codec copy g:/ ...
- Tomcat JDK MySql 安装配置
Tomcat 7绿色版指定jdk并注册服务 https://blog.csdn.net/weixin_43976019/article/details/89386171 例如:service.b ...
- 在Mac上如何运行jar文件
操作一:编译,打包. 操作二: 运行jar文件,提示:jar中没有主清单属性. 解决办法: 使用解压工具解压HelloWorld.jar文件,往MANIFEST.MF中添加Main-Class. 1. ...
- C语言预处理理论-宏定义2
宏定义21.带参宏和带参函数的区别(1)宏定义是在预处理期间处理的,而函数是在编译期间处理的.这个区别带来的实质差异是:宏定义最终是在调用宏的地方把宏体原地展开,而函数是在调用函数处跳转到函数中去执行 ...
- MySLQ排序后标记排行
查询排行及所有(表名.*) 1. ; AS top, customer.* FROM customer 2. AS top, customer.* ) r, customer ORDER BY cus ...