Object结构初探

前言:

本来lbq同学在这里已经将Object基本分析个底朝天了,然而最近看Shared Source Internal 看的手痒,于是小生还是不禁想补充一下,顺便也当自己理清一下思路好了 J, 如果您能从本文带来一点收获,当然是高兴不过了

本文逻辑

从Object的对象布局入手,结合SSCLI代码对于Object的结构做一次整体解析

引言

Object是所有对象的基础,如 ECMA335中所说,Common Language Runtime中提出Object的概念是为了兼容所有的编程语言,强类型如Java, 动态语言如Python等等. 在Object之上派生出 类型系统等等基本架构,而Object本身的设计则考虑到反射,GC回收等特性做了一些自己的设计。相关的资料很难查及,勉强入手,还望指教

Object结构

Figure 1: Object布局, FROM Shared Source CLI 2.0 Draft

看看Object.cpp中的Object Class (部分截取)

class Object
{
protected:
MethodTable
* m_pMethTab;
EEClass
* GetClass()
{
WRAPPER_CONTRACT;
return( GetMethodTable()->GetClass() );
}
TypeHandle GetTypeHandle();
TypeHandle GetTrueTypeHandle();

// Sync Block & Synchronization services

// Access the ObjHeader which is at a negative offset on the object (because of
// cache lines)
ObjHeader *GetHeader()
{
LEAF_CONTRACT;
return PTR_ObjHeader(PTR_HOST_TO_TADDR(this) - sizeof(ObjHeader));
}
friend
class ObjHeader;
};

在这里,有几个核心概念,需要解释。
  • SyncBlock

题外话: Lbq在这里讲过, 然而刚开始我去找SyncBlock.h和Object.h中却怎么也找不到SyncBlock这个变量,只存在GetSyncBlockIndex这个方法,只有对应的Index那保存这个Index的Table呢? 他的名字是g_pSyncTable, 他是一个全局表用来存储所有的SyncBlock.

SyncBlock这个名称其实不太符合实际作用,他有两种作用

  1. 同步锁的作用
  2. 作垃圾回收时的索引

他是怎么达到的呢? 查看下代码

    // Access to the Sync Block Index, by masking the Value.
DWORD GetHeaderSyncBlockIndex()
{
LEAF_CONTRACT;

// pull the value out before checking it to avoid race condition
DWORD value = m_SyncBlockValue;
if ((value & (BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE)) != BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX)
return 0;
return value & MASK_SYNCBLOCKINDEX;
}

我们看到,其实m_SyncBlockValue用一个DWORD大小的值来标示两种状态. 算法是在返回之前会与对应的BIGFLAGS进行一次与,或运算

我们看其中两个, 更多的定义在CLR/SRC/VM/ObjHeader.h 的注释里

// This lock is only taken when we need to modify the index value in m_SyncBlockValue. 
// It should not be taken if the object already has a real syncblock index.
#define BIT_SBLK_SPIN_LOCK 0x10000000
#define BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX 0x08000000
// if BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX is set,
// then if BIT_SBLK_IS_HASHCODE is also set, the rest of the dword is the hash code (bits 0 thru 25),
// otherwise the rest of the dword is the sync block index (bits 0 thru 25)
#define BIT_SBLK_IS_HASHCODE 0x04000000

 

当设置值时,则有

if (FastInterlockCompareExchange((LONG*)&m_SyncBlockValue,                                          newValue, oldValue) == oldValue)
{
return;
}
// 检查是否为符合范围的值

也许你会问,锁和垃圾回收是如何实现的呢?

对于锁, lock关键字锁对应的代码为

lock(obj);
// Equals to code below
Monitor.Enter(obj);
try
{
// selection here
}
finally
{
Montior.Exit(obj);
}

对于垃圾回收,.Net中的垃圾回收采用的是标记清扫(MarkUp-Sweep)算法,即每个对象都存在一个标记为来标示状态,如果没有对象使用,即标记为某种状态,但有对象使用则标记为另一种状态,而在垃圾回收的过程中,则将该对象标示为锁状态,这样来避免一些可能的问题。如想知道更多,请查看本文最后的参考.
  • MethodTable 和 EEClass

首先看看Object.h中对于MethodTable和EEClass的获取方式

MethodTable:

  protected:
MethodTable
* m_pMethTab;

public:
#define MARKED_BIT 0x1

MethodTable
*GetMethodTable() const
{
LEAF_CONTRACT;

#if !defined(DACCESS_COMPILE)
// We should always use GetGCSafeMethodTable() if we're running during a GC.
// If the mark bit is set then we're running during a GC
_ASSERTE((((size_t)m_pMethTab) & MARKED_BIT) == 0);
#endif // !DACCESS_COMPILE

return PTR_MethodTable((TADDR) m_pMethTab);
}

EEClass:
   EEClass*        GetClass()                          
{
WRAPPER_CONTRACT;
return( GetMethodTable()->GetClass() );
}

可以看到GetClass的方法说调用的是MethodTable中的GetClass方法来获取EEClass,我们进去看看这个方法

inline EEClass* MethodTable::GetClass()
{
LEAF_CONTRACT;

// the only time a MT can have a NULL EEClass, and normal callers will never encounter this case.
// the above PREFIX_ASSUME slows down the checked build (see CHECK class in inc\check.h)
// this is a frequent call site, it needs faster asserts
_ASSERTE_IMPL(m_wFlags2 != enum_flag2_IsAsyncPin);
_ASSERTE_IMPL(m_pEEClass
!= NULL);

return m_pEEClass;
}

我们再看看EEClass的定义 位于clr/src/vm/class.h

private:
PTR_Module m_pModule;
mdTypeDef m_cl;
PTR_MethodTable m_pMethodTable;

// NOTE: Place items that are WORD sized or smaller together, otherwise padding will be used implicitly by the C++ compiler
WORD m_wCCtorSlot;
WORD m_wDefaultCtorSlot;
BYTE m_NormType;
WORD m_wNumInstanceFields;
WORD m_wNumStaticFields;
WORD m_wNumHandleStatics;
WORD m_wNumBoxedStatics;
WORD m_wNumGCPointerSeries;

DWORD m_cbModuleDynamicID;
DWORD m_cbNonGCStaticFieldBytes;
DWORD m_dwNumInstanceFieldBytes;
#ifndef DACCESS_COMPILE
FieldDesc
*m_pFieldDescList; // 保存字段列表
#else // DACCESS_COMPILE
FieldDesc
* m_pFieldDescList_UseAccessor;
#endif // DACCESS_COMPILE
DWORD m_dwAttrClass;
volatile DWORD m_VMFlags;
SecurityProperties m_SecProps;

PTR_MethodDescChunk m_pChunks;
// 保存方法表数据
在代码中我们可以看到很多的方法是关于Class的基本结构,方法表,字段表等等 按照Shared Source Internals的说法,MethodDesc和FiledDesc都是表述方法和字段信息,里面包含的是一些字段标示. 在Shared Source Internals 2.0中书中将EEClass和MethodTable称之为Hot State和Code State,意指MethodTable里保存的是一些常用状态,而EEClass则存在于Meta data中,每次访问会有性能损失。在看注释时(clr/src/vm/class.cpp)中提到了EEClass在2.0中已经对于普通的代码关闭了访问,能访问只能通过些不寻常的代码,例如异常,
是否意味着2.0中的反射实际上是建立在MethodTable之上而于EEClass毫无关系了?
是否EEClass只是在编译器和运行期建立MethodTable,组织关系之用呢?

关于MethodDescChunk, MethodTable, 当然还有那VTable, lbq这篇文章讲的很精彩 参考里也有链接

参考

Custom Object Layout for Garbage-Collected Languages

Explanatory notes on Rotor's Garbage Collector

给 C 实现一个垃圾收集器

Interoperability & COM

Gyro: Generics for Rotor

Shared Source Internals 2.0

CLR探索系列:System.Object内存布局模型及实现研究

希望本文能给你有所帮助

posted on 2008-11-23 21:30 xwang 阅读(...) 评论(...) 编辑 收藏

导航

统计