专业编程基础技术教程

网站首页 > 基础教程 正文

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

ccvgpt 2024-11-13 09:54:05 基础教程 6 ℃

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

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

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

Object 源码

位置:java.lang包

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

//加载本地方法
private static native void registerNatives();
//获取对象的类型 Class
public final native Class<?> getClass();
//获取hash码
 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;

private static native void registerNatives()

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

private static native void registerNatives();
static {
    registerNatives();
}

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语言函数如下:

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]));
}

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类获取对象的构造器、方法、属性、注解等相关信息:

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);
}

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

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);
    }
}

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

JNI_ENTRY(jclass, jni_GetObjectClass(JNIEnv *env, jobject obj))
  JNIWrapper("GetObjectClass");
#ifndef USDT2
  DTRACE_PROBE2(hotspot_jni, GetObjectClass__entry, env, obj);
#else /* USDT2 */
  HOTSPOT_JNI_GETOBJECTCLASS_ENTRY(env, obj);
#endif /* USDT2 */
//主要作用是根据java对象引用找到JVM中引用对象,将Java对象转换为JVM中的引用对象,然后调用klass()方法找到元数据
  Klass* k = JNIHandles::resolve_non_null(obj)->klass();
  jclass ret =(jclass) JNIHandles::make_local(env, k->java_mirror());
#ifndef USDT2
  DTRACE_PROBE1(hotspot_jni, GetObjectClass__return, ret);
#else /* USDT2 */
  HOTSPOT_JNI_GETOBJECTCLASS_RETURN(
                                    ret);
#endif /* USDT2 */
  return ret;
JNI_END

上述的核心代码为:

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

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

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");
  // Don't let that private _deleted_handle object escape into the wild.
  assert(result != deleted_handle(), "Used a deleted global handle.");
  return result;
};

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

inline Klass* oopDesc::klass() const {
  if (UseCompressedClassPointers) {
    return Klass::decode_klass_not_null(_metadata._compressed_klass);
  } else {
  //返回元数据
    return _metadata._klass;
  }
}

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

 // java/lang/Class instance mirroring this class
oop       _java_mirror;
oop java_mirror() const              { return _java_mirror; }

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

jobject JNIHandles::make_local(oop obj) {
  if (obj == NULL) {
      //返回null
    return NULL;                // ignore null handles
  } else {
     //获取当前线程
    Thread* thread = Thread::current();
    assert(Universe::heap()->is_in_reserved(obj), "sanity check");
     //利用当前线程活跃的handles对oop进行处理。
    return thread->active_handles()->allocate_handle(obj);
  }
}

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

public native int hashCode()

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

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

    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

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

    static inline intptr_t get_next_hash(Thread * Self, oop obj) {
      intptr_t value = 0 ;
      if (hashCode == 0) {
          //OpenJdk 6 &7的默认实现,此类方案返回一个Park-Miller伪随机数生成器生成的随机数
         value = os::random() ;
      } else
      if (hashCode == 1) {
          //此类方案将对象的内存地址,做移位运算后与一个随机数进行异或得到结果
         intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
         value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
      } else
      if (hashCode == 2) {
          //此类方案将对象的内存地址,做移位运算后与一个随机数进行异或得到结果
         value = 1 ;            // for sensitivity testing
      } else
      if (hashCode == 3) {
          //此类方案返回一个自增序列的当前值
         value = ++GVars.hcSequence ;
      } else
      if (hashCode == 4) {
          //此类方案返回当前对象的内存地址
         value = cast_from_oop<intptr_t>(obj) ;
      } else {
         //OpenJdk 8 默认hashCode的计算方法
         //通过和当前线程有关的一个随机数+三个确定值
          //运用Marsaglia's xorshift scheme随机数算法得到的一个随机数
         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;
    }

    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算法是通过移位和与或计算,能够在计算机上以极快的速度生成伪随机数序列。算法如下:

    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)) );
    }

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

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

    public boolean equals(Object obj)

    public boolean equals(Object obj) {
            return (this == obj);
    }

    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()方法,会编译错误:

     Object o=new Object();
     //编译错误
     Object o1=  o.clone();

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

    public class ObjectCloneTest {
        public static void main(String [] args) throws CloneNotSupportedException {
            ObjectCloneTest o=new ObjectCloneTest();
            ObjectCloneTest cloneTest= (ObjectCloneTest) o.clone();
            System.out.println(cloneTest);
        }
    }

    但是运行时发生异常:

    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)

    如果子没有实现Cloneable接口,当子类调用clone()方法时,会抛出CloneNotSupportedException异常。当子类实现Cloneable接口,程序运行会成功:

    //实现Cloneable接口
    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@6d6f6e28

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

    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());
    }
    //结果
    [Ljava.lang.String;@17d10166
    [Ljava.lang.String;@1b9e1916
    [I@ba8a1dc
    [I@4f8e5cde

    对于任何对象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的实现如下:

    //JVM_Clone 的实现
    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;
    
    #ifdef ASSERT
      // Just checking that the cloneable flag is set correct
    //如果是传入的是数组
      if (obj->is_array()) {
          //所有的数组都默认实现了Cloneable接口
        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());
          //判断是否是合法的cloneable
        guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");
      }
    #endif
    ----------------------------------分割线1-------------------------------------------------
    // Check if class of obj supports the Cloneable interface.
     // All arrays are considered to be cloneable (See JLS 20.1.5)
        //如果没有实现Cloneable接口,抛出CloneNotSupportedException异常
      if (!klass->is_cloneable()) {
        ResourceMark rm(THREAD);
        THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
      }
    
      // Make shallow object copy 
    	//浅拷贝
      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-------------------------------------------------
     // 4839641 (4840070): We must do an oop-atomic copy, because if another thread
      // is modifying a reference field in the clonee, a non-oop-atomic copy might
      // be suspended in the middle of copying the pointer and end up with parts
      // of two different pointers in the field.  Subsequent dereferences will crash.
      // 4846409: an oop-copy of objects with long or double fields or arrays of same
      // won't copy the longs/doubles atomically in 32-bit vm's, so we copy jlongs instead
      // of oops.  We know objects are aligned on a minimum of an jlong boundary.
      // The same is true of StubRoutines::object_copy and the various oop_copy
      // variants, and of the code generated by the inline_native_clone intrinsic.
      assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned");
      //原子性拷贝,可能另外一个线程会修改clone引用的字段
      Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj,
                                   (size_t)align_object_size(size) / HeapWordsPerLong);
      // Clear the header
      new_obj->init_mark();
    
      // Store check (mark entire object and let gc sort it out)
      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));
    
      // Caution: this involves a java upcall, so the clone should be
      // "gc-robust" by this stage.
      //当java子类实现了finalize方法,已经调用finalize,注册finalizer方法
      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

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

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

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

    public String toString()

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

    public String toString() {
            return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

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

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

    Tags:

    最近发表
    标签列表