揭示同步块索引(中):如何获得对象的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同步块用来避免竞争.本 ...
随机推荐
- Sql Server的还原和导入数据效果不一样
SQL SERVER2012数据库,导入数据和通过备份还原的效果不一样,如果之前的数据库结构修改过,那么另一个数据在导这个数据库时就会有问题,数据有丢失,这时候就要通过备份还原来同步数据.
- C++中输入输出十六进制八进制
本文参考链接:https://www.cnblogs.com/hxsyl/archive/2012/09/18/2691693.html,经重新实验得此文 1.进制问题 默认情况下使用cin和cout ...
- Dapper 条件语句(Where) 中参数使用
public static List<ECInput> GetECInputList(DateTime beginDate,DateTime endDate,string[] barcod ...
- C++实现矩阵压缩
C++实现矩阵压缩 转置运算时一种最简单的矩阵运算.对于一个m*n的矩阵M,他的转置矩阵T是一个n*m的矩阵,且T(i,j) = M(j,i). 一个稀疏矩阵的转置矩阵仍然是稀疏矩阵. 矩阵转置 方案 ...
- ana3+opencv+TensorFlow+NVIDIAGPU 安装
http://blog.csdn.net/qq_30611601/article/details/79067982 这个博客写的挺完整的 当你发现你的anna下载的贼鸡儿的慢,你就需要使用清华的镜像网 ...
- jsp获取绝对路径
在JavaWeb开发中,常使用绝对路径的方式引入javaScript和CSS文件,这样可以避免因为目录变动导致引入文件找不到的情况,常用的做法是: 一.使用${pageContext.request. ...
- HUST 1328 String (字符串前缀子串个数 --- KMP)
题意 给定一个字符串S,定义子串subS[i] = S[0..i],定义C[i]为S中subS[i]的数量,求sigma(C[i])(0<=i<N). 思路 我们以子串结尾的位置来划分阶段 ...
- IOS UI-UISearchController
ViewController.m // // ViewController.m // IOS_0224_查找联系人 // // Created by ma c on 16/2/24. // Copyr ...
- Qt中使用ActiveX控件
(转自:http://blog.csdn.net/tingsking18/article/details/5403038) 在Qt中使用ActiveX控件 Qt的windows商业版本提供了Activ ...
- jQuery实现select三级联动
参考:jQuery权威指南jQuery初步jQuery选择器jQuery操作domjQuery操作dom事件jQuery插件jQuery操作AjaxjQuery动画与特效jQuery实现导航栏jQue ...