一,Object类简述
Object类是Java中所有类的基类,在编译时会自动导入,位于java.lang包中,而Object中具有的属性和行为,是Java语言设计背后的思维体现。这里写的代码是JDK11中的,其他版本的JDK可能略有不同。
包含的方法如下图:
其中,两个protected方法没有实现,其他的都有;线程相关的方法以及getClass()不支持重写(自定义),其他的都支持
二、源码解析
1、registerNatives()方法
- 类首次加载时执行
初始化静态变量、调用静态方法
private static native void registerNatives();
static {
registerNatives();
}
registerNatives如何找到调用C语言方法(OpenJDK)???
答:本地方法调用遵守JNI命名规范,即:要求本地方法名由 “Java”+“包名”+“方法名”构成
所以,该方法对应的关键字就是【 】
接下来我们全文检索一下:
果然!找到该方法在Object.c里2、Object()构造方法
@HotSpotIntrinsicCandidate:在HotSpot中都有一套高效的实现,该高效实现基于CPU指令,运行时,HotSpot维护的高效实现会替代JDK的源码实现,从而获得更高的效率。
@HotSpotIntrinsicCandidate
public Object() {}
3、getClass()方法
final,说明此方法不能被重写
- 返回Class对象,可用于反射进行处理
- Class对象表示运行时此对象的类
debug:经过一系列的String.class处理,启动一个线程类,最终跑到LauncherHelper.class,启动ClassLoader加载一个class,而后利用虚拟机的PostVMInitHook.class完成结果输出;
@HotSpotIntrinsicCandidate
public final native Class<?> getClass();
4、hashCode()方法
返回对象的对象的哈希码,是一个整数。这个方法的好处是提供了对诸如HashMap等哈希表的支持。
- debug:hash code是经过String.class和BaseLocale.class重写的hashCode方法,不断计算得出。
hashCode到底是什么?是不是对象的内存地址?@HotSpotIntrinsicCandidate
public native int hashCode();
1) 直接用内存地址?
目标:通过一个Demo验证这个hasCode到底是不是内存地址
```java import java.util.ArrayList; import java.util.List;public native int hashCode();
public class HashCodeTest {
//目标:只要发生重复,说明hashcode不是内存地址,但还需要证明(JVM代码证明)
public static void main(String[] args) {
List
}
}
15万个循环,发生了重复,说明hashCode不是内存地址(严格的说,肯定不是直接取的内存地址)<br />
<a name="e80b8a68"></a>
#### 2) 不是地址那在哪里?
既然不是内存地址,那一定在某个地方存着,那在哪里存着呢?
答案:在对象头里!(画图。类在jvm内存中的布局)
```java
-XX:+UseCompressedOops // 开启指针压缩
-XX:-UseCompressedOops // 关闭指针压缩
3) 什么时候生成的?
new的瞬间就有hashcode了吗??
show me the code!我们用代码验证
引入pom
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
编写ShowHashCode
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class ShowHashCode {
public static void main(String[] args) {
ShowHashCode a = new ShowHashCode();
//jvm的信息
System.out.println(VM.current().details());
System.out.println("-------------------------");
//调用之前打印a对象的头信息
//以表格的形式打印对象布局
System.out.println(ClassLayout.parseInstance(a).toPrintable());
System.out.println("-------------------------");
//调用后再打印a对象的hashcode值
System.out.println(Integer.toHexString(a.hashCode()));
System.out.println(ClassLayout.parseInstance(a).toPrintable());
System.out.println("-------------------------");
//有线程加重量级锁的时候,再来看对象头
new Thread(()->{
try {
synchronized (a){
Thread.sleep(5000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
System.out.println(Integer.toHexString(a.hashCode()));
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
结果分析
结论:在你没有调用的时候,这个值是空的,当第一次调用hashCode方法时,会生成,加锁以后,不知道去哪里了……
4) 怎么生成的?
接上文 , 我们追究一下,它详细的生成及移动过程。
我们都知道,这货是个本地方法
public native int hashCode();
那就需要借助上面提到的办法,通过JVM虚拟机源码,查看hashcode的生成
全局检索JVM_IHashCode
src\share\vm\prims\jvm.cpp
JVM_ENTRY是一个预加载宏,增加一些样板代码到jvm的所有function中
这个api是位于本地方法与jdk之间的一个连接层。
所以,此处才是生成hashCode的逻辑!
继续,ObjectSynchronizer::FastHashCode
先说生成流程,留个印象:
intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
//是否开启了偏向锁(Biased:偏向,倾向)
if (UseBiasedLocking) {
//如果当前对象处于偏向锁状态
if (obj->mark()->has_bias_pattern()) {
Handle hobj (Self, obj) ;
assert (Universe::verify_in_progress() ||
!SafepointSynchronize::is_at_safepoint(),
"biases should not be seen by VM thread here");
//那么就撤销偏向锁(达到无锁状态,revoke:废除)
BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
obj = hobj() ;
//断言下,看看是否撤销成功(撤销后为无锁状态)
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
}
// ……
ObjectMonitor* monitor = NULL;
markOop temp, test;
intptr_t hash;
//读出一个稳定的mark;防止对象obj处于膨胀状态;
//如果正在膨胀,就等他膨胀完毕再读出来
markOop mark = ReadStableMark (obj);
//是否撤销了偏向锁(也就是无锁状态)(neutral:中立,不偏不斜的)
if (mark->is_neutral()) {
//从mark头上取hash值
hash = mark->hash();
//如果有,直接返回这个hashcode(xor)
if (hash) { // if it has hash, just return it
return hash;
}
//如果没有就新生成一个(get_next_hash)
hash = get_next_hash(Self, obj); // allocate a new hash code
//生成后,原子性设置,将hash放在对象头里去,这样下次就可以直接取了
temp = mark->copy_set_hash(hash); // merge the hash code into header
// use (machine word version) atomic operation to install the hash
test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
if (test == mark) {
return hash;
}
// If atomic operation failed, we must inflate the header
// into heavy weight monitor. We could add more code here
// for fast path, but it does not worth the complexity.
//如果已经升级成了重量级锁,那么找到它的monitor
//也就是我们所说的内置锁(objectMonitor),这是c里的数据类型
//因为锁升级后,mark里的bit位已经不再存储hashcode,而是指向monitor的地址
//而升级的markword呢?被移到了c的monitor里
} else if (mark->has_monitor()) {
//沿着monitor找header,也就是对象头
monitor = mark->monitor();
temp = monitor->header();
assert (temp->is_neutral(), "invariant") ;
//找到header后取hash返回
hash = temp->hash();
if (hash) {
return hash;
}
// Skip to the following code to reduce code size
} else if (Self->is_lock_owned((address)mark->locker())) {
//轻量级锁的话,也是从java对象头移到了c里,叫helper
temp = mark->displaced_mark_helper(); // this is a lightweight monitor owned
assert (temp->is_neutral(), "invariant") ;
hash = temp->hash(); // by current thread, check if the displaced
//找到,返回
if (hash) { // header contains hash code
return hash;
}
}
......略
问:
为什么要先撤销偏向锁到无锁状态,再来生成hashcode呢?这跟锁有什么关系?
答:
mark word里,hashcode存储的字节位置被偏向锁给占了!偏向锁存储了锁持有者的线程id
扩展:关于hashCode的生成算法(了解)
// hashCode() generation :
// 涉及到c++算法领域,感兴趣的同学自行研究
// Possibilities:
// * MD5Digest of {obj,stwRandom}
// * CRC32 of {obj,stwRandom} or any linear-feedback shift register function.
// * A DES- or AES-style SBox[] mechanism
// * One of the Phi-based schemes, such as:
// 2654435761 = 2^32 * Phi (golden ratio)
// HashCodeValue = ((uintptr_t(obj) >> 3) * 2654435761) ^ GVars.stwRandom ;
// * A variation of Marsaglia's shift-xor RNG scheme.
// * (obj ^ stwRandom) is appealing, but can result
// in undesirable regularity in the hashCode values of adjacent objects
// (objects allocated back-to-back, in particular). This could potentially
// result in hashtable collisions and reduced hashtable efficiency.
// There are simple ways to "diffuse" the middle address bits over the
// generated hashCode values:
//
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so it's possible for two threads to race and generate the same RNG.
// On MP system we'll have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random() ;//返回随机数
} else if (hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
//和地址相关,但不是地址;右移+异或算法
intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;//随机数位移异或计算
} else if (hashCode == 2) {
value = 1 ; // 返回1
} else if (hashCode == 3) {
value = ++GVars.hcSequence ;//返回一个Sequence序列号
} else if (hashCode == 4) {
value = cast_from_oop<intptr_t>(obj) ;//也不是地址
} else {
//常用
// Marsaglia's xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we'll
// likely make this the default in future releases.
//马萨利亚教授写的xor-shift 随机数算法(异或随机算法)
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
5)总结
通过分析虚拟机源码我们证明了hashCode不是直接用的内存地址,而是采取一定的算法来生成
hashcode值的存储在mark word里,与锁共用一段bit位,这就造成了跟锁状态相关性
- 如果是偏向锁:
一旦调用hashcode,偏向锁将被撤销,hashcode被保存占位mark word,对象被打回无锁状态
- 那偏偏这会就是有线程硬性使用对象的锁呢?
对象再也回不到偏向锁状态而是升级为重量级锁。hash code跟随mark word被移动到c的object monitor,从那里取
5、equals()方法
1) equals源码
- equals方法主要是比较两个对象是否相同,Object中的equals方法比较的是对象的地址是否相同。
猜想:如果我们不做任何操作,equals将继承object的方法,那么它和==也没啥区别!public boolean equals(Object obj) {
// 比较的是两个对象的内存地址
return (this == obj);
}
下面一起做个面试题,验证一下这个猜想:
public class DefaultEq {
String name;
public DefaultEq(String name){
this.name = name;
}
public static void main(String[] args) {
DefaultEq eq1 = new DefaultEq("张三");
DefaultEq eq2 = new DefaultEq("张三");
DefaultEq eq3 = eq1;
//虽然俩对象外面看起来一样,eq和==都不行
//因为我们没有改写equals,它使用默认object的,也就是内存地址
System.out.println(eq1.equals(eq2));
System.out.println(eq1 == eq2);
System.out.println("----");
//1和3是同一个引用
System.out.println(eq1.equals(eq3));
System.out.println(eq1 == eq3);
System.out.println("===");
//以上是对象,再来看基本类型
int i1 = 1;
Integer i2 = 1;
Integer i = new Integer(1);
Integer j = new Integer(1);
Integer k = new Integer(2);
//只要是基本类型,不管值还是包装成对象,都是直接比较大小
System.out.println(i.equals(i1)); //比较的是值
System.out.println(i==i1); //拆箱 ,
// 封装对象i被拆箱,变为值比较,1==1成立
//相当于 System.out.println(1==1);
System.out.println(i.equals(j)); //
System.out.println(i==j); // 比较的是地址,这是俩对象
System.out.println(i2 == i); // i2在常量池里,i在堆里,地址不一样
System.out.println(i.equals(k)); //1和2,不解释
}
}
结果
false
false
----
true
true
===
true
true
true
false
false
false
结论:
- “==”比较的是什么?
用于基本数据(8种)类型(或包装类型)相互比较,比较二者的值是否相等。
用于引用数据(类、接口、数组)类型相互比较,比较二者地址是否相等。 - equals比较的什么?
默认情况下,所有对象继承Object,而Object的equals比较的就是内存地址
所以默认情况下,这俩没啥区别2) 内存地址生成与比较
tips:既然没区别,那我们看一下,内存地址到底是个啥玩意
目标:内存地址是如何来的?
Main.java
public static void main(String[] args) {
User user1=new User("张三");
User user2=new User("张三");
}
1、加载过程(回顾)
从java文件到jvm:
tips: 加载到方法区
这个阶段只是User类的信息进入方法区,还没有为两个user来分配内存
2、分配内存空间
在main线程执行阶段,指针碰撞(连续内存空间时),或者空闲列表(不连续空间)方式开辟一块堆内存
每次new一个,开辟一块,所以两个new之间肯定不是相同地址,哪怕你new的都是同一个类型的class。
那么它如何来保证内存地址不重复的呢?(cas画图)
3、指向
在栈中创建两个局部变量 user1,user2,指向堆里的内存
归根到底,上面的==比较的是两个对象的堆内存地址,也就是栈中局部变量表里存储的值。
public boolean equals(Object obj) {
return (this == obj);//本类比较的是内存地址(引用)
}
6、Clone()方法
- clone方法是创建并且返回一个对象的复制之后的结果。复制的含义取决于对象的类定义。
// 一般在原型模式时使用
// 使用时需要让被拷贝的对象所属的类实现Cloneable接口,才能调用该方法进行拷贝。
@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
6.1 浅拷贝
准备一个房子对象
public class House {
private String addr;
public House(String addr) {
this.setAddr(addr);
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
准备一个人对象,并重写clone()方法
public class Person implements Cloneable {
private String name;
private House house;
@Override
public Object clone() {
// 浅拷贝
Object obj=null;
//调用Object类的clone方法,返回一个Object实例
try {
obj= super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
public Person(String name, House house) {
this.name = name;
this.house = house;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public House getHouse() {
return house;
}
public void setHouse(House house) {
this.house = house;
}
}
测试
public class ShallowClone {
public static void main(String[] args) {
House house = new House("地址");
Person person = new Person("名字", house);
Person personClone = (Person) person.clone();
// 未修改的场合
System.out.println(person.getHouse().getAddr());
System.out.println(personClone.getHouse().getAddr());
// 修改原对象属性,发现属性值跟着变化
person.getHouse().setAddr("新地址");
System.out.println(person.getHouse().getAddr());
System.out.println(personClone.getHouse().getAddr());
// 修改复制对象的属性,发现属性值照样跟着变化
personClone.getHouse().setAddr("旧地址");
System.out.println(person.getHouse().getAddr());
System.out.println(personClone.getHouse().getAddr());
}
}
结果
地址
地址
新地址
新地址
旧地址
旧地址
结论
准备一个房子对象,并重写clone()方法
public class House implements Cloneable{
private String addr;
public House(String addr) {
this.setAddr(addr);
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public Object clone() {
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
创建一个人的对象,并引用房子对象的clone方法
public class Person implements Cloneable {
private String name;
private House house;
@Override
public Object clone() {
// 浅拷贝
Object obj=null;
//调用Object类的clone方法,返回一个Object实例
try {
obj= super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
Person person = (Person) obj;
person.house = (House) person.getHouse().clone();
return obj;
}
public Person(String name, House house) {
this.name = name;
this.house = house;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public House getHouse() {
return house;
}
public void setHouse(House house) {
this.house = house;
}
}
测试
public class ShallowClone {
public static void main(String[] args) {
House house = new House("地址");
Person person = new Person("名字", house);
Person personClone = (Person) person.clone();
// 未修改的场合
System.out.println(person.getHouse().getAddr());
System.out.println(personClone.getHouse().getAddr());
// 修改原对象属性,只有原对象属性值更改
person.getHouse().setAddr("新地址");
System.out.println(person.getHouse().getAddr());
System.out.println(personClone.getHouse().getAddr());
// 修改复制对象的属性,只有复制对象属性值更改
personClone.getHouse().setAddr("旧地址");
System.out.println(person.getHouse().getAddr());
System.out.println(personClone.getHouse().getAddr());
}
}
结果
地址
地址
新地址
地址
新地址
旧地址
结论
进行了深拷贝之后,无论是什么类型的属性值的修改,都不会影响另一个对象的属性值。
6.3 浅拷贝和深拷贝区别
浅拷贝: 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝: 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。7、toString()方法
返回方法名和十六位进制的hashcode,格式:com.java.lang.obj.Person@1d251891
public String toString() {
// 用于显示该对象的内容
// 一般需要重写
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
8、notify()方法
final,说明此方法不能被重写
// 唤醒对象的等待队列上的 一个线程
@HotSpotIntrinsicCandidate
public final native void notify();
9、notifyAll()方法
final,说明此方法不能被重写
// 唤醒所有 等待在这个对象的监视器上的线程
@HotSpotIntrinsicCandidate
public final native void notifyAll();
10、wait()方法
final,说明此方法不能被重写
// 使线程进入等待状态,直到被notify or notifyAll调用
public final void wait() throws InterruptedException {
wait(0L);
}
// 使线程在几秒钟内进入等待状态
public final native void wait(long timeoutMillis) throws InterruptedException;
// 使线程进入等待状态,直到被notify or notifyAll调用 or 等待时间达到限定值
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);
}
11、finalize()方法
@Deprecated(since="9")
protected void finalize() throws Throwable { }