JVM-如何判断一段数据是真正的数据,还是对象的引用

JVM 判断一段数据到底是数据还是引用类型,首先要看JVM选择用什么方式。通常这个选择会影响到GC的实现。

一、保守式

如果JVM选择不记录任何这种类型的数据,那么它就无法区分内存里某个位置上的数据到底应该解读为引用类型还是整型还是别的什么。这种条件下,实现出来的GC就会是“保守式GC(conservative GC)”。
在进行GC的时候,JVM开始从一些已知位置(例如说JVM栈)开始扫描内存,扫描的时候每看到一个数字就看看它“像不像是一个指向GC堆中的指针”。
里会涉及上下边界检查(GC堆的上下界是已知的)、对齐检查(通常分配空间的时候会有对齐要求,假如说是4字节对齐,那么不能被4整除的数字就肯定不是指
针),之类的。然后递归的这么扫描出去。

保守式GC的好处是相对来说实现简单些,而且可以方便的用在对GC没有特别支持的编程语言里提供自动内存管理功能。Boehm-Demers-Weiser GC是保守式GC中的典型代表,可以嵌入到C或C++等语言写的程序中。

小历史故事:

微软的JScript和早期版VBScript也是用保守式GC的;微软的JVM也是。VBScript后来改回用引用计数了。而微软JVM的后代,也就是.NET里的CLR,则改用了完全准确式GC。

为了赶上在一个会议上发布消息,微软最初的JVM原型只有一个月左右的时间从开工到达到符合Java标准。所以只好先用简单的办法来实现,也就自然选用了保守式GC。

信息来源:Patrick Dussud在Channel 9的访谈,23分钟左右

保守式GC的缺点有:

1、会有部分对象本来应该已经死了,但有疑似指针指向它们,使它们逃过GC的收集。这对程序语义来说是安全的,因为所有应该活着的对象都会是活
的;但对内存占用量来说就不是件好事,总会有一些已经不需要的数据还占用着GC堆空间。具体实现可以通过一些调节来让这种无用对象的比例少一些,可以缓解
(但不能根治)内存占用量大的问题。

2、由于不知道疑似指针是否真的是指针,所以它们的值都不能改写;移动对象就意味着要修正指针。换言之,对象就不可移动了。有一种办法可以在使用
保守式GC的同时支持对象的移动,那就是增加一个间接层,不直接通过指针来实现引用,而是添加一层“句柄”(handle)在中间,所有引用先指到一个句
柄表里,再从句柄表找到实际对象。这样,要移动对象的话,只要修改句柄表里的内容即可。但是这样的话引用的访问速度就降低了。Sun
JDK的Classic VM用过这种全handle的设计,但效果实在算不上好。

【也就是所说的 通过句柄实现对象的访问】l另一种方式就是通过直接指针访问内存。

由于JVM要支持丰富的反射功能,本来就需要让对象能了解自身的结构,而这种信息GC也可以利用上,所以很少有JVM会用完全保守式的GC。除非真的是特别懒…

------------------------------------------

二、半保守
JVM可以选择在栈上不记录类型信息(同保守式),而在对象上记录类型信息。这样的话,扫描栈的时候仍然会跟上面说的过程一样,但扫描到GC堆内的对象时因为对象带有足够类型信息了,JVM就能够判断出在该对象内什么位置的数据是引用类型了。这种是“半保守式GC”,也称为“根上保守(conservative with respect to the roots)”。

为了支持半保守式GC,运行时需要在对象上带有足够的元数据。如果是JVM的话,这些数据可能在类加载器或者对象模型的模块里计算得到,但不需要JIT编译器的特别支持。

前面提到了Boehm GC,实际上它不但支持完全保守的方式,也可以支持半保守的方式。GCJMono都是以半保守方式使用Boehm GC的例子。

Google Android的Dalvik VM的早期版本也是使用半保守式GC的一个例子。不过到2009年中的时候Dalvik VM的内部版本就已经开始支持准确式GC了——代价是优化过的DEX文件的体积膨胀了约9%

其实许多较老的JVM都选择这种实现方式。

由于半保守式GC在堆内部的数据是准确的,所以它可以在直接使用指针来实现引用的条件下支持部分对象的移动,方法是只将保守扫描能直接扫到的对象设置为不可移动(pinned),而从它们出发再扫描到的对象就可以移动了。

完全保守的GC通常使用不移动对象的算法,例如mark-sweep。半保守方式的GC既可以使用mark-sweep,也可以使用移动部分对象的算法,例如Bartlett风格的mostly-copying GC

半保守式GC对JNI方法调用的支持会比较容易:管它是不是JNI方法调用,是栈都扫过去…完事了。不需要对引用做任何额外的处理。当然代价跟完全保守式一样,会有“疑似指针”的问题。

------------------------------------------

三、准确式

  与保守式GC相对的是“准确式GC”,原文可以是precise GC、exact GC、accurate GC或者type accurate GC。外国人也挺麻烦的,“准确”都统一不到一个词上?

是什么东西“准确”呢?关键就是“类型”,也就是说给定某个位置上的某块数据,要能知道它的准确类型是什么,这样才可以合理地解读数据的含义;GC所关心的含义就是“这块数据是不是指针”。

要实现这样的GC,JVM就要能够判断出所有位置上的数据是不是指向GC堆里的引用,包括活动记录(栈+寄存器)里的数据。

有几种办法:

1、让数据自身带上标记(tag)。这种做法在JVM里不常见,但在别的一些语言实现里有体现。就不详细介绍了。打标记的方式在半保守式GC中倒
是更常见一些,例如CRuby就是用打标记的半保守式GC。CLDC-HI比较有趣,栈上对每个slot都配对一个字长的tag来说明它的类型,通过这种
方式来减少stack map的开销;类似的实现在别的地方没怎么见过,大家一般都不这么取舍。

2、让编译器为每个方法生成特别的扫描代码。我还没见过JVM实现里这么做的,虽说在别的语言实现里有见过。

3、从外部记录下类型信息,存成映射表。现在三种主流的高性能JVM实现,HotSpot、JRockit和J9都是这样做的。其
中,HotSpot把这样的数据结构叫做OopMap,JRockit里叫做livemap,J9里叫做GC map。Apache
Harmony的DRLVM也把它叫GCMap

要实现这种功能,需要虚拟机里的解释器和JIT编译器都有相应的支持,由它们来生成足够的元数据提供给GC。

使用这样的映射表一般有两种方式:

1、每次都遍历原始的映射表,循环的一个个偏移量扫描过去;这种用法也叫“解释式”;

2、为每个映射表生成一块定制的扫描代码(想像扫描映射表的循环被展开的样子),以后每次要用映射表就直接执行生成的扫描代码;这种用法也叫“编译式”。

在HotSpot中,对象的类型信息里有记录自己的OopMap,记录了在该类型的对象内什么偏移量上是什么类型的数据。所以从对象开始向外的扫描可以是准确的;这些数据是在类加载过程中计算得到的。

每个被JIT编译过后的方法也会在一些特定的位置记录下OopMap,记录了执行到该方法的某条指令的时候,栈上和寄存器里哪些位置是引用。这样GC在扫描栈的时候就会查询这些OopMap就知道哪里是引用了。这些特定的位置主要在:

1、循环的末尾

2、方法临返回前 / 调用方法的call指令后

3、可能抛异常的位置

这种位置被称为“安全点”(safepoint)。之所以要选择一些特定的位置来记录OopMap,是因为如
果对每条指令(的位置)都记录OopMap的话,这些记录就会比较大,那么空间开销会显得不值得。选用一些比较关键的点来记录就能有效的缩小需要记录的数
据量,但仍然能达到区分引用的目的。因为这样,HotSpot中GC不是在任意位置都可以进入,而只能在safepoint处进入。

只有到达安全点的时候才会停止当前正在执行的程序(Stop the world),去进行gc.

但是对于安全点,另外一个需要考虑的问题就是,如何在需要gc的时候,让所有的线程(不包括JNI调用的线程)都跑到安全点在停顿下来,这里包括联众方式:

1、抢占式中断

  不需要线程执行相关的代码主动去配合,在GC的时候,首先让所有的线程全部中断,如果发现有的线程没有到达安全点,就恢复线程,让他跑到安全点上。---如今很少只用这种抢占式的方式来响应gc

2、主动中断

  当gc需要中断线程的时候,不直接对线程进行操作,仅仅是设置一个标志位,各个线程主动去轮询这个标志位,当发现这个标志位为真时,该线程主动挂起,
而仍然在解释器中执行的方法则可以通过解释器里的功能自动生成出OopMap出来给GC用。

平时这些OopMap都是压缩了存在内存里的;在GC的时候才按需解压出来使用。

HotSpot是用“解释式”的方式来使用OopMap的,每次都循环变量里面的项来扫描对应的偏移量。

对Java线程中的JNI方法,它们既不是由JVM里的解释器执行的,也不是由JVM的JIT编译器生成的,所以会缺少OopMap信息。那么GC碰到这样的栈帧该如何维持准确性呢?

HotSpot的解决方法是:所有经过JNI调用边界(调用JNI方法传入的参数、从JNI方法传回的返回值)的引用都必须用“句柄”
(handle)包装起来。JNI需要调用Java
API的时候也必须自己用句柄包装指针。在这种实现中,JNI方法里写的“jobject”实际上不是直接指向对象的指针,而是先指向一个句柄,通过句柄
才能间接访问到对象。这样在扫描到JNI方法的时候就不需要扫描它的栈帧了——只要扫描句柄表就可以得到所有从JNI方法能访问到的GC堆里的对象。

但这也就意味着调用JNI方法会有句柄的包装/拆包装的开销,是导致JNI方法的调用比较慢的原因之一。

时间: 2024-09-30 22:08:27

JVM-如何判断一段数据是真正的数据,还是对象的引用的相关文章

jvm如何判断对象是否可以被回收

内容基本来自周志明 深入理解Java虚拟机 第二版 第三章 .这本书还可以,不过好像也没什么其他中文的关于jvm比较好的书了 jvm要做垃圾回收时,首先要判断一个对象是否还有可能被使用.那么如何判断一个对象是否还有可能被用到? 如果我们的程序无法再引用到该对象,那么这个对象就肯定可以被回收,这个状态称为不可达.当对象不可达,该对象就可以作为回收对象被垃圾回收器回收. 那么这个可达还是不可达如何判断呢? 答案就是GC roots ,也就是根对象,如果从一个对象没有到达根对象的路径,或者说从根对象开

oracle使用还原段的目的和还原数据的管理方法及还原段的类型

一.引入还原段主要有3个目的: 1.事务回滚:主要是针对rollback语句起作用 2.事务恢复:非正常关闭数据库即非保留事务级关闭数据库(abort.immediate)或者数据库instance崩溃,则当数据库再次打开时oracle服务器就要还原所有没有提及的事务.这种还原就是事务恢复的一部分.要使得恢复真正的成为可能写到还原段的变化也要受到联机重做日志的保护. 3.保证数据的读一致性. 二.还原数据的管理方法 1.自动的还原数据管理:oracle服务器自动的管理还原段的创建.分配和优化 2

判断两条线段是否相交

根据这个性质可以判断点p2是在线段的左边还是右边,这是判断两条线段是否相交的一个重要性质. 这是判断两条线段相交的一种情况,一条线段的端点在另一条线段上. 这是判断两条线段是否相交的原理. 1 #include <iostream> 2 #include <algorithm> 3 #include <cmath> 4 using namespace std; 5 6 struct point { 7 double x, y; 8 }; 9 10 bool segmen

[转]kafka要等一段时间才能消费到数据

kafka要等一段时间才能消费到数据  pythonkafka 为什么用python写的kafka客户端脚本,程序一运行就能生产数据,而要等一段时间才能消费到数据(topic里面有数据).(pykafka和confluentKafka都一样) 只有极少的概率立刻可以消费到数据,大多数都要等个几分钟,很影响测试效率. 自己封装的一个给予confluentKafka的consumer对象 调用,topic里面是有内容的等一段时间也是可以消费到数据的 阅读 4.8k 赞 1踩 收藏关注 6 评论 更新

使用聚合数据API查询快递数据-短信验证码-企业核名

有位朋友让我给他新开的网站帮忙做几个小功能,如下: 输入快递公司.快递单号,查询出这个快件的所有动态(从哪里出发,到了哪里) 在注册.登录等场景下的手机验证码(要求有一定的防刷策略) 通过输入公司名的关键词,查看这个公司是否已经注册.法人信息.有类似名称的公司等等 并且可以用的接口.文档都提供给我了.其中需求 1.2,都通过 聚合数据 这家网站提供的接口实现:需求 3 通过 云聚数据 来实现. 本项目的文件 因为朋友的网站是用 ThinkPHP 写的,为了保持将来代码的兼容,这三个功能也用 Th

大数据理论体系总结--数据仓库管理与全链路数据体系

前言 就这样,大数据领域蓬勃发展了好几年,有很多伙伴执迷于技术,成为了分布式计算与存储的领域专家.也有很多伙伴执迷于数据,成为了行业的数据研发专家.当然还有很多小伙伴,热衷于工具系统开发,成为了数据技术专家.那么我们回过头来考虑,什么是大数据,什么又是数据仓库,什么又是数据技术.大数据其实是个非常笼统的感念,它是由数据仓库演化而来的数据与技术方法论,那么我们先说一下数据仓库的由来: 早在多年以前在Hadoop.Spark.Storm.Kafka等系列分布式计算与存储.消息中间件还没有成熟的时候,

数据分析之数据质量分析和数据特征分析

1.数据质量分析 数据质量分析是数据挖掘中数据准备过程的重要一环,是数据预处理的前提,也是数据挖掘分析结论有效性和准确性的基础,没有可信的数据,数据挖掘构建的模型将是空中楼阁. 数据质量分析的主要任务是检查原始数据中是否存在脏数据,脏数据一般是指不符合要求,以及不能直接进行相应分析的数据.在常见的数据挖掘工作中,脏数据包括: (1)缺失值:     (2)异常值:     (3)不一致的值:     (4)重复数据及含有特殊符号(如#.¥.*)的数据. 1.1缺失值分析        数据的缺失

C#+HtmlAgilityPack+XPath带你采集数据(以采集天气数据为例子)

转自原文C#+HtmlAgilityPack+XPath带你采集数据(以采集天气数据为例子) 阅读目录 1.HtmlAgilityPack简介 2.XPath技术介绍与使用 3.采集天气网站案例 4.资源 第一次接触HtmlAgilityPack是在5年前,一些意外,让我从技术部门临时调到销售部门,负责建立一些流程和寻找潜在客户,最后在阿里巴巴找到了很多客户信息,非常全面,刚开始是手动复制到Excel,是真尼玛的累,虽然那个时候C#还很菜,也想能不能通过程序来批量获取(所以平时想法要多才好).几

我这么玩Web Api(二):数据验证,全局数据验证与单元测试

目录 一.模型状态 - ModelState 二.数据注解 - Data Annotations 三.自定义数据注解 四.全局数据验证 五.单元测试   一.模型状态 - ModelState 我理解的ModelState是微软在ASP.NET MVC中提出的一种新机制,它主要实现以下几个功能: 1. 保存客户端传过来的数据,如果验证不通过,把数据返回到客户端,这样可以保存用户输入,不需要重新输入. 2. 验证数据,以及保存数据对应的错误信息. 3. 微软的一种DRY(Don't Repeat