JVM如何判断对象能否被回收
•写在前面
说起Java和C++,很容易想到让人疯狂的指针,Java使用了内存动态分配和垃圾回收技术,让我们从C++的各种指针问题中摆脱出来,更加专心于业务逻辑,不过如果我们需要深入了解java的JVM相关原理,我们必须要面对这些东西,深入了解JVM在内存动态分配和垃圾回收技术的原理知识,这篇文章就是来做一个先导,在jvm进行垃圾回收之前,它必须要知道回收的对象是否已“死”,这样才能保证程序的正常稳定。
•对象的创建
我们将回收对象前,先讲讲在虚拟机上,对象是怎么被创建的。在我们编写代码的角度(语言层面)来看,我们创建一个对象实例,只需要使用new关键词就完事儿了,很简单,不过你享受的简单是因为虚拟机帮你承受了所有繁琐的工作,那虚拟机是怎么工作创建一个对象的呢?
当虚拟机遇到new指令时,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用(没有类,创建个锤子的对象),并且检查这个符号引用代表的类是否已被加载、解析和初始化过,如果没有,那必须要执行相应的类加载过程。这是第一步,在类加载检查过后,接下来虚拟机将为新生对象分配内存,对象所需的内存大小在类加载完成后便已经完全确定了(这里插一句,如何确定的?这就和对象的内存布局有关了,对象在内存中的布局可以分为3个区域,分别是对象头、实例数据和对齐填充,对象头里面存的是对象自身的运行时数据,比如哈希码、GC分代年龄、锁状态、线程持有的锁、偏向线程ID等等之类的信息,也就是和储存数据无关的额外内存空间,按道理这一块空间应该是固定的,不过在设计上还是被弄成了非固定的数据结构,这样更具不同的类节省空间,不深入不然扯不完,想要可以看另外一篇文章。接下来实例数据就是对象真正储存的有效信息,也是程序代码中所定义的各种类型的字段内容。最后一个对齐填充,顾名思义就是填补空间,因为以HotSpotVM为例,对象的大小必须是8字节的整数倍,所以就靠这个补全),给对象分配空间的任务相当于把一块确定大小的内存从Java堆中划分出来(为啥可以看我另一篇文章,运行时数据区)。
划分的时候会出现两种情况,第一种就是java堆中的内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间的一边移动对象大小相等的距离,这种分配方式就是“指针碰撞”。第二种情况就是空间不规整,也就是已使用的内存和空闲内存相互交错,这个时候指针碰撞起不来作用,那么这个时候虚拟机必须维护一个列表,记录哪些内存可用,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新相关内存信息,这种方式叫做“空闲列表”。因为创建对象非常频繁,所以会涉及到并发的时候,会出现一个叫做“本地线程分配缓冲”的概念,我这里也不深入,自己去查,哈哈哈。空间分配完成之后,虚拟机需要分配到的内存空间都进行初始化为零值(注意不包括对象头),这样就保证对象的实例字段在java代码中可以不赋初始值就直接使用。最后虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息,对象的哈希码、对象的GC分代等信息。到此,对于虚拟机来说,对象创建完毕。
•引用计数算法
引用计数是一个很好理解的概念,就是给对象添加一个引用计数器,每当有一个地方引用这个对象时,计数器值加1,每当一个引用失效时,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的。是不是很好理解,而且判定对象是否可用效率很高,在大部分时候它是一个很不错的算法,不过要注意,是大部分时候。在java虚拟机中,并没有使用这个算法来管理内存,其中最主要的原因就是它很难解决对象之间循环引用的问题。来,举个例子来理解,比如现在有两个对象objectA和objectB都有字段instance,赋值让objectA.instance = objectB, objectB.instance = objectA,除此之外没有任何其他引用,实际上这两个对象已经不可能再被访问了,但是因为它们两个互相引用这对方,导致它们的引用计数不为0,则算法不能通知GC收集器回收它们。所以这种算法不适合在虚拟机上使用,但是并不是说这个算法很垃圾,它可是在其他方面有很多著名的案例。
•可达性分析算法
JVM的主流实现是可达性分析,可达性分析在概念上其实也不难理解,它的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时(图论里面专业一点来说,就是从GC Roots到这个对象不可达),则证明对象是不可用的,大致可以像下图理解。
<ignore_js_op>![]()
那么哪些对象可以作为GC Roots对象呢?在java中大致有如下几种:
•虚拟机栈(栈帧中的本地变量表)中引用的对象;(不知道栈帧是啥的看我另一篇文章,运行时数据区)
•方法区中类静态属性引用的对象;
•方法区常量引用的对象;
•本地方法栈中JNI(即一般说的Native方法)引用的对象
•引用
引用是啥?搞过C++的我们第一反应就会回答,如果reference类型的数据中储存的数值代表的是另一个内存的起始地址,就称这块内存代表着一个引用。这种定义没有错,不过太狭隘了,一个对象在这种定义下只能被引用或者没有被引用两种状态,显然在回收中不足以应付碰到的情况。所以,java对引用概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种,这四种引用强度一次逐渐减弱。
•强引用,就是指在程序代码之中普遍存在的,类似A a = new A()这样的引用,只要强引用存在,垃圾回收器就不会回收掉被引用的对象;
•软引用,用来描述一些还有用但并非必须的对象,对于软引用的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会出现内存溢出异常;
•弱引用,也是用来描述非必需的对象,但是它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次回收发生之前,当垃圾回收器工作时,无论当前内存是否足够,都会回收掉;
•虚引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例、为一个对象设置引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
•不可达必须“死”?
其实在实际中,就算在可达性分析算法中不可达的对象,也并非一定会回收,这个时候不可达的对象暂时处于暂缓的阶段,一个对象要真正宣告死亡,至少要经历两次标记的过程,当对象进行可达性分析而不可达时,它会被第一次标记并且进行一次筛选,筛选条件是这个对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被调用过了,虚拟机将会把这两种情况视为没有必要执行finalize。当对象被判定有必要执行finalize时,对象将会被放置在一个叫做F-Queue的队列中,并在稍后的一个由虚拟机自动建立的、优先级低的一个Finalizer线程去出发这些对象的finalize(要注意的是,虚拟机并不承诺会等待这些对象finalize方法执行结束,这是因为如果一个对象的finalize方法执行缓慢、或者发生死循环,将导致F-Queue队列其他对象处于永久等待,甚至导致内存回收系统崩溃)。finalize是对象逃脱回收的最后一次机会,GC会将F-Queue中的对象进行第二次小规模的标记,如果对象在finalize中重新和引用链连上了,那么就被移出回收集合,没有逃脱则将会被回收(要记住哦,对象的finalize只能被执行一次,也就是说当对象通过finalize逃脱回收之后,下一次如果再被可达性分析标记,那么就逃不了了)。
•最后
其实很多时候我们谈论回收都在java堆上进行的,上面对象实例都是在java堆上进行的,很少谈及方法区的回收,因为方法区(一般被称为永久代)中的回收条件很苛刻,比如在java堆上进行回收可以达到70%-95%的空间,在方法区却低很低,但并不代表方法区不能有垃圾回收,Java虚拟机规范中,只是说可以不要求在方法区实现回收机制。
更多技术资讯可关注:itheimaGZ获取
JVM如何判断对象能否被回收的更多相关文章
- Gc如何判断对象可以被回收?
Gc如何判断对象可以被回收? 1 引用计数器 引用计数法的算法思路:给对象增加一个引用计数器,每当对象增加一个引用计数器+1,失去一个引用-1,所以当计数器是0的时候对象就没有引用了,就会被认为可回收 ...
- 【Java面试】JVM如何判断一个对象可以被回收
Hi, 我是Mic. 今天分享一道一线互联网公司必问的面试题. "JVM如何判断一个对象可以被回收" 关于这个问题,来看看普通人和高手的回答. 普通人: 嗯.......... 高 ...
- jvm如何判断对象是否可以被回收
内容基本来自周志明 深入理解Java虚拟机 第二版 第三章 .这本书还可以,不过好像也没什么其他中文的关于jvm比较好的书了 jvm要做垃圾回收时,首先要判断一个对象是否还有可能被使用.那么如何判断一 ...
- JVM垃圾收集器&对象的引用回收
1.介绍垃圾收集器 垃圾收集器(Garbage Collection,GC)就是用于回收方法区和堆区,其他程序计数器.虚拟机栈.本地方法栈这3个区域都是随线程而生,随线程而灭,栈中的栈帧会随着方法的进 ...
- JVM中判断对象是否存活的方法
Java中几乎所有的对象实例都存放在堆中,在垃圾收集器对堆内存进行回收前,第一件事情就是要确定哪些对象还“存活”,哪些对象已经“死去”(即不可能再通过任何途径被使用). 引用计数算法 首先需要声明,至 ...
- JVM的内存分配与垃圾回收策略
自动内存管理机制主要解决了两个问题:给对象分配内存以及回收分配给对象的内存. >>垃圾回收的区域 前面的笔记中整理过虚拟机运行数据区,再看一下这个区域: 注意在这个Runtime Data ...
- JVM-如何判断对象存活与否与CMS收集器和G1收集器的区别
JVM如何判断对象存活? 1.计数器 2.可达性分析 (很多主流语言采用这种方法来判断对象是否存活) 计数器:每当有一个地方引用该对象时,计数器 +1:引用失效则 -1: 优点:实现简单,判定效率 ...
- JVM GC之对象生死
1.简述 在Java内存运行时区域的各个部分中,程序计数器.虚拟机栈.本地方法栈3个区域随着线程而生,随着线程而亡.栈中的栈帧随着方法的进入和退出而有条不紊的进行着入栈和出栈操作. 每个栈帧需要分配多 ...
- 2.1.JVM的垃圾回收机制,判断对象是否死亡
因为热爱,所以坚持. 文章下方有本文参考电子书和视频的下载地址哦~ 这节我们主要讲垃圾收集的一些基本概念,先了解垃圾收集是什么.然后触发条件是什么.最后虚拟机如何判断对象是否死亡. 一.前言 我们 ...
随机推荐
- nodejs(8) 使用ejs渲染动态页面
使用ejs渲染动态页面 步骤: 安装 ejs 模板引擎npm i ejs -S 使用 app.set() 配置默认的模板引擎 app.set('view engine', 'ejs') 使用 app. ...
- Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path 解决过程
原因:log4j-over-slf4j和slf4j-log4j12是跟Java日志系统相关的两个jar包,如果同时出现,就可能会引起堆栈异常 解决:找到依赖冲突发生位置,排除一个即可. 问题是 如何找 ...
- 导入的Java Web项目提示找不到javax.servlet.http.*包
在网上下载了个Java web项目,导入到eclipse发现以下错误 解决办法: 1.右击项目,进入Configure Build Path 2.在Libraries标签下点击Add Library. ...
- Python笔记_第四篇_高阶编程_再议装饰器和再议内置函数
1. 概述: 我们在前面用了很多的装饰器这个工具的方法.这个位置要系统的讲一下装饰器. 1.2 为什么需要装饰器. 装饰器本质是一个Python函数,它可以让其他函数在不需要任何代码变动的前提下增加额 ...
- python装饰器类
from functools import wraps class logit(object): def __init__(self, logger): self.logger = logger de ...
- 吴裕雄--天生自然Linux操作系统:Linux 忘记密码解决方法
忘记Linux系统的root密码,linux系统忘记root密码的情况该怎么办呢?重新安装系统吗?当然不用!进入单用户模式更改一下root密码即可. 步骤如下: 重启linux系统 3 秒之内要按一下 ...
- 远程SSH服务使用指南
Author Email Yaoyao Liu yaoyaoliu@msn.com 本文所有教程以ubuntu为例,对其他unix内核系统如Debian.CentOS.macOS等也适用. 目录 安装 ...
- js操作元素导致元素错位和大小改变
使用js循环的方式批量控制元素的大小时结果往往不尽如人意. 我总结了一条规律 在一个循环体内不可以同时存在一下两种操作,否则容易导致元素错位或大小改变: 1.对元素的offsetWidth.offse ...
- ⼩程序中⽀持es7的async语法
⼩程序中⽀持es7的async语法 es7的 async 号称是解决回调的最终⽅案 在⼩程序的开发⼯具中,勾选 es6转es5语法 下载 facebook 的 regenerator 库中的 rege ...
- Long型转ZonedDateTime型
/** * 将Long类型转化成0 * @author yk * @param time * @return */public static ZonedDateTime toZonedDateTime ...