JDK核心JAVA源代码解析(1) - Object
想写这个系列非常久了,对自己也是个总结与提高。原来在学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的更多相关文章
- PMD:Java源代码扫描器
PMD是一个开源代码分析器.可以查找常见编程缺陷,比如未使用的变量.空catch代码块.不必要的对象创建等.支持Java.JavaScript.PLSQL.Apache Velocity.XML.XS ...
- [ 转载 ] Java基础10--关于Object类下所有方法的简单解析
关于Object类下所有方法的简单解析 类Object是类层次结构的根类,是每一个类的父类,所有的对象包括数组,String,Integer等包装类,所以了解Object是很有必要的,话不多说,我们直 ...
- NIO框架之MINA源代码解析(二):mina核心引擎
NIO框架之MINA源代码解析(一):背景 MINA的底层还是利用了jdk提供了nio功能,mina仅仅是对nio进行封装.包含MINA用的线程池都是jdk直接提供的. MINA的server端主要有 ...
- Android EventBus源代码解析 带你深入理解EventBus
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...
- Java XML解析器
使用Apache Xerces解析XML文档 一.技术概述 在用Java解析XML时候,一般都使用现成XML解析器来完成,自己编码解析是一件很棘手的问题,对程序员要求很高,一般也没有专业厂商或者开源组 ...
- Java注解解析-搭建自己的注解处理器(CLASS注解使用篇)
该文章是继Java注解解析-基础+运行时注解(RUNTIME)之后,使用注解处理器处理CLASS注解的文章.通过完整的Demo例子介绍整个注解处理器的搭建流程以及注意事项,你将知道如何去搭建自己的注解 ...
- JDK(Java Development Kit)内置常用自带工具一览(转)
注意:可能随着JDK的版本升级,工具也会随着增多. JDK(Java Development Kit)是Java程序员最核心的开发工具,没有之一. JDK是一个功能强大的Java开发套装,它不仅仅为我 ...
- Spark技术内幕:Client,Master和Worker 通信源代码解析
Spark的Cluster Manager能够有几种部署模式: Standlone Mesos YARN EC2 Local 在向集群提交计算任务后,系统的运算模型就是Driver Program定义 ...
- 理解Java中对象基础Object类
一.Object简述 源码注释:Object类是所有类层级关系的Root节点,作为所有类的超类,包括数组也实现了该类的方法,注意这里说的很明确,指类层面. 所以在Java中有一句常说的话,一切皆对象, ...
随机推荐
- The YubiKey -- HOW IT WORKS
A single YubiKey has multiple functions for protecting access to your email, your apps and your phys ...
- eclipse鼠标变成十字架
不知道按到什么或者点到什么button了,在eclipse里面鼠标就变成了十字架形式.解决的方法是按:alt+shift+a 原来alt+shift+a是框选代码的.长见识了!
- 图片未完成加载显示loading
<html> <title>js图片未加载完显示loading效果</title> <body> <style> img{float:lef ...
- OWIN and Katana
OWIN(Open Web Interface for .NET)是在.net的web server和web应用之间定义了一套规范. Katana是微软实现了OWIN的一个Web Server的项 ...
- AES算法工具类
什么是对称加密算法? AES已经变成目前对称加密中最流行算法之一:AES可以使用128.192.和256位密钥,并且用128位分组加密和解密数据. 对称加密算法安全吗? 看过间谍局的知友们一定知道电台 ...
- [Git] 写文章 史上最全文献检索、阅读及管理攻略
copy from : https://zhuanlan.zhihu.com/p/30605683 一.查文献 首先,我认为需要常备几个体量大.文献全的数据库,有针对性找哦!下面列出了一些适合所有专 ...
- [PHP] ubuntu 16.04系统下解决MySQL 5.7版本的root用户重置密码问题
reference to : http://www.cnblogs.com/roadofstudy/p/7446690.html 最近在ubuntu系统上安装了MySQL,但是安装时没有提示输入roo ...
- MySQL对索引的使用
什么是索引 使用索引可快速访问数据库表中的特定信息.索引是对数据库表中一列或多列的值进行排序的一种结构,例如 order 表的订单号(orderNum)列.如果要按订单号查找特定订单,与必须搜索表中的 ...
- OpenCV学习(28) 轮廓
OpenCV中可以方便的在一副图像中检测到轮廓,并把这些轮廓画出来.主要用到两个函数:一个是findContours( img, contours0, hierarchy, RETR_TREE, CH ...
- 80端口占用异常解决方法java.net.BindException: Address already in use: JVM_Bind:80(或8080)
1:Tomcat(或其他Web容器)启动时控制台报错如下示: 2007-8-2 15:20:43 org.apache.coyote.http11.Http11Protocol init 严重: Er ...