Object类

2015/02/03 JDK

Object类

Object类是Java中其他所有类的祖先,没有Object类Java面向对象无从谈起。作为其他所有类的基类,Object具有哪些属性和行为,是Java语言设计背后的思维体现。

Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。

Object 类位于 java.lang 包中,编译时会自动导入,我们创建一个类时,如果没有明确继承一个父类,那么它就会自动继承 Object,成为 Object 的子类。

Object类方法

Object类位于java.lang包中,java.lang包包含着Java最基础和核心的类,在编译时会自动导入。Object类没有定义属性,一共有13个方法:

类构造器public Object();

大部分情况下,Java中通过形如 new A(args…)形式创建一个属于该类型的对象。其中A即是类名,A(args…)即此类定义中相对应的构造函数。通过此种形式创建的对象都是通过类中的构造函数完成。为体现此特性,Java中规定:在类定义过程中,对于未定义构造函数的类,默认会有一个无参数的构造函数,作为所有类的基类,Object类自然要反映出此特性,在源码中,未给出Object类构造函数定义,但实际上,此构造函数是存在的。

当然,并不是所有的类都是通过此种方式去构建,也自然的,并不是所有的类构造函数都是public。

private static native void registerNatives();

registerNatives函数前面有native关键字修饰,Java中,用native关键字修饰的函数表明该方法的实现并不是在Java中去完成,而是由C/C++去完成,并被编译成了.dll,由Java去调用。方法的具体实现体在dll文件中,对于不同平台,其具体实现应该有所不同。用native修饰,即表示操作系统,需要提供此方法,Java本身需要使用。具体到registerNatives()方法本身,其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。

既然如此,可能有人会问,registerNatives()修饰符为private,且并没有执行,作用何以达到?其实,在Java源码中,此方法的声明后有紧接着一段静态代码块:

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

protected native Object clone() throws CloneNotSupportedException;

clone()方法又是一个被声明为native的方法,因此,我们知道了clone()方法并不是Java的原生方法,具体的实现是有C/C++完成的。clone英文翻译为”克隆”,其目的是创建并返回此对象的一个副本,而克隆出来的副本与原来的对象没有任何关系的。Java术语表述为:clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。

默认的是浅拷贝

void LibraryCallKit::copy_to_clone(Node* obj, Node* alloc_obj, Node* obj_size, bool is_array, bool card_mark) {
  //首先做一些基本判断,例如对象不为空
  assert(obj_size != NULL, "");
  Node* raw_obj = alloc_obj->in(1);
  assert(alloc_obj->is_CheckCastPP() && raw_obj->is_Proj() && raw_obj->in(0)->is_Allocate(), "");

  AllocateNode* alloc = NULL;
  if (ReduceBulkZeroing) {
    // We will be completely responsible for initializing this object -
    // mark Initialize node as complete.
    alloc = AllocateNode::Ideal_allocation(alloc_obj, &_gvn);
    // The object was just allocated - there should be no any stores!
    guarantee(alloc != NULL && alloc->maybe_set_complete(&_gvn), "");
    // Mark as complete_with_arraycopy so that on AllocateNode
    // expansion, we know this AllocateNode is initialized by an array
    // copy and a StoreStore barrier exists after the array copy.
    alloc->initialization()->set_complete_with_arraycopy();
  }

  //分配初始指针
  Node* src  = obj;
  Node* dest = alloc_obj;
  Node* size = _gvn.transform(obj_size);

  // 根据是否为数组决定对象头结构
  int base_off = is_array ? arrayOopDesc::length_offset_in_bytes() :
                            instanceOopDesc::base_offset_in_bytes();
  // base_off:
  // 8  - 32-bit VM
  // 12 - 64-bit VM, compressed klass
  // 16 - 64-bit VM, normal klass
  if (base_off % BytesPerLong != 0) {
    assert(UseCompressedClassPointers, "");
    if (is_array) {
      // Exclude length to copy by 8 bytes words.
      base_off += sizeof(int);
    } else {
      // Include klass to copy by 8 bytes words.
      base_off = instanceOopDesc::klass_offset_in_bytes();
    }
    assert(base_off % BytesPerLong == 0, "expect 8 bytes alignment");
  }
  src  = basic_plus_adr(src,  base_off);
  dest = basic_plus_adr(dest, base_off);

  // Compute the length also, if needed:
  Node* countx = size;
  countx = _gvn.transform(new (C) SubXNode(countx, MakeConX(base_off)));
  countx = _gvn.transform(new (C) URShiftXNode(countx, intcon(LogBytesPerLong) ));

  const TypePtr* raw_adr_type = TypeRawPtr::BOTTOM;
  bool disjoint_bases = true;
  generate_unchecked_arraycopy(raw_adr_type, T_LONG, disjoint_bases,
                               src, NULL, dest, NULL, countx,
                               /*dest_uninitialized*/true);

  // If necessary, emit some card marks afterwards.  (Non-arrays only.)
  if (card_mark) {
    assert(!is_array, "");
    // Put in store barrier for any and all oops we are sticking
    // into this object.  (We could avoid this if we could prove
    // that the object type contains no oop fields at all.)
    Node* no_particular_value = NULL;
    Node* no_particular_field = NULL;
    int raw_adr_idx = Compile::AliasIdxRaw;
    post_barrier(control(),
                 memory(raw_adr_type),
                 alloc_obj,
                 no_particular_field,
                 raw_adr_idx,
                 no_particular_value,
                 T_OBJECT,
                 false);
  }

  // Do not let reads from the cloned object float above the arraycopy.
  if (alloc != NULL) {
    // Do not let stores that initialize this object be reordered with
    // a subsequent store that would make this object accessible by
    // other threads.
    // Record what AllocateNode this StoreStore protects so that
    // escape analysis can go from the MemBarStoreStoreNode to the
    // AllocateNode and eliminate the MemBarStoreStoreNode if possible
    // based on the escape status of the AllocateNode.
    insert_mem_bar(Op_MemBarStoreStore, alloc->proj_out(AllocateNode::RawAddress));
  } else {
    insert_mem_bar(Op_MemBarCPUOrder);
  }
}

public final native Class<?> getClass();

getClass()也是一个native方法,返回的是此Object对象的类对象/运行时类对象Class<?>。效果与Object.class相同。

首先解释下”类对象”的概念:在Java中,类是是对具有一组相同特征或行为的实例的抽象并进行描述,对象则是此类所描述的特征或行为的具体实例。作为概念层次的类,其本身也具有某些共同的特性,如都具有类名称、由类加载器去加载,都具有包,具有父类,属性和方法等。于是,Java中有专门定义了一个类,Class,去描述其他类所具有的这些特性,因此,从此角度去看,类本身也都是属于Class类的对象。为与经常意义上的对象相区分,在此称之为”类对象”。

public boolean equals(Object obj);

Object类中关于equals()方法的定义:

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

由此可见,Object原生的equals()方法内部调用的正是==,与==具有相同的含义。既然如此,为什么还要定义此equals()方法?

equlas()方法的正确理解应该是:判断两个对象是否相等。那么判断对象相等的标尺又是什么?

如上,在object类中,此标尺即为==。当然,这个标尺不是固定的,其他类中可以按照实际的需要对此标尺含义进行重定义。如String类中则是依据字符串内容是否相等来重定义了此标尺含义。如此可以增加类的功能型和实际编码的灵活性。当然了,如果自定义的类没有重写equals()方法来重新定义此标尺,那么默认的将是其父类的equals(),直到object基类。

对于equals方法的实现,则需要满足:

  1. 自反性:对于任意非null的x,x.equals(x)为true
  2. 对称性:对于任意非null的x和y,如果x.equals(y),那么y.equals(x)
  3. 传递性:对于任意非null的x,y和z,如果x.equals(y)并且y.equals(z),那么x.equals(z)
  4. 一致性:对于任意非null的x和y,无论执行多少次x.equals(y)结果都是一样的
  5. 对于任意非null的x,x.equals(null)返回false

public native int hashCode();

hashCode()方法返回一个整形数值,表示该对象的哈希码值。

hashCode()具有如下约定:

1).在Java应用程序程序执行期间,对于同一对象多次调用hashCode()方法时,其返回的哈希码是相同的,前提是将对象进行equals比较时所用的标尺信息未做修改。在Java应用程序的一次执行到另外一次执行,同一对象的hashCode()返回的哈希码无须保持一致; 但是,在程序重启后同一个对象的hashCode不用和之前那次运行的hashCode保持一致。但是考虑如果在分布式的情况下,如果对象作为key,最好还是保证无论在哪台机器上运行多少次,重启多少次,不同机器上,同一个对象(指的是两个equals对象),的hashCode值都一样(原因之后会说的)。 例如这里的Object对于hashCode的实现,在当前次运行,这个对象的存储地址是不变的。所以hashCode不变,但是程序重启后就不一定了。对于String的hashCode实现:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

2).如果两个对象相等(依据:调用equals()方法),那么这两个对象调用hashCode()返回的哈希码也必须相等;

3).反之,两个对象调用hasCode()返回的哈希码相等,这两个对象不一定相等。

即严格的数学逻辑表示为: 两个对象相等 <=> equals()相等 => hashCode()相等。因此,重写equlas()方法必须重写hashCode()方法,以保证此逻辑严格成立,同时可以推理出:hasCode()不相等 => equals()不相等 <=> 两个对象不相等。

可能有人在此产生疑问:既然比较两个对象是否相等的唯一条件(也是冲要条件)是equals,那么为什么还要弄出一个hashCode(),并且进行如此约定,弄得这么麻烦?

其实,这主要体现在hashCode()方法的作用上,其主要用于增强哈希表的性能。

以集合类中,以Set为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。(注:Set的底层用的是Map的原理实现)

在此需要纠正一个理解上的误区:对象的hashCode()返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址,hashCode()相同的两个对象,不一定相等,换言之,不相等的两个对象,hashCode()返回的哈希码可能相同。

public String toString();

toString()方法返回该对象的字符串表示。先看一下Object中的具体方法体:

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

默认的实现就是class名字@hashcode(如果hashCode()方法也是默认的话,那么就是如之前所述地址)

toString()方法我们会经常用到,即使没有显式调用,但当我们使用System.out.println(obj)时,其内部也是通过toString()来实现的

toString()是由对象的类型和其哈希码唯一确定,同一类型但不相等的两个对象分别调用toString()方法返回的结果可能相同。

wait(…) / notify() / notifyAll()

wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。这几个方法主要用于java多线程之间的协作.

public final native void wait(long timeout) throws InterruptedException;
public final native void notify();
public final native void notifyAll();

wait():调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notify()/notifyAll()方法。

notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。

wait(long timeout)/wait(long timeout, int nanos):调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notisfy()/notisfyAll()方法,或超过指定的超时时间量。

notify()/notifyAll():唤醒在此对象监视器上等待的单个线程/所有线程。

wait(…) / notify() notifyAll()一般情况下都是配套使用。

那么底层实现是怎么回事呢? 首先我们需要先明确JDK底层实现共享内存锁的基本机制。 每个Object都有一个ObjectMonitor,这个ObjectMonitor中包含三个特殊的数据结构,分别是CXQ(实际上是Contention List),EntryList还有WaitSet;一个线程在同一时间只会出现在他们三个中的一个中。首先来看下CXQ: 一个尝试获取Object锁的线程,如果首次尝试(就是尝试CAS更新轻量锁)失败,那么会进入CXQ;进入的方法就是CAS更新CXQ指针指向自己,如果成功,自己的next指向剩余队列;CXQ是一个LIFO队列,设计成LIFO主要是为了:

  1. 进入CXQ队列后,每个线程先进入一段时间的spin自旋状态,尝试获取锁,获取失败的话则进入park状态。这个自旋的意义在于,假设锁的hold时间非常短,如果直接进入park状态的话,程序在用户态和系统态之间的切换会影响锁性能。这个spin可以减少切换;
  2. 进入spin状态如果成功获取到锁的话,需要出队列,出队列需要更新自己的头指针,如果位于队列前列,那么需要操作的时间会减少 但是,如果全部依靠这个机制,那么理所当然的,CAS更新队列头的操作会非常频繁。所以,引入了EntryList来减少争用:

假设Thread A是当前锁的Owner,接下来他要释放锁了,那么如果EntryList为null并且cxq不为null,就会从cxq末尾取出一个线程,放入EntryList(注意,EntryList为双向队列),并且标记EntryList其中一个线程为Successor(一般是头节点,这个EntryList的大小可能大于一,一般在notify时,后面会说到),这个Successor接下来会进入spin状态尝试获取锁(注意,在第一次自旋过去后,之后线程一直处于park状态)。如果获取成功,则成为owner,否则,回到EntryList中。 这种利用两个队列减少争用的算法,可以参考: Michael Scott’s “2Q” algorithm 接下来,进入我们的正题,wait方法。如果一个线程成为owner后,执行了wait方法,则会进入WaitSet:

Object.wait()底层实现

void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {

    //检查线程合法性
    Thread *const Self = THREAD;
    assert(Self->is_Java_thread(), "Must be Java thread!");
    JavaThread *jt = (JavaThread *) THREAD;
    DeferredInitialize();
    //检查当前线程是否拥有锁
    CHECK_OWNER();
    EventJavaMonitorWait event;
    // 检查中断位
    if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
        if (JvmtiExport::should_post_monitor_waited()) {
            JvmtiExport::post_monitor_waited(jt, this, false);
        }
        if (event.should_commit()) {
            post_monitor_wait_event(&event, 0, millis, false);
        }
        TEVENT(Wait - ThrowIEX);
        THROW(vmSymbols::java_lang_InterruptedException());
        return;
    }
    TEVENT(Wait);
    assert(Self->_Stalled == 0, "invariant");
    Self->_Stalled = intptr_t(this);
    jt->set_current_waiting_monitor(this);

    //建立放入WaitSet中的这个线程的封装对象
    ObjectWaiter node(Self);
    node.TState = ObjectWaiter::TS_WAIT;
    Self->_ParkEvent->reset();
    OrderAccess::fence();
    //用自旋方式获取操作waitset的lock,因为一般只有owner线程会操作这个waitset(无论是wait还是notify),所以竞争概率很小(除非响应interrupt事件才会有争用),采用spin方式效率高
    Thread::SpinAcquire(&_WaitSetLock, "WaitSet - add");
    //添加到waitset
    AddWaiter(&node);
    //释放锁,代表现在线程已经进入了waitset,接下来要park了
    Thread::SpinRelease(&_WaitSetLock);

    if ((SyncFlags & 4) == 0) {
        _Responsible = NULL;
    }
    intptr_t save = _recursions; // record the old recursion count
    _waiters++;                  // increment the number of waiters
    _recursions = 0;             // set the recursion level to be 1
    exit(true, Self);                    // exit the monitor
    guarantee(_owner != Self, "invariant");

    // 确保没有unpark事件冲突影响本次park,方法就是主动post一次unpark
    if (node._notified != 0 && _succ == Self) {
        node._event->unpark();
    }

    // 接下来就是park操作了
    。。。。。。。。。
    。。。。。。。。。
}

当另一个owner线程调用notify时,根据Knob_MoveNotifyee这个值,决定将从waitset里面取出的一个线程放到哪里(cxq或者EntrySet)

Object.notify()底层实现

void ObjectMonitor::notify(TRAPS) {
    //检查当前线程是否拥有锁
    CHECK_OWNER();
    if (_WaitSet == NULL) {
        TEVENT(Empty - Notify);
        return;
    }
    DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);
    //决定取出来的线程放在哪里
    int Policy = Knob_MoveNotifyee;
    //同样的,用自旋方式获取操作waitset的lock
    Thread::SpinAcquire(&_WaitSetLock, "WaitSet - notify");
    ObjectWaiter *iterator = DequeueWaiter();
    if (iterator != NULL) {
        TEVENT(Notify1 - Transfer);
        guarantee(iterator->TState == ObjectWaiter::TS_WAIT, "invariant");
        guarantee(iterator->_notified == 0, "invariant");
        if (Policy != 4) {
            iterator->TState = ObjectWaiter::TS_ENTER;
        }
        iterator->_notified = 1;
        Thread *Self = THREAD;
        iterator->_notifier_tid = Self->osthread()->thread_id();

        ObjectWaiter *List = _EntryList;
        if (List != NULL) {
            assert(List->_prev == NULL, "invariant");
            assert(List->TState == ObjectWaiter::TS_ENTER, "invariant");
            assert(List != iterator, "invariant");
        }

        if (Policy == 0) {       // prepend to EntryList
            if (List == NULL) {
                iterator->_next = iterator->_prev = NULL;
                _EntryList = iterator;
            } else {
                List->_prev = iterator;
                iterator->_next = List;
                iterator->_prev = NULL;
                _EntryList = iterator;
            }
        } else if (Policy == 1) {      // append to EntryList
            if (List == NULL) {
                iterator->_next = iterator->_prev = NULL;
                _EntryList = iterator;
            } else {
                // CONSIDER:  finding the tail currently requires a linear-time walk of
                // the EntryList.  We can make tail access constant-time by converting to
                // a CDLL instead of using our current DLL.
                ObjectWaiter *Tail;
                for (Tail = List; Tail->_next != NULL; Tail = Tail->_next);
                assert(Tail != NULL && Tail->_next == NULL, "invariant");
                Tail->_next = iterator;
                iterator->_prev = Tail;
                iterator->_next = NULL;
            }
        } else if (Policy == 2) {      // prepend to cxq
            // prepend to cxq
            if (List == NULL) {
                iterator->_next = iterator->_prev = NULL;
                _EntryList = iterator;
            } else {
                iterator->TState = ObjectWaiter::TS_CXQ;
                for (;;) {
                    ObjectWaiter *Front = _cxq;
                    iterator->_next = Front;
                    if (Atomic::cmpxchg_ptr(iterator, &_cxq, Front) == Front) {
                        break;
                    }
                }
            }
        } else if (Policy == 3) {      // append to cxq
            iterator->TState = ObjectWaiter::TS_CXQ;
            for (;;) {
                ObjectWaiter *Tail;
                Tail = _cxq;
                if (Tail == NULL) {
                    iterator->_next = NULL;
                    if (Atomic::cmpxchg_ptr(iterator, &_cxq, NULL) == NULL) {
                        break;
                    }
                } else {
                    while (Tail->_next != NULL) Tail = Tail->_next;
                    Tail->_next = iterator;
                    iterator->_prev = Tail;
                    iterator->_next = NULL;
                    break;
                }
            }
        } else {
            ParkEvent *ev = iterator->_event;
            iterator->TState = ObjectWaiter::TS_RUN;
            OrderAccess::fence();
            ev->unpark();
        }

        if (Policy < 4) {
            iterator->wait_reenter_begin(this);
        }

        // _WaitSetLock protects the wait queue, not the EntryList.  We could
        // move the add-to-EntryList operation, above, outside the critical section
        // protected by _WaitSetLock.  In practice that's not useful.  With the
        // exception of  wait() timeouts and interrupts the monitor owner
        // is the only thread that grabs _WaitSetLock.  There's almost no contention
        // on _WaitSetLock so it's not profitable to reduce the length of the
        // critical section.
    }
    //释放waitset的lock
    Thread::SpinRelease(&_WaitSetLock);

    if (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) {
        ObjectMonitor::_sync_Notifications->inc();
    }
}

为什么这三个方法都定义在 Object 类而不是 Thread 类中??

只有同一把锁上的调用 wait() 被阻塞的线程可以被 notify() 唤醒,对于不同锁的线程不起作用,而 Java 中任意对象都可以作为一把锁,所以这三个方法可以被任意对象调用,应定义在 Object 类中,线程通过锁对象找到对应的 Monitor

如果定义在 Thread 类中,线程在调用 wait() 和 notify() 的时候针对的是哪一把锁便无从判断,提高了管理上的难度

protected void finalize();

finalize方法主要与Java垃圾回收机制有关。首先我们看一下finalized方法在Object中的具体定义:

protected void finalize() throws Throwable { }

我们发现Object类中finalize方法被定义成一个空方法,为什么要如此定义呢?finalize方法的调用时机是怎么样的呢?

首先,Object中定义finalize方法表明Java中每一个对象都将具有finalize这种行为,其具体调用时机在:JVM准备对此对形象所占用的内存空间进行垃圾回收前,将被调用。由此可以看出,此方法并不是由我们主动去调用的(虽然可以主动去调用,此时与其他自定义方法无异)。

当确定一个对象不会被其他方法再使用时,该对象就没有存在的意义了,就只能等待JVM的垃圾回收线程来回收了。垃圾回收是以占用一定内存资源为代价的。System.gc();就是启动垃圾回收线程的语句。当用户认为需要回收时,可以使用Runtime.getRuntime( ).gc( );或者System.gc();来回收内存。(System.gc();调用的就是Runtime类的gc( )方法) 当一个对象在回收前想要执行一些操作,就要覆写Object类中的finalize( )方法。

注意到抛出的是Throwable,说明除了常规的异常Exceprion外,还有可能是JVM错误。说明调用该方法不一定只会在程序中产生异常,还有可能产生JVM错误。

Search

    微信好友

    博士的沙漏

    Table of Contents