Java Object类源码分析

获取源码的方式:https://blog.csdn.net/weixin_35946648/article/details/114849170

注意:后文中提到的所有路径均为源码根目录的相对路径。

1、registerNatives()

方法作用:向JVM注册Object类中的其他一系列native方法。

源码实现(jdk/src/share/native/java/lang/Object.c):

源码分析

(1)JNIEnv 即 JNI 环境,用于在 c/c++ 代码中调用 java 代码,例如获取 java 类信息、修改 java 类变量的值……

(2)methods数组中指定了Object类中其他的native方法(getClass()方法除外,后文会讲到)所对应的c/c++实现函数名。例如:hashCode的实现方法为c++函数JVM_IHashCode,该函数位于:hotspot/src/share/vm/prims/jvm.cpp。

(3)Java_java_lang_Object_registerNatives函数内部调用了c++实现的RegisterNatives函数。

(4)RegisterNatives函数(hotspot/src/share/vm/prims/jni.cpp)实现如下图所示:

该函数内部遍历了methods数组,实现对methods数组中的每个native方法的注册。

对比分析:

JVM调用native方法的过程简述:

(1)根据方法名定位对应的 so(linux)/ddl(windows)文件(后文统称为动态链接库),并加载文件内容(方法实现)到内存中。

(2)连接 java native 方法声明和 so/ddl方 法实现。

相比依赖JVM调用native方法,使用自定义的 registerNatives 方法的好处

(1)可以通过 static 静态代码块在类加载之后立刻注册 native 方法,而不需要在调用 native 方法时才注册,能加快运行期间的程序执行速度(以空间换时间)。

(2)能够自定义c/c++实现函数的函数名,而不需要严格遵循JNI方法命名规则,且能够在 java 程序运行期间,通过更新动态链接库中的函数名映射,能够修改 native 方法对应的函数实现。

关于JNI方法命名规则:native 方法的c/c++实现函数名称必须是“Java_包路径('.'替换成_)_类名_native方法名”。

例如:Object 类的 getClass() 方法对应的实现方法名为 Java_java_lang_Object_getClass

 

2、getClass()

方法声明:public final native Class<?> getClass()

方法作用:获取当前对象对应的 Class 对象。

关于 Class 对象:Class 对象是 class 文件的运行时数据结构。一个类在被应用程序使用之前,需要经历加载、连接(验证,准备,解析)、初始化阶段,Class 对象会在加载阶段被开始创建,然后在连接阶段逐渐将 class 字节码中的内容转换为 Class 对象的属性,最终在初始化阶段完成 Class 对象的初始化(对父类和自身静态属性的赋予应用程序指定的初始值,并执行static代码块)。

源码实现(jdk/src/share/native/java/lang/Object.c):

源码分析

(1)参数 this 即调用对象实例指针,this为空(即 null.getClass())时抛出空指针异常。

(2)this 不为空时调用 c++ 实现的 GetObjectClass 函数,该函数位于:hotspot/src/share/vm/prims/jni.cpp,函数实现如下:

其中的关键代码:

(1)Klass* k = JNIHandles::resolve_non_null(obj)->klass():根据传入的 java 对象引用找到对象实例,然后根据对象实例找到该对象类型的元数据。

(2)jclass ret = (jclass) JNIHandles::make_local(env, k->java_mirror()):根据元数据找到其镜像,即 Class 对象实例。

 

和其他获取 Class 对象的方式作对比:

package com.cc.kit.java8.usage.base.lang.object;

import org.junit.jupiter.api.Test;

class A {
    static int a = 10;

    static {
        System.out.println("A.static: " + a);
    }
}

class B {
    static int b = 100;

    static {
        System.out.println("B.static: " + b);
    }
}

class C {
    static int c = 1000;

    static {
        System.out.println("c.static: " + c);
    }
}

public class GetClassUsage {
    @Test
    public void getClassUsage() throws ClassNotFoundException {
        Class<A> aClass = A.class;
        Class bClass = Class.forName("com.cc.kit.java8.usage.base.lang.object.B");
        Class cClass = new C().getClass();
        System.out.println(aClass.getName());
    }
}

打印结果:

可以发现,通过“类名.class”获取 Class 对象时,不会进行类的初始化

 

3、hashCode()

方法声明:public native int hashCode()

方法作用:计算当前对象的哈希值。

方法约定:

  • 在java程序执行过程中,在一个对象没有被改变的前提下,无论这个对象被调用多少次,hashCode方法都会返回相同的整数值。对象的哈希码没有必要在不同的程序中保持相同的值。
  • 如果2个对象使用equals方法进行比较并且相同的话,那么这2个对象的hashCode方法的值也必须相等。
  • 如果根据equals方法,得到两个对象不相等,那么这2个对象的hashCode值不需要必须不相同。因为,不相等的对象的hashCode值不同的话可以提高哈希表的性能。
  • 默认情况下,如果一个 java 类不重写 equals() 方法和 hashCode 方法,则该类的每个对象调用 hashCode() 方法将返回该对象的内存地址的转换结果。

 

4、equals(Object obj)

方法声明:public boolean equals(Object obj)

方法作用:判断两个对象是否相等。默认情况下比较两个对象的内存地址是否相等,除非重写该方法。

注意重写 equals 方法时,应同时重写 hashCode 方法!理由请参考 hashCode 方法约定。

 

5、clone()

方法声明:protected native Object clone() throws CloneNotSupportedException;

方法作用:对象浅拷贝。拷贝策略:拷贝所有基本数据类型的成员变量,拷贝所有引用数据类型变量的引用地址。即,对于引用类型的对象而言,拷贝的结果仅仅是对象引用地址,而实际的对象实例是同一个。

举例说明:

package com.cc.kit.java8.usage.base.lang.object;

import org.junit.jupiter.api.Test;

public class CloneUsage {
    class A implements Cloneable {
        int i;
        B b;

        public A clone() throws CloneNotSupportedException {
            return (A) super.clone();
        }
    }

    class B {
        String s;
    }

    @Test
    public void cloneUsage() throws CloneNotSupportedException {
        A a1 = new A();
        a1.i = 10;
        a1.b = new B();
        a1.b.s = "b1";

        A a2 = a1.clone();

        System.out.println(a1.i == a2.i);
        System.out.println(a1.b == a2.b);
        System.out.println(a2.b.s);

        a1.b.s = "b2";
        System.out.println(a2.b.s);
    }
}

打印结果:

 

6、notify()

方法声明:public final native void notify()

方法作用:唤醒一个持有当前对象 monitor 锁的线程,使其能去竞争该对象的 monitor 锁。

唤醒原则:在 hotspot 虚拟机中,notify() 遵循先进先出(FIFO)的唤醒原则,即,先进入等待状态的线程先被唤醒。

注意notify() 方法必须在 synchronized 同步代码块/方法中调用,否则会抛出 IllegalMonitorStateException 异常。

 

7、wait()

方法声明:public final native void wait(long timeout) throws InterruptedException

方法作用:释放当前对象 monitor 锁,使当前线程进入该 monitor 锁的等待池中,并释放 CPU 资源,直到被唤醒。

注意wait() 方法必须在 synchronized 同步代码块/方法中调用,否则会抛出 IllegalMonitorStateException 异常。

notify() 与 wait() 联合使用示例:

    /**
     * 两个线程,一个线程打印1-52,另一个线程打印A-Z,打印结果为12A34B...5152Z
     */
    public static void main(String[] args) {
        final boolean[] flag = {false};

        new Thread(() -> {
            synchronized (flag) {
                for (int i = 1; i <= 52; i++) {
                    System.out.print(i);
                    System.out.print(++i);

                    if (!flag[0]) {
                        flag[0] = true;
                    }
                    flag.notify();

                    try {
                        flag.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 先结束的线程必须在退出之前唤醒其他线程,否则会造成其他线程一直处于等待状态而无法结束
                flag.notify();
            }
        }).start();

        new Thread(() -> {
            synchronized (flag) {
                // 等待前一个线程先打印:12
                while (!flag[0]) {
                    try {
                        flag.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                for (char i = 'A'; i <= 'Z'; i++) {
                    System.out.print(i);
                    flag.notify();

                    try {
                        flag.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 在这个例子中,下面这行语句是可以没有的,因为这个线程后结束。但是如果这个线程打印到 Z 之前的字母,则该线程会先结束,此时必须唤醒前一个线程,否则会造成前一个线程一直处于等待状态
                // flag.notify();
            }
        }).start();
    }

 

8、notifyAll()

方法声明:public final native void notifyAll()

方法作用:唤醒所有持有当前对象 monitor 锁的线程,使这些线程能去竞争该对象的 monitor 锁。

 

9、wait(long timeout)

方法声明:public final native void wait(long timeout) throws InterruptedException

方法作用:释放当前对象 monitor 锁,使当前线程进入该 monitor 锁的等待池中,并释放 CPU 资源,直到被唤醒或等待时间达到 timeout 值(单位:毫秒)。

 

10、wait(long timeout, int nanos)

方法声明:public final void wait(long timeout, int nanos) throws InterruptedException

方法作用:释放当前对象 monitor 锁,使当前线程进入该 monitor 锁的等待池中,并释放 CPU 资源,直到被唤醒或等待时间达到等待时间。该方法首先会根据参数值计算等待时间,然后调用 wait(long timeout) 方法。

等待时间:如下图 timeout 的计算过程所示。可以发现,当 timeout>=0 且 nanos>500000纳秒(500微秒/0.5毫秒)时,等待超时时间为 timeout+1 毫秒;当 timeout=0 且 nanos 在 (0,999999] 之间时,等待超时时间为 1 毫秒。

注意:上图所示为文章开头所声明的指定版本的 JDK 源码实现,不同版本 JDK 对该方法中 timeout 的计算有不同的实现,使用之前应仔细阅读源代码和注释。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值