美文网首页JDK源码解析
Java源码分析 | Object

Java源码分析 | Object

作者: 朝雾轻寒 | 来源:发表于2022-08-19 15:03 被阅读0次

    本文基于 OracleJDK 11, HotSpot 虚拟机。

    Object 定义

    Object 类是类层次结构的根。每个类都有 Object 类作为超类。所有对象,包括数组等,都实现了这个类的方法。

    静态代码块

    在Object类的最开始部分,有如下四行代码:

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

    native 方法主要用于通过调用 C 或 C++ 实现的本地方法来对底层操作系统的访问。

    扩展

    native 关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如 C 和C++)实现的文件中。

    Java 语言本身不能对操作系统底层进行访问和操作,但是可以通过 JNI(Java Native Interface)接口调用其他语言来实现对底层的访问。

    **JNI **全称是 Java Native Interface,即 Java 本机接口,是 Java 和 Native 间的通信桥梁。Java 调用 Native,可以去调用非 Java 实现的库,扩充 Java 的使用场景;反之 Native 调用 Java,可以在别的语言里面调用 Java。

    类的 static 静态代码块会在类初始化时调用,其目的是为该类中包含的除了registerNatives()方法以外的所有本地方法(被 native 关键字修饰的方法)进行注册。

    构造函数

     @HotSpotIntrinsicCandidate
     public Object() {}
    
    • 无参构造函数主要是创建一个新的 Object 对象。
    • @HotSpotIntrinsicCandidate 注解特定于 HotSpot 虚拟机,它表明带注解的方法可能被 HotSpot 内在化,在虚拟机内存在更高效的实现来替换被注解修饰的方法以提高性能。该注解是 Java 库内部的,与应用程序没有任何关联。

    方法

    getClass()

    @HotSpotIntrinsicCandidate
    public final native Class<?> getClass();
    

    该方法是 native 方法,作用是返回当前对象运行时的类。返回的 Class 对象是被static synchronized方法锁定的对象。

    实际的结果类型是 Class<? extends |X|>,X 是对调用 getClass 的表达式的静态类型擦除。如下所示,代码中不需要强制转换:

    Number n = 0; 
    Class<? extends Number> c = n.getClass(); 
    

    Class 对象表示运行时此对象的类。

    hashCode()

    public native int hashCode();
    

    该方法是 native 方法,作用是返回当前对象的哈希码值。支持此方法是为了便于对 java.util.HashMap 等提供的哈希表。

    hashCode 的通用规则是:

    • 每当在 Java 应用程序执行期间对同一对象多次调用它时,该方法始终返回相同的整数。如果 equals() 中使用的信息没有被修改。从应用程序的一次执行到相同的应用程序的一次执行,此整数不必保持一致。
    • 如果两个对象根据调用 equals() 方法相等,则在每个对象上调用 hashCode() 方法必须产生相同的整数结果。
    • 如果两个对象根据调用 equals()方法不相等,在每个对象上调用hasCode()方法不要求必须产生不同的整数结果(因为可能存在哈希碰撞)。

    equals()

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

    该方法指示其他对象是否“等于”当前对象,判断两个对象是否具有相同的引用(对象的内存地址)。

    equals() 函数必须满足以下五点条件:

    • 反身性:对于任何 x, x.equals(x) 应该返回 true。
    • 对称性:对于任何 x 和 y,x.equals(y) 应该返回 true 当且仅当 y.equals(x) 返回 true 。
    • 传递性:对于任何 x,y, 还有 z,如果 x.equals(y) 返回 true 并且 y.equals(z) 返回 true,那么 x.equals(z) 应该返回 true。
    • 一致性:对于任何 x 和 y,在对象没有被改变的情况下,多次调用 x.equals(y) 应该总是返回 true 或者 false。
    • 对于任何非 null 的 x,x.equals(null) 应该返回 false。

    类 Object 的equals()方法在对象上实现了最有区别的等价关系,也就是说,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用的是同一对象的时候,x==y 返回 true。

    默认情况下从超类 Object 继承而来的 equals() 方法与 == 是完全等价的,比较的都是对象的内存地址,但我们可以重写 equals()方法,使其按照我们的需求的方式进行比较。

    注意

    每当重写 equals()方法时,通常都需要重写hashCode()方法,以便维护hashCode()方法的常规约定,该方法申明相等的对象必须具有相同的 hashCode 值。如果子类不重写,将会默认使用父类对象的 hashCode() 方法,导致对象内容一致但对象内存地址不一致。

    因为equals()方法比较消耗性能且效率低,一般有大量数据需要快速的对比的话,会先比对hashCodehashCode相等的再使用equals进行比较。

    Java 中常见的一些默认类都会重写 hashCode()equals() 方法,如 String 类等。

    clone()

     @HotSpotIntrinsicCandidate
     protected native Object clone() throws CloneNotSupportedException;
    

    该方法是 native 方法,作用是创建并返回此对象的副本。

    注意:clone 方法本身没有实现 Cloneable 接口,但在调用 clone 方法时需要实现 Cloneable 接口并重写,否则会抛出 CloneNotSupportedException 异常以表示无法克隆。

    通常重写克隆需要满足以下条件:

    • x.clone() != x 为 true (对象引用指向堆内存地址不同)
    • x.clone().getClass() == x.getClass() 为 true(相同的运行时类)
    • x.clone().equals(x) 为 true (对象属性内容相同,由于 Object 的 equals() 默认为 this == obj,所以需要重写)

    但这不是绝对的,因为浅克隆和深克隆的区别。

    扩展

    浅克隆

    被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换而言之,浅拷贝仅仅复制所拷贝的对象,而不复制它所引用的对象.

    深克隆

    被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

    toString()

    返回对象的字符串表示形式。

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

    此方法默认返回 类名@无符号十六进制表示形式组成对象的哈希码。该方法是为了更简洁清晰且信息丰富的表示对象内容,易于阅读,所以建议所有子类重写该方法。

    notify()、notifyAll()

    @HotSpotIntrinsicCandidate
    public final native void notify();
    
    @HotSpotIntrinsicCandidate
    public final native void notifyAll();
    

    这两个方法都是 native 方法,作用都是唤醒正在此对象的监视器上等待的线程,区别在于 notify() 是唤醒单线程,而 notifyAll()是唤醒所有线程。

    如果有任何线程正在等待该对象,则选择其中一个被唤醒。该选择是任意的,并由实施自行决定。线程通过调用 wait 方法之一在对象的监视器上等待。

    在当前线程放弃对该对象的锁定之前,被唤醒的线程将无法继续。被唤醒的线程将以通常的方式与可能正在积极竞争以在此对象上同步的任何其他线程竞争

    该方法只能由作为该对象监视器所有者的线程调用,否则会抛出 IllegalMonitorStateException 异常。

    扩展

    每个对象都有一个”锁“,即监视器 Monitor,而且每个对象都有一个同步队列(EntrySet)和等待队列(WaitSet),同步队列和等待队列里面都存放着线程对象的引用。

    notify() 是对 notifyAll() 的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死锁。正确的场景应该是 WaitSet 中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果唤醒的线程无法正确处理,务必确保继续 notify() 下一个线程,并且自身需要重新回到 WaitSet 中。

    wait()、wait(long timeout)、wait(long timeout, int nanos)

     public final void wait() throws InterruptedException {
            wait(0L);
     }
    public final native void wait(long timeoutMillis) throws InterruptedException;
         
    public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
            if (timeoutMillis < 0) {
                throw new IllegalArgumentException("timeoutMillis value is negative");
            }
    
            if (nanos < 0 || nanos > 999999) {
                throw new IllegalArgumentException(
                                    "nanosecond timeout value out of range");
            }
    
            if (nanos > 0) {
                timeoutMillis++;
            }
    
            wait(timeoutMillis);
        }
    

    wait(long timeout) 方法是 native 方法,作用是让当前线程释放 CPU 占用资源并且释放对象的”锁“,直到该对象执行了 notify()/noyifyAll()方法,或者过了 timeoutMillis 的等待时间(单位:毫秒),或者被其他线程调用了该线程的 interrupt()方法打断,该线程会被唤醒。

    wait()方法就是调用了 wait(0L),代表无限等待,只能通过该对象执行了 notify()/noyifyAll()方法、被打断,该线程会被唤醒。

    wait(long timeoutMillis, int nanos) 方法只是添加了一个范围在0~999999纳秒的附加时间int nanos,本质上还是调用了 wait(long timeout)

    如果当前线程不是对象监视器的所有者,则会抛出 IllegalMonitorStateException 异常。

    如果当前线程在等待之前或期间被任何线程中断,则会抛出 InterruptedException 异常。

    线程也可以在没有被通知、中断或超时的情况下唤醒,即所谓的虚假唤醒。虽然这在实践中很少发生,但应用程序必须通过测试应该导致线程被唤醒的条件来防范它,可以将 wait 配合 while 循环使用,苏醒后进行条件检查,如果不满足则 继续 wait() 直至条件满足再往下执行。

    finalize()

    @Deprecated(since="9")
    protected void finalize() throws Throwable { }
    

    当垃圾收集确定不再有对该对象的引用时,由对象上的垃圾收集器调用。子类覆盖finalize()方法来处理系统资源或执行其他清理。

    每个对象的 finalize() 方法只能被系统执行一次,该方法类似析构函数但不等价。

    死亡逃逸,可以在对象被回收之前在 finalize 方法里面重新与其他对象(其他对象不能是即将被回收的对象)建立关联即可,但机会只有一次。

    在JDK9及其之后已被废弃使用。原因是最终确定机制本质上是有问题的。最终确定会导致性能问题、死锁和挂起。终结器中的错误可能导致资源泄漏;如果不再需要,则无法取消最终确定;并且在对不同对象的 finalize 方法的调用之间没有指定顺序。此外,无法保证最终确定的时间。finalize() 方法可能仅在无限期延迟之后才在可终结对象上调用

    对象持有非堆资源的类应该提供一种方法来启用这些资源的显式释放,它们可以实现 java.lang.AutoCloseable

    java.lang.ref.Cleanerjava.lang.ref.PhantomReference 提供了更灵活、更有效的方法来在对象变得无法访问时释放资源。

    相关文章

      网友评论

        本文标题:Java源码分析 | Object

        本文链接:https://www.haomeiwen.com/subject/nzdapctx.html