揭示同步块索引(中):如何获得对象的HashCode
转自:http://www.cnblogs.com/yuyijq/archive/2009/08/13/1545617.html
题外话:为了尝鲜,也兴冲冲的安装了Win7,不过兴奋之余却郁闷不已,由于是用Live Writer写博客,写了好几篇草稿,都完成了80%左右,没有备份全部没了。欲哭无泪,只好重写了。
Visual Studio + SOS 小实验
咋一看标题,觉得有些奇怪,同步块索引和HashCode有啥关系呢。从名字上来看离着十万八千里。在不知道细节之前,我也是这样想的,知道细节之后,才发现这两兄弟如此亲密。我们还是先来用Visual Studio + SOS,看一个东西,下面是作为小白兔的示例代码:
1: using System;
2: public class Program
3: {
4: static void Main()
5: {
6: Foo f = new Foo();
7: Console.WriteLine(f.GetHashCode());
8:
9: Console.ReadLine();
10: }
11: }
12: //就这么一个简单的类
13: public class Foo
14: {
15:
16: }
(使用Visual Studio + SOS调试的时候,请先在项目的属性,调试栏里设置“允许非托管代码调试”)
我们分别在第7行,第9行设置断点,F5运行,当程序停在第一个断点处时(此时f.GetHashCode()还没有执行),我们在Visual Studio的立即窗口里输入:
1: .load sos.dll
2: extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded
3: !dso
4: PDB symbol for mscorwks.dll not loaded
5: OS Thread Id: 0x1730 (5936)
6: ESP/REG Object Name
7: 0013ed78 01b72d58 Foo
8: 0013ed7c 01b72d58 Foo
9: 0013efc0 01b72d58 Foo
10: 0013efc4 01b72d58 Foo
使用.load sos.dll加载sos模块,然后使用!dso,我们找到了Foo类型的f对象的内存地址:01b72d58,然后使用Visual Studio调试菜单下的查看内存的窗口,查看f对象头部的内容:
阴影遮住的00 00 00 00就是同步块索引所在的地方了,可以看得出来,此时同步块索引的值还是0(后面会对这个做解释),然后继续F5,程序运行到下一个断点处,这个时候f.GetHashCode()也已调用了,细心的你就会发现,原来对象同步块索引所在的地方的值变了:
Visual Studio这个内存查看器有个很好的功能,对内存变化的以红色标出。我们看到,原来是00 00 00 00变成了现在的4a 73 78 0f。嗯,看来HashCode的获取和同步块索引还是有一些关系的,不然调用GetHashCode方法为什么同步块索引的值会变化呢。再来看看Console.WriteLine(f.GetHashCode())的输出:
不知道着两个值有没有什么关系,我们先把它们都换算成二进制吧。注意,这里的4a 73 78 0f是低位在左,高位在右,下面的十进制是高位再左,低位在右,那4a 73 78 0f实际上就是0x0f78734a了。
0x0f78734a:000011
58225482:000000
Rotor源代码
我们先用0补齐32位,突然发现这两者低26位居然是一模一样的(红色标出的部分),这是巧合还是必然?为了一探究竟只好搬出Rotor的源代码,从源代码里看看是否能发现什么东西。还是遵循老路子,我们先从托管代码开始:
1: public virtual int GetHashCode()
2: {
3: return InternalGetHashCode(this);
4: }
5: [MethodImpl(MethodImplOptions.InternalCall)]
6: internal static extern int InternalGetHashCode(object obj);
在本系列的第一篇文章已经提到过,标记有[MethodImpl(MethodImplOptions.InternalCall)]特性的方法是使用Native Code的方式实现的,在Rotor中,这些代码位于sscli20\clr\src\vm\ecall.cpp文件中:
1: FCFuncElement("InternalGetHashCode", ObjectNative::GetHashCode)
2: FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) {
3: DWORD idx = 0;
4: OBJECTREF objRef(obj);
5: idx = GetHashCodeEx(OBJECTREFToObject(objRef));
6: return idx;
7: }
8: FCIMPLEND
9: INT32 ObjectNative::GetHashCodeEx(Object *objRef)
10: {
11: // This loop exists because we're inspecting the header dword of the object
12: // and it may change under us because of races with other threads.
13: // On top of that, it may have the spin lock bit set, in which case we're
14: // not supposed to change it.
15: // In all of these case, we need to retry the operation.
16: DWORD iter = 0;
17: while (true)
18: {
19: DWORD bits = objRef->GetHeader()->GetBits();
20:
21: if (bits & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX)
22: {
23: if (bits & BIT_SBLK_IS_HASHCODE)
24: {
25: // Common case: the object already has a hash code
26: return bits & MASK_HASHCODE;
27: }
28: else
29: {
30: // We have a sync block index. This means if we already have a hash code,
31: // it is in the sync block, otherwise we generate a new one and store it there
32: SyncBlock *psb = objRef->GetSyncBlock();
33: DWORD hashCode = psb->GetHashCode();
34: if (hashCode != 0)
35: return hashCode;
36:
37: hashCode = Object::ComputeHashCode();
38:
39: return psb->SetHashCode(hashCode);
40: }
41: }
42: else
43: {
44: // If a thread is holding the thin lock or an appdomain index is set, we need a syncblock
45: if ((bits & (SBLK_MASK_LOCK_THREADID | (SBLK_MASK_APPDOMAININDEX << SBLK_APPDOMAIN_SHIFT))) != 0)
46: {
47: objRef->GetSyncBlock();
48: // No need to replicate the above code dealing with sync blocks
49: // here - in the next iteration of the loop, we'll realize
50: // we have a syncblock, and we'll do the right thing.
51: }
52: else
53: {
54: DWORD hashCode = Object::ComputeHashCode();
55:
56: DWORD newBits = bits | BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE | hashCode;
57:
58: if (objRef->GetHeader()->SetBits(newBits, bits) == bits)
59: return hashCode;
60: // Header changed under us - let's restart this whole thing.
61: }
62: }
63: }
64: }
代码很多,不过大部分操作都是在做与、或、移位等。而操作的对象就是这行代码获取的:objRef->GetHeader()->GetBits(),实际上就是获取同步块索引。
想想,在第一个断点命中的时候,同步块索引的值还是0x00000000,那应该是下面这块代码执行:
1: DWORD hashCode = Object::ComputeHashCode();
2: DWORD newBits = bits | BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE | hashCode;
3: if (objRef->GetHeader()->SetBits(newBits, bits) == bits)
4: return hashCode;
通过Object的ComputeHashCode方法算出一个哈希值来(由于本文不是关注哈希算法的,所以这里不讨论这个ComputeHashCode方法的实现)。然后进行几个或操作(这里还要与原先的bits或操作是为了保留原来的值,说明这个同步块索引还起了别的作用,比如上篇文章的lock),然后将同步块索引中老的位换掉。从这里我们还看不出来什么。不过,如果我们再次对这个对象调用GetHashCode()方法呢?那同步块索引不再为0x00000000,而是0x0f78734a,在来看看几个定义的常量的值:
1: #define BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX 0x08000000
2: #define BIT_SBLK_IS_HASHCODE 0x04000000
3: #define HASHCODE_BITS 26
4: #define MASK_HASHCODE ((1<<HASHCODE_BITS)-1)
从刚才设置hashcode的地方可以看到:DWORD newBits = bits | BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE | hashCode;
所以开头的两个if都可以通过了,返回的hashcode就是bits & MASK_HASHCODE。
这个MASK_HASHCODE是将1向左移26位=100000000000000000000000000,然后减1=00000011111111111111111111111111(低26位全部为1,高6位为0),然后与同步块索引相与,其实这里的作用不就是为了取出同步块索引的低26位的值么。再回想一下本文开头的那个试验,原来不是巧合啊。
连上上一篇,我们可以看到同步块索引不仅仅起到lock的作用,有时还承担着存储HashCode的责任。实际上同步块索引是这样的一个结构:总共32位,高6位作为控制位,后26的具体含义随着高6位的不同而变化,高6位就像很多小开关,有的打开(1),有的关闭(0),不同位的打开和关闭有着不同的意义,程序也就知道低26位到底是干啥的了。这里的设计真是巧妙,不断占用内存很紧凑,程序也可以灵活处理,灵活扩展。
后记
本篇和上一篇一样,都是单独将独立的内容拿出来,这样可以更简单的来阐述。比如在本文中,我只设想同步块索引做hashcode的存储,这个时候,同步块索引就干干净净(本文前面的试验中先得到的同步块索引就是一个0),但实际中同步块索引可能担任更多的职责,比如既lock,又要获取HashCode,这个时候情况就更复杂,这个在后面一篇文章会综合各种情况更详细的说明。
揭示同步块索引(中):如何获得对象的HashCode的更多相关文章
- 揭示同步块索引(上):从lock开始
转自:http://www.cnblogs.com/yuyijq/archive/2009/03/13/1410071.html 大家都知道引用类型对象除实例字段的开销外,还有两个字段的开销:类型指针 ...
- C# CLR via 对象内存中堆的存储【类型对象指针、同步块索引】
最近在看书,看到了对象在内存中的存储方式. 讲到了对象存储在内存堆中,分配的空间除了类型对象的成员所需的内存量,还有额外的成员(类型对象指针. 同步块索引 ),看到这个我就有点不懂了,不知道类型对象指 ...
- [C#学习笔记]类型对象指针和同步块索引
写在前面 看<CLR via C#>第四章时,看到了类型对象指针和同步块索引这两个概念,不知如何解释,查看过相关资料之后,在此记录. 类型对象指针 <CLR via C#>中的 ...
- (转)为什么wait(),notify()和notifyAll()必须在同步块或同步方法中调用
我们常用wait(),notify()和notifyAll()方法来进行线程间通信.线程检查一个条件后就行进入等待状态,例如,在“生产者-消费者”模型中,生产者线程发现缓冲区满了就等待,消费者线程通过 ...
- .NET中 类型,对象,线程栈,托管堆在运行时的关系
.NET中 类型,对象,线程栈,托管堆在运行时的关系 The Relationship at Run Time between Types,Objects,A Thread's Stack,and T ...
- [读书心得] .NET中 类型,对象,线程栈,托管堆在运行时的关系
.NET中 类型,对象,线程栈,托管堆 在运行时的关系 The Relationship at Run Time between Types,Objects,A Thread's Stack,and ...
- java多线程-同步块
Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java 同步块用来避免竞争.本文介绍以下内容: Java 同步关键字(synchronzied) 实例方法同步 ...
- synchronized同步块和volatile同步变量
Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而 ...
- Java同步块
原文:http://ifeve.com/synchronized-blocks/ Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java同步块用来避免竞争.本 ...
随机推荐
- 关于Spring Test 小结
1.>public class CustomerPackagePrealertControllerTest extends WebSpringBaseTest{} 2.> @WebApp ...
- SQL Server配置管理器”远程过程调用失败“
在设置服务器远程连接的时候,打开SQL server配置管理器时,SQL server服务右侧显示“远程调用失败”. 解决方法: 在控制面板中找到 Microsoft SQL Server 2016 ...
- 基于JQuery 的消息提示框效果代码
提示框效果 一下是封装到 Jquery.L.Message.js 中的JS文件内容 var returnurl = ''; var messagebox_timer; $.fn.messagebox ...
- Codeforces Round #409
第一题很水但是wa了一发,因为没考虑K前面是K的情况 #include<map> #include<set> #include<cmath> #include< ...
- CodeForces - 91B单调队列
有一个数列,对于每一个数,求比它小的在他右边距离他最远的那个数和他的距离 用单调队列做,维护单调队列时可采用如下方法,对于每一个数,如果队列中没有数,则加入队列,如果队列头的数比当前数大,则舍弃该数 ...
- 20.并发容器之ArrayBlockingQueue和LinkedBlockingQueue实现原理详解
1. ArrayBlockingQueue简介 在多线程编程过程中,为了业务解耦和架构设计,经常会使用并发容器用于存储多线程间的共享数据,这样不仅可以保证线程安全,还可以简化各个线程操作.例如在“生产 ...
- write 系统调用耗时长的原因
前一阵子公司一部门有人叫帮忙调查,说他们write系统调用基本上是个位数微秒就返回,或者说几十us,但偶尔出现几次write系统调用达到几百毫秒和情况.大家都知道,通过vfs进行write,都是写写到 ...
- Tracing on Linux
The Linux tracing APIs are a relatively new addition to the kernel and one of the most powerful new ...
- iptables详解(11):iptables之网络防火墙
我们一起来回顾一下之前的知识,在第一篇介绍iptables的文章中,我们就描述过防火墙的概念,我们说过,防火墙从逻辑上讲,可以分为主机防火墙与网络防火墙. 主机防火墙:针对于单个主机进行防护. 网络防 ...
- 无密码登陆的ssh和ssh-agent
原文地址:http://lxshopping.blog.51cto.com/4542643/1179864/ 一,不需要输密码的ssh 原理:首先服务器端把公钥传给Client端,Client端在验证 ...