从JVM层面带你分析Java的Object类源码第一部分

大家好,我是小图灵视界,最近在分析Java8源码,使用的JDK是OpenJDK8,打算将分析源码的笔记都会分享出来,在头条上代码排版比较难看,想要笔记的可以关注并私信我。

Object 源码

位置:java.lang包

Object类是Java中最基本的类,是所有类的根。也就说,所有的类默认都会继承它,包括数组等,都要继承Object中的所有方法。Object类中大多数都是native方法,native就是本地方法,由关键native字修饰,这些方法不在java语言中实现,底层实现是的c/c++代码。主要的native方法如下:

<code> 

private

static

native

void

registerNatives

()

;

public

final

native

Class> getClass();

public

native

int

hashCode

()

;

protected

native

Object

clone

()

throws

CloneNotSupportedException;

public

final

native

void

notify

()

;

public

final

native

void

notifyAll

()

;

public

final

native

void

wait

(

long

timeout)

throws

InterruptedException;/<code>

private static native void registerNatives()

在Object类中,有static代码块,这个静态代码块调用了registerNatives()方法:

<code>

private

static

native

void

registerNatives

()

;

static

{ registerNatives(); }/<code>

registerNatives方法的作用是加载和注册本地C、C++语言函数,将Java的本地方法与JVM底层的C、C++语言函数对应起来,是连接java语言与底层语言的桥梁,registerNatives方法在其他类中也可能存在,如Class类。Java类的本地方法对应C、C++函数的规则是Java_包名_方法名,包名以下划线分隔,Object类的registerNatives对应着C语言函数是
Java_java_lang_Object_registerNatives,java_lang_Object是java全类名以下划线连接,registerNatives是Java中的方法,其中


Java_java_lang_Object_registerNatives这个C语言函数如下:

<code>

static

JNINativeMethod methods[] = { {

"hashCode"

,

"()I"

, (

void

*)&JVM_IHashCode}, {

"wait"

,

"(J)V"

, (

void

*)&JVM_MonitorWait}, {

"notify"

,

"()V"

, (

void

*)&JVM_MonitorNotify}, {

"notifyAll"

,

"()V"

, (

void

*)&JVM_MonitorNotifyAll}, {

"clone"

,

"()Ljava/lang/Object;"

, (

void

*)&JVM_Clone}, };

JNIEXPORT

void

JNICALL

Java_java_lang_Object_registerNatives

(JNIEnv *env, jclass cls)

{ (*env)->RegisterNatives(env, cls, methods,

sizeof

(methods)/

sizeof

(methods[

0

])); }/<code>


Java_java_lang_Object_registerNatives主要是注册Object类的hashCode、wait、notify、notifyAll、clone等方法,而getClass方法却不在这里加载。在Object类中,这些本地方法不是在Java层面实现的,所有在调用这些方法的时候,是调用了底层语言的具体实现。

在methods[]数组中,有多个Java本地方法对应JVM层面的函数,如Object中hashCode方法对应JVM中的JVM_IHashCode函数,返回的类型是()I,即返回的是整数类型。

public final native Class> getClass()

getClass()方法的作用是返回对象运行时的Class类型,java编译会将java类编译成以.class结尾的文件,这是编译生成的二进制字节码文件,用于JVM加载,Java跨平台是因为使用与平台无关的class二进制字节码文件,可以通过Jjava中的Class类获取对象的构造器、方法、属性、注解等相关信息:

<code>Object 

object

=new Object(); System.

out

.println(

object

.getClass()); Class> objectClass=

object

.getClass(); Constructor>[] constructors=objectClass.getConstructors(); System.

out

.println(

"Object 类的构造器:"

);

for

(Constructor

constructor

:constructors){ System.

out

.print(

constructor

); } Method[] methods=objectClass.getMethods(); System.

out

.println(

"Object 类的方法:"

);

for

(Method method:methods){ System.

out

.println(method); } Field[] fields=objectClass.getFields(); System.

out

.println(

"Object 类的属性:"

);

for

(Field field:fields){ System.

out

.println(field); }/<code>

getClass方法对应的底层C语言函数为
Java_java_lang_Object_getClass,具体实现为:

<code>JNIEXPORT jclass JNICALL
Java_java_lang_Object_getClass(JNIEnv *env, jobject 

this

) {

if

(

this

== NULL) { JNU_ThrowNullPointerException(env, NULL);

return

0

; }

else

{

return

(*env)->GetObjectClass(env,

this

); } }/<code>

当传入的对象this为null,直接抛出NullPointerException异常,否则调用GetObjectClass函数,GetObjectClass函数如下:

<code>JNI_ENTRY(jclass, jni_GetObjectClass(JNIEnv *env, jobject obj))
  JNIWrapper(

"GetObjectClass"

); DTRACE_PROBE2(hotspot_jni, GetObjectClass__entry, env, obj); HOTSPOT_JNI_GETOBJECTCLASS_ENTRY(env, obj); Klass* k = JNIHandles::resolve_non_null(obj)->klass(); jclass ret =(jclass) JNIHandles::make_local(env, k->java_mirror()); DTRACE_PROBE1(hotspot_jni, GetObjectClass__return, ret); HOTSPOT_JNI_GETOBJECTCLASS_RETURN( ret);

return

ret; JNI_END/<code>

上述的核心代码为:

<code>Klass* k = JNIHandles::resolve_non_null(obj)->klass();
/<code>

resolve_non_null方法主要作用是根据java对象引用找到JVM中引用对象oop,将Java对象转换为JVM中的引用对象,然后调用klass()方法找到元数据,resolve_non_null的方法为:

<code>

inline

oop JNIHandles::resolve_non_null(jobject handle) { assert(handle !=

NULL

,

"JNI handle should not be null"

); oop result = *(oop*)handle; assert(result !=

NULL

,

"Invalid value read from jni handle"

); assert(result != badJNIHandle,

"Pointing to zapped jni handle area"

); assert(result != deleted_handle(),

"Used a deleted global handle."

);

return

result; };/<code>

resolve_non_null方法的核心代码是oop result = (oop)handle;这句代码的意思是先将传入的Java对象转为oop实例,((oop*)handle),然后再获取oop的指针,这个指针就是引用对象实例的地址(*(oop*)handle)。然后调用klass()获取对象实例所属的元数据Klass,Klass是指向Class类型的指针。

<code>

inline

Klass* oopDesc::klass()

const

{

if

(UseCompressedClassPointers) {

return

Klass::decode_klass_not_null(_metadata._compressed_klass); }

else

{

return

_metadata._klass; } }/<code>

jclass ret =(jclass) JNIHandles::make_local(env, k->java_mirror())分为两步,先调用Klass的java_mirror()方法,java_mirror方法的作用是返回Klass元数据的镜像(oop),对应着jjava/lang/Class类的实例,ava_mirror方法如下:

<code> 

//

java/lang/Class instance mirroring

this

class

oop _java_mirror; oop java_mirror() const {

return

_java_mirror; }/<code>

最后通过 JNIHandles::make_local处理oop,然后返回处理过后的oop,make_local的代码如下:

<code>jobject JNIHandles::make_local(oop obj) {
  

if

(obj ==

NULL

) {

return

NULL

; }

else

{ Thread* thread = Thread::current(); assert(Universe::heap()->is_in_reserved(obj),

"sanity check"

);

return

thread->active_handles()->allocate_handle(obj); } }/<code>

在JNIHandles::make_local函数中,当传入的oop实例obj为空时,直接返回空,否则将处理过的结果。

public native int hashCode()

hashCode方法返回对象的哈希码,以整数形式返回。哈希表在java集合中如HashMap、HashSet中被广泛使用,主要作用是为了提高查询效率。hashCode方法一些规定/约定如下:

  • 在不修改对象的equals()方法时,同一个对象无论何时调用hashCode方法,每次调用hashCode方法必须返回相同的整数(哈希码)。此整数不需要在应用程序的一次执行与同一应用程序的另一次执行之间保持一致。
  • 如果两个对象相等(使用equals()方法判断),那么这两个对象调用hashCode方法必须返回相同的整数结果(哈希码)。
  • 如果两个对象不相等(使用equals()方法判断),两个对象调用hashCode方法返回的结果不一定不同,也就是不同的两个对象的hashCode方法返回的结果可以相同也可以不相同。但是不同对象返回的哈希码最好不同,这样在哈希表查询时效率会更高。
  • hashCode方法的使用:

    <code>

    Object

    o1=

    new

    Object

    ();

    Object

    o2=

    new

    Object

    ();

    Object

    o3=o1; System.out.println(

    "o1的hash的哈希码:"

    + o1.hashCode()); System.out.println(

    "o2的hash的哈希码:"

    + o2.hashCode()); System.out.println(

    "o3的hash的哈希码:"

    + o3.hashCode()); o1的hash的哈希码:

    399573350

    o2的hash的哈希码:

    463345942

    o3的hash的哈希码:

    399573350

    /<code>

    o1和o2是不同的对象,hashCode方法产生不同的哈希码, o1与o3是不同的对象,它们的哈希码不一样。hashCode的底层C++实现

    <code>

    static

    inline intptr_t get_next_hash(Thread *

    Self

    , oop obj) { intptr_t value =

    0

    ;

    if

    (hashCode ==

    0

    ) { value = os::random() ; }

    else

    if

    (hashCode ==

    1

    ) { intptr_t addrBits = cast_from_oop(obj) >>

    3

    ; value = addrBits ^ (addrBits >>

    5

    ) ^ GVars.stwRandom ; }

    else

    if

    (hashCode ==

    2

    ) { value =

    1

    ; }

    else

    if

    (hashCode ==

    3

    ) { value = ++GVars.hcSequence ; }

    else

    if

    (hashCode ==

    4

    ) { value = cast_from_oop(obj) ; }

    else

    { unsigned t =

    Self

    ->_hashStateX ; t ^= (t <

    11

    ) ;

    Self

    ->_hashStateX =

    Self

    ->_hashStateY ;

    Self

    ->_hashStateY =

    Self

    ->_hashStateZ ;

    Self

    ->_hashStateZ =

    Self

    ->_hashStateW ; unsigned v =

    Self

    ->_hashStateW ; v = (v ^ (v >>

    19

    )) ^ (t ^ (t >>

    8

    )) ;

    Self

    ->_hashStateW = v ; value = v ; } value &= markOopDesc::hash_mask;

    if

    (value ==

    0

    ) value =

    0xBAD

    ; assert (value != markOopDesc::no_hash,

    "invariant"

    ) ; TEVENT (hashCode: GENERATE) ;

    return

    value; }/<code>

    get_next_hash函数根据hashCode的值,来采用不同的hashCode计算方法,当hashCode=0时,是OpenJDK6、7的默认实现方法,此类方案返回一个Park-Miller伪随机数生成器生成的随机数;当hashCode=1时,通过将对象的内存地址,做移位运算后与一个随机数进行异或得到结果;当hashCode == 3,返回一个自增序列的当前值;当hashCode == 4时,返回当前对象的内存地址;当hashCode 为其他值时,是openJDK8 的hashCode 方法的默认实现。

    openJDK8 的默认hashCode的计算方法是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia's xorshift scheme随机数算法得到的一个随机数。xorshift算法是通过移位和与或计算,能够在计算机上以极快的速度生成伪随机数序列。算法如下:

    <code>

    unsigned

    long

    xor128

    ()

    {

    static

    unsigned

    long

    x=

    123456789

    ,y=

    362436069

    ,z=

    521288629

    ,w=

    88675123

    ;

    unsigned

    long

    t; t=(xˆ(x

    11

    ));x=y;y=z;z=w;

    return

    ( w=(wˆ(w>>

    19

    ))ˆ(tˆ(t>>

    8

    )) ); }/<code>

    Self->hashStateX 是随机数、Self->hashStateY 、Self->hashStateZ、Self->hashStateW 是三个确认的数,分别对应xorshift 算法的x、y、z、w。

    在启动JVM时,可以通过设置-XX:hashCode参数,改变默认的hashCode的计算方式。

    public boolean equals(Object obj)

    <code>

    public

    boolean

    equals

    (Object obj)

    {

    return

    (

    this

    == obj); }/<code>

    equals方法判断两个对象是否相等,在这个方法中,直接用this==obj进行比较,返回this和obj比较的结果,“==“符号是比较两个对象是否是同一个对象,比较的是两个对象内存地址是否相等。子类在继承Object类的时候,一般需要重写equals方法,如果不重写,那么就默认父类Object的方法,在JDK源码中,很多对象都重写equals方法,比如String类。

    对于非空的对象引用,equals方法有几个性质:

  • 自反性:对于任何非空对象x和y,如果x.equals(y) 的结果为true,那么y.equals(x) 的结果也为true。
  • 传递性:对于任何非空对象x、y和z,如果x.equals(y)和y.equals(z)的结果都为true,那么x.equals(z)的结果也为true。
  • 一致性:对于任何非空对象x和y,如果未修改equals方法,多次调用equals方法结果始终为true或者false,不能这次返回true,下次返回false。
  • 对于任何非空对象x,x.equals(null)的结果是false。
  • 子类在继承Object,重写equals方法时,必须重写hashCode方法,在hashCode约定中,如果两个对象相等,hashCode必须返回相同的哈希码。很多时候,子类在重写父类Object的equals方法时,往往会忘了重写hashCode,当重写了equals但没有重写hashCode方法时,那么该子类无法结合java集合正常运行,因为java集合如HashMap、HashSet等都是基于哈希码进行存储的。

    protected native Object clone() throws CloneNotSupportedException;

    clone()本地方法,创建并返回此对象的副本,该方法使用protected 修饰,Object不能直接调用clone()方法,会编译错误:

    <code> 

    Object

    o=

    new

    Object

    ();

    Object

    o1= o.clone();/<code>

    如果想要使用clone()方法,子类必须重写这个方法,并用public修饰。如果子类没有实现clone(),子类默认调用父类的clone方法,如下:

    <code>

    public

    class

    ObjectCloneTest

    {

    public

    static

    void

    main

    (

    String [] args

    ) throws CloneNotSupportedException { ObjectCloneTest o=

    new

    ObjectCloneTest(); ObjectCloneTest cloneTest= (ObjectCloneTest) o.clone(); System.

    out

    .println(cloneTest); } }/<code>

    但是运行时发生异常:

    <code>

    Exception

    in

    thread

    "

    main

    "

    java

    .lang

    .CloneNotSupportedException

    :

    com

    .lingheng

    .java

    .source

    .ObjectCloneTest

    at

    java

    .lang

    .Object

    .clone

    (

    Native

    Method

    )

    at

    com

    .lingheng

    .java

    .source

    .ObjectCloneTest

    .main

    (

    ObjectCloneTest

    .java

    :12)

    /<code>

    如果子没有实现Cloneable接口,当子类调用clone()方法时,会抛出


    CloneNotSupportedException异常。当子类实现Cloneable接口,程序运行会成功:

    <code> 
    

    public

    class

    ObjectCloneTest

    implements

    Cloneable

    {

    public

    static

    void

    main

    (String [] args)

    throws

    CloneNotSupportedException { ObjectCloneTest o=

    new

    ObjectCloneTest(); ObjectCloneTest cloneTest= (ObjectCloneTest) o.clone(); System.out.println(

    "打印cloneTest:"

    +cloneTest); } } 打印cloneTest:com.lingheng.java.source.ObjectCloneTest@

    6

    d6f6e28/<code>

    所以,想要使用clone()方法,除了继承Object外(默认继承),还需要实现Cloneable接口。所有的数组默认是实现了Cloneable接口的,数组的clone()返回数组类型:

    <code>

    public

    void

    arrayClone

    (

    ){ String[] s=

    new

    String[]{

    "hello"

    ,

    "world"

    }; System.

    out

    .println(s); System.

    out

    .println(s.clone());

    int

    [] num=

    new

    int

    []{

    1

    ,

    3

    }; System.

    out

    .println(num); System.

    out

    .println(num.clone()); } [/<code>

    对于任何对象x,具有以下几个约定:

    1. x.clone() != x 为true
    2. x.clone().getClass() == x.getClass() 为true,但这不是必须满足的要求,也就是说可能是false。按照约定,返回的对象应该通过调用super.clone来获得。如果一个类和它所有的超类(除了Object) 遵循这个约定,那就会x.clone().getClass() == x.getClass()。
    3. x.clone().equals(x)通常情况下为true,这不是必须满足的要求。

    对于约定1,因为拷贝是创建一个新的对象,所以拷贝的对象和原对象是不相等的。

    对于约定2,这个比较好理解,一个类和它所有的超类的clone方法都调用super.clone()获取返回对象,所以这个类的Class个原对象的Claas是一样的。clone返回的类型是Object类,也就是说,在clone中可以返回只要是Object的子类就可以了,这样的话,x.clone().getClass() == x.getClass()的结果为false;

    对于约定3,这不是必须满足的条件,当在clone中改变了对象的属性值,那么x.clone().equals(x)的结果就可能不是true了。

    分析了Java层面的clone的约定,我们从JVM层面看看clone底层的实现,在registerNatives函数中会加载clone的JVM实现JVM_Clone,JVM_Clone的实现如下:

    <code> 
    JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))
      JVMWrapper(

    "JVM_Clone"

    ); Handle obj(THREAD, JNIHandles::resolve_non_null(handle));

    const

    KlassHandle klass (THREAD, obj->klass()); JvmtiVMObjectAllocEventCollector oam;

    if

    (obj->is_array()) { guarantee(klass->is_cloneable(),

    "all arrays are cloneable"

    ); }

    else

    { guarantee(obj->is_instance(),

    "should be instanceOop"

    ); bool cloneable = klass->is_subtype_of(SystemDictionary::Cloneable_klass()); guarantee(cloneable == klass->is_cloneable(),

    "incorrect cloneable flag"

    ); } ----------------------------------分割线

    1

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

    if

    (!klass->is_cloneable()) { ResourceMark rm(THREAD); THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name()); }

    const

    int size = obj->size(); oop new_obj =

    NULL

    ;

    if

    (obj->is_array()) {

    const

    int length = ((arrayOop)obj())->length(); new_obj = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL); }

    else

    { new_obj = CollectedHeap::obj_allocate(klass, size, CHECK_NULL); } ----------------------------------分割线

    2

    ------------------------------------------------- assert(MinObjAlignmentInBytes >= BytesPerLong,

    "objects misaligned"

    ); Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj, (size_t)align_object_size(size) / HeapWordsPerLong); new_obj->init_mark(); BarrierSet* bs = Universe::heap()->barrier_set(); assert(bs->has_write_region_opt(),

    "Barrier set does not have write_region"

    ); bs->write_region(MemRegion((HeapWord*)new_obj, size));

    if

    (klass->has_finalizer()) { assert(obj->is_instance(),

    "should be instanceOop"

    ); new_obj = InstanceKlass::register_finalizer(instanceOop(new_obj), CHECK_NULL); }

    return

    JNIHandles::make_local(env, oop(new_obj)); JVM_END/<code>

    JVM_Clone的实现过程比较长,我将分割成三部分,分割线1上部分主要是检查拷贝的一些标志是否正确,判断传入的是数组还是对象,在上面我讲过所有的数组都默认实现了Cloneable接口,在这里就可以证明了。然后还判断了传入的对象是否继承Cloneable接口,没有继承的话,就是非法的,这是第一段逻辑。

    分割线中间部分的逻辑,主要是申请内存,供拷贝的时候使用。首先先判断是否实现Cloneable接口,如果没有抛出
    CloneNotSupportedException异常,然后根据传入的是数组还是对象,调用不同的方法进行申请不同大小的内存。代码的有段注释是”Make shallow object copy “,就是说这里的拷贝是浅拷贝。

    最后的逻辑是,做一些检查,最后返回新创建的拷贝对象。因为对象的属性可能被另外一个线程改变,所以进行的是原子性的拷贝,最后创建拷贝的对象进行返回。这些源码的注释很详细,有兴趣的可以具体看看其他的一些逻辑。

    public String toString()

    oString()返回对象的字符串表示形式,最好所有Object的子类都重写这个方法,Object类的toString方法返回的是"类名@哈希码的十六进制,代码实现如下:

    <code>

    public

    String

    toString

    ()

    {

    return

    getClass().getName() +

    "@"

    + Integer.toHexString(hashCode()); }/<code>

    当我们在代码输入使用System.out.println输入对象的时候,会调用这个对象的toString方法,返回的是toString的字符串。一般在重写的toString的过程,返回的字符串要易读的。

    Object类的源码先分析到这里,因为分析的笔记会比较长,我将用两篇文章来发表。想要笔记的可以关注并私信我。


    分享到:


    相關文章: