想写这个系列非常久了,对自己也是个总结与提高。原来在学JAVA时。那些JAVA入门书籍会告诉你一些规律还有法则,可是用的时候我们一般非常难想起来,由于我们用的少而且不知道为什么。知其所以然方能印象深刻并学以致用。

首先我们从所有类的父类Object開始:

1. Object类

(1)hashCode方法和equals方法

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

Java内规定,hashCode方法的结果须要与equals方法一致。也就是说。假设两个对象的hashCode同样,那么两个对象调用equals方法的结果须要一致。那么也就是在以后的java程序设计中。你须要同一时候覆盖这两个方法来保证一致性。

在Object代码中,hashCode是native的。非java代码实现。主要原因是它的实现方法是通过将对象在内存中所处于的位置转换成数字,这个数字就是hashCode。

可是这个内存地址实际上java程序并不关心也是不可知的。这个地址是由JVM维护并保存的,所以实现是native的。

假设两个Object的hashCode一样,那么就代表两个Object的内存地址一样,实际上他们就是同一个对象。所以。Object的equals实现就是看两个对象指针是否相等(是否是同一个对象)

在JAVA程序设计中,对于hashCode方法须要满足:

1.在程序执行过程中,同一个对象的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;
}

String就是一种典型的适合在分布式的情况下作为key的存储对象。不管程序何时在哪里执行,同一个String的hashCode结果都是一样的。

2.假设两个对象是euqal的,那么hashCode要同样

3.建议但不强制对于不相等的对象的hashCode一定要不同。这样能够有效降低hash冲突

hashCode主要用于集合类中的hashmap(hashset的底层实现也是hashmap)。之后我们在Set源代码部分会继续细说。

对于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

自己实现这些方法一定要满足这些条件,否则再用Java其它数据结构时会有意想不到的结果,后面会在回想这个问题。

(2)toString( )方法——打印对象的信息

这个没啥好说的,默认的实现就是class名字@hashcode(假设hashCode()方法也是默认的话,那么就是如之前所述地址)

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

(3) wait(), notify(), notifyAll()

这些属于主要的Java多线程同步类的API,都是native实现:

public final native void wait(long timeout) throws InterruptedException;
public final native void notify();
public final native void 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()底层实现

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemh4ZGljaw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

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

对于NotifyAll就非常好猜測了,这里不再赘述。

(4)clone方法

默认的是浅拷贝,至于为什么,这里不赘述。我们直接看源代码:

对于object的clone,我们须要考虑是否为数组,还有jvm环境等等,详细能够參考我的还有一篇文章:垃圾收集分析(1)-Java对象结构(上)

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

(5) finalize()方法——对象的回收

当确定一个对象不会被其它方法再使用时,该对象就没有存在的意义了,就仅仅能等待JVM的垃圾回收线程来回收了。

垃圾回收是以占用一定内存资源为代价的。System.gc();就是启动垃圾回收线程的语句。

当用户觉得须要回收时,能够使用Runtime.getRuntime( ).gc( );或者System.gc();来回收内存。(System.gc();调用的就是Runtime类的gc( )方法)

当一个对象在回收前想要执行一些操作。就要覆写Object类中的finalize( )方法。

protected void finalize() throws Throwable { }

注意到抛出的是Throwable,说明除了常规的异常Exceprion外,还有可能是JVM错误。

说明调用该方法不一定仅仅会在程序中产生异常,还有可能产生JVM错误。

JDK核心JAVA源代码解析(1) - Object的更多相关文章

  1. PMD:Java源代码扫描器

    PMD是一个开源代码分析器.可以查找常见编程缺陷,比如未使用的变量.空catch代码块.不必要的对象创建等.支持Java.JavaScript.PLSQL.Apache Velocity.XML.XS ...

  2. [ 转载 ] Java基础10--关于Object类下所有方法的简单解析

    关于Object类下所有方法的简单解析 类Object是类层次结构的根类,是每一个类的父类,所有的对象包括数组,String,Integer等包装类,所以了解Object是很有必要的,话不多说,我们直 ...

  3. NIO框架之MINA源代码解析(二):mina核心引擎

    NIO框架之MINA源代码解析(一):背景 MINA的底层还是利用了jdk提供了nio功能,mina仅仅是对nio进行封装.包含MINA用的线程池都是jdk直接提供的. MINA的server端主要有 ...

  4. Android EventBus源代码解析 带你深入理解EventBus

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...

  5. Java XML解析器

    使用Apache Xerces解析XML文档 一.技术概述 在用Java解析XML时候,一般都使用现成XML解析器来完成,自己编码解析是一件很棘手的问题,对程序员要求很高,一般也没有专业厂商或者开源组 ...

  6. Java注解解析-搭建自己的注解处理器(CLASS注解使用篇)

    该文章是继Java注解解析-基础+运行时注解(RUNTIME)之后,使用注解处理器处理CLASS注解的文章.通过完整的Demo例子介绍整个注解处理器的搭建流程以及注意事项,你将知道如何去搭建自己的注解 ...

  7. JDK(Java Development Kit)内置常用自带工具一览(转)

    注意:可能随着JDK的版本升级,工具也会随着增多. JDK(Java Development Kit)是Java程序员最核心的开发工具,没有之一. JDK是一个功能强大的Java开发套装,它不仅仅为我 ...

  8. Spark技术内幕:Client,Master和Worker 通信源代码解析

    Spark的Cluster Manager能够有几种部署模式: Standlone Mesos YARN EC2 Local 在向集群提交计算任务后,系统的运算模型就是Driver Program定义 ...

  9. 理解Java中对象基础Object类

    一.Object简述 源码注释:Object类是所有类层级关系的Root节点,作为所有类的超类,包括数组也实现了该类的方法,注意这里说的很明确,指类层面. 所以在Java中有一句常说的话,一切皆对象, ...

随机推荐

  1. Namespace declaration statement has to be the very first statement in the script

    php 中 Namespace declaration statement has to be the very first statement in the script 错误解决方法: 在PHP文 ...

  2. 7. python 字符串格式化方法(1)

    7. python 字符串格式化方法(1) 承接上一章节,我们这一节来说说字符串格式化的另一种方法,就是调用format() >>> template='{0},{1} and {2 ...

  3. java中初始化对象变量的方法

    1.在类定义对象的地方初始化 2.在类构造器中初始化 3.在正要使用这些对象之前,惰性初始化,或者叫惰性载入 4.使用实例初始化    在方法里使用初始化

  4. python笔记26-命令行传参sys.argv实际运用

    前言 平常我们在用别人写好的python包的时候,在cmd输入xx -h就能查看到帮助信息,输入xx -p 8080就能把参数传入程序里,看起来非常酷. 本篇就来讲下如何在python代码里加入命令行 ...

  5. javax.mail 遇到501 mail from address must be same as authorization user 的問題

    使用不同的兩個帳戶发送email时,第一个账户可以发送成功,但到第二个账户的时候就报出了501 mail from address must be same as authorization user ...

  6. 常见的Hadoop十大应用误解

    常见的Hadoop十大应用误解 1.        (误解) Hadoop什么都可以做 (正解) 当一个新技术出来时,我们都会去思考它在各个不同产业的应用,而对于平台的新技术来说,我们思考之后常会出现 ...

  7. RestClient POST

    SSL: ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyError ...

  8. UVA 10194 (13.08.05)

    :W Problem A: Football (aka Soccer)  The Problem Football the most popular sport in the world (ameri ...

  9. iOS开发-iPad侧边栏Tab选项卡切换

    Android中习惯了叫侧边栏,iOS中如果不习惯侧边栏称呼的话可以叫dock,侧边栏的切换,类似于Android中的底部导航栏的切换,iPad尺寸大了一些,导航的栏目放在侧边会显示的更好耐看一些.选 ...

  10. Anagrams leetcode java

    题目: Given an array of strings, return all groups of strings that are anagrams. Note: All inputs will ...