一,Object类简述

Object类是Java中所有类的基类,在编译时会自动导入,位于java.lang包中,而Object中具有的属性和行为,是Java语言设计背后的思维体现。这里写的代码是JDK11中的,其他版本的JDK可能略有不同。
包含的方法如下图:
image.png
其中,两个protected方法没有实现,其他的都有;线程相关的方法以及getClass()不支持重写(自定义),其他的都支持

二、源码解析

1、registerNatives()方法

  • 类首次加载时执行
  • 初始化静态变量、调用静态方法

    1. private static native void registerNatives();
    2. static {
    3. registerNatives();
    4. }

    registerNatives如何找到调用C语言方法(OpenJDK)???
    答:本地方法调用遵守JNI命名规范,即:要求本地方法名由 “Java”+“包名”+“方法名”构成
    所以,该方法对应的关键字就是【 】
    接下来我们全文检索一下:
    image.png
    果然!找到该方法在Object.c里

    image.png

    2、Object()构造方法

  • @HotSpotIntrinsicCandidate:在HotSpot中都有一套高效的实现,该高效实现基于CPU指令,运行时,HotSpot维护的高效实现会替代JDK的源码实现,从而获得更高的效率。

    1. @HotSpotIntrinsicCandidate
    2. public Object() {}

    3、getClass()方法

  • final,说明此方法不能被重写

  • 返回Class对象,可用于反射进行处理
  • Class对象表示运行时此对象的类
  • debug:经过一系列的String.class处理,启动一个线程类,最终跑到LauncherHelper.class,启动ClassLoader加载一个class,而后利用虚拟机的PostVMInitHook.class完成结果输出;

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

    4、hashCode()方法

  • 返回对象的对象的哈希码,是一个整数。这个方法的好处是提供了对诸如HashMap等哈希表的支持。

  • debug:hash code是经过String.class和BaseLocale.class重写的hashCode方法,不断计算得出。
    1. @HotSpotIntrinsicCandidate
    2. public native int hashCode();
    hashCode到底是什么?是不是对象的内存地址?

    1) 直接用内存地址?

    目标:通过一个Demo验证这个hasCode到底是不是内存地址
    1. public native int hashCode();
    ```java import java.util.ArrayList; import java.util.List;

public class HashCodeTest { //目标:只要发生重复,说明hashcode不是内存地址,但还需要证明(JVM代码证明) public static void main(String[] args) { List integerList = new ArrayList(); int num = 0; for (int i = 0; i < 150000; i++) { //创建新的对象 Object object = new Object(); if (integerList.contains(object.hashCode())) { num++;//发生重复(内存地址肯定不会重复) } else { integerList.add(object.hashCode());//没有重复 } } System.out.println(num + “个hashcode发生重复”); System.out.println(“List合计大小” + integerList.size() + “个”);

  1. }

}

  1. 15万个循环,发生了重复,说明hashCode不是内存地址(严格的说,肯定不是直接取的内存地址)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22478710/1632968714998-a3a30f65-5651-408f-a1c1-96aeaea40999.png#clientId=ua1b95aa8-5bec-4&from=paste&height=103&id=u5fed3171&margin=%5Bobject%20Object%5D&name=image.png&originHeight=115&originWidth=358&originalType=binary&ratio=1&size=6395&status=done&style=none&taskId=uee14ec17-df16-4008-8ff1-2046f4422ad&width=321)
  2. <a name="e80b8a68"></a>
  3. #### 2) 不是地址那在哪里?
  4. 既然不是内存地址,那一定在某个地方存着,那在哪里存着呢?
  5. 答案:在对象头里!(画图。类在jvm内存中的布局)
  6. ```java
  7. -XX:+UseCompressedOops // 开启指针压缩
  8. -XX:-UseCompressedOops // 关闭指针压缩

image.png

3) 什么时候生成的?

new的瞬间就有hashcode了吗??

show me the code!我们用代码验证

引入pom

  1. <dependency>
  2. <groupId>org.openjdk.jol</groupId>
  3. <artifactId>jol-core</artifactId>
  4. <version>0.9</version>
  5. </dependency>

编写ShowHashCode

  1. import org.openjdk.jol.info.ClassLayout;
  2. import org.openjdk.jol.vm.VM;
  3. public class ShowHashCode {
  4. public static void main(String[] args) {
  5. ShowHashCode a = new ShowHashCode();
  6. //jvm的信息
  7. System.out.println(VM.current().details());
  8. System.out.println("-------------------------");
  9. //调用之前打印a对象的头信息
  10. //以表格的形式打印对象布局
  11. System.out.println(ClassLayout.parseInstance(a).toPrintable());
  12. System.out.println("-------------------------");
  13. //调用后再打印a对象的hashcode值
  14. System.out.println(Integer.toHexString(a.hashCode()));
  15. System.out.println(ClassLayout.parseInstance(a).toPrintable());
  16. System.out.println("-------------------------");
  17. //有线程加重量级锁的时候,再来看对象头
  18. new Thread(()->{
  19. try {
  20. synchronized (a){
  21. Thread.sleep(5000);
  22. }
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. }).start();
  27. System.out.println(Integer.toHexString(a.hashCode()));
  28. System.out.println(ClassLayout.parseInstance(a).toPrintable());
  29. }
  30. }

结果分析
image.png
image-20210430162355320.png
结论:在你没有调用的时候,这个值是空的,当第一次调用hashCode方法时,会生成,加锁以后,不知道去哪里了……

4) 怎么生成的?

接上文 , 我们追究一下,它详细的生成及移动过程。

我们都知道,这货是个本地方法

  1. public native int hashCode();

那就需要借助上面提到的办法,通过JVM虚拟机源码,查看hashcode的生成
全局检索JVM_IHashCode
image.png
src\share\vm\prims\jvm.cpp
JVM_ENTRY是一个预加载宏,增加一些样板代码到jvm的所有function中
这个api是位于本地方法与jdk之间的一个连接层。

所以,此处才是生成hashCode的逻辑!image.png
继续,ObjectSynchronizer::FastHashCode
image.png
image.png
先说生成流程,留个印象:
hashcode生成过程.jpg

  1. intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
  2. //是否开启了偏向锁(Biased:偏向,倾向)
  3. if (UseBiasedLocking) {
  4. //如果当前对象处于偏向锁状态
  5. if (obj->mark()->has_bias_pattern()) {
  6. Handle hobj (Self, obj) ;
  7. assert (Universe::verify_in_progress() ||
  8. !SafepointSynchronize::is_at_safepoint(),
  9. "biases should not be seen by VM thread here");
  10. //那么就撤销偏向锁(达到无锁状态,revoke:废除)
  11. BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
  12. obj = hobj() ;
  13. //断言下,看看是否撤销成功(撤销后为无锁状态)
  14. assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
  15. }
  16. }
  17. // ……
  18. ObjectMonitor* monitor = NULL;
  19. markOop temp, test;
  20. intptr_t hash;
  21. //读出一个稳定的mark;防止对象obj处于膨胀状态;
  22. //如果正在膨胀,就等他膨胀完毕再读出来
  23. markOop mark = ReadStableMark (obj);
  24. //是否撤销了偏向锁(也就是无锁状态)(neutral:中立,不偏不斜的)
  25. if (mark->is_neutral()) {
  26. //从mark头上取hash值
  27. hash = mark->hash();
  28. //如果有,直接返回这个hashcode(xor)
  29. if (hash) { // if it has hash, just return it
  30. return hash;
  31. }
  32. //如果没有就新生成一个(get_next_hash)
  33. hash = get_next_hash(Self, obj); // allocate a new hash code
  34. //生成后,原子性设置,将hash放在对象头里去,这样下次就可以直接取了
  35. temp = mark->copy_set_hash(hash); // merge the hash code into header
  36. // use (machine word version) atomic operation to install the hash
  37. test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
  38. if (test == mark) {
  39. return hash;
  40. }
  41. // If atomic operation failed, we must inflate the header
  42. // into heavy weight monitor. We could add more code here
  43. // for fast path, but it does not worth the complexity.
  44. //如果已经升级成了重量级锁,那么找到它的monitor
  45. //也就是我们所说的内置锁(objectMonitor),这是c里的数据类型
  46. //因为锁升级后,mark里的bit位已经不再存储hashcode,而是指向monitor的地址
  47. //而升级的markword呢?被移到了c的monitor里
  48. } else if (mark->has_monitor()) {
  49. //沿着monitor找header,也就是对象头
  50. monitor = mark->monitor();
  51. temp = monitor->header();
  52. assert (temp->is_neutral(), "invariant") ;
  53. //找到header后取hash返回
  54. hash = temp->hash();
  55. if (hash) {
  56. return hash;
  57. }
  58. // Skip to the following code to reduce code size
  59. } else if (Self->is_lock_owned((address)mark->locker())) {
  60. //轻量级锁的话,也是从java对象头移到了c里,叫helper
  61. temp = mark->displaced_mark_helper(); // this is a lightweight monitor owned
  62. assert (temp->is_neutral(), "invariant") ;
  63. hash = temp->hash(); // by current thread, check if the displaced
  64. //找到,返回
  65. if (hash) { // header contains hash code
  66. return hash;
  67. }
  68. }
  69. ......略

问:
为什么要先撤销偏向锁到无锁状态,再来生成hashcode呢?这跟锁有什么关系?
答:
mark word里,hashcode存储的字节位置被偏向锁给占了!偏向锁存储了锁持有者的线程id
扩展:关于hashCode的生成算法(了解)

  1. // hashCode() generation :
  2. // 涉及到c++算法领域,感兴趣的同学自行研究
  3. // Possibilities:
  4. // * MD5Digest of {obj,stwRandom}
  5. // * CRC32 of {obj,stwRandom} or any linear-feedback shift register function.
  6. // * A DES- or AES-style SBox[] mechanism
  7. // * One of the Phi-based schemes, such as:
  8. // 2654435761 = 2^32 * Phi (golden ratio)
  9. // HashCodeValue = ((uintptr_t(obj) >> 3) * 2654435761) ^ GVars.stwRandom ;
  10. // * A variation of Marsaglia's shift-xor RNG scheme.
  11. // * (obj ^ stwRandom) is appealing, but can result
  12. // in undesirable regularity in the hashCode values of adjacent objects
  13. // (objects allocated back-to-back, in particular). This could potentially
  14. // result in hashtable collisions and reduced hashtable efficiency.
  15. // There are simple ways to "diffuse" the middle address bits over the
  16. // generated hashCode values:
  17. //
  18. static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  19. intptr_t value = 0 ;
  20. if (hashCode == 0) {
  21. // This form uses an unguarded global Park-Miller RNG,
  22. // so it's possible for two threads to race and generate the same RNG.
  23. // On MP system we'll have lots of RW access to a global, so the
  24. // mechanism induces lots of coherency traffic.
  25. value = os::random() ;//返回随机数
  26. } else if (hashCode == 1) {
  27. // This variation has the property of being stable (idempotent)
  28. // between STW operations. This can be useful in some of the 1-0
  29. // synchronization schemes.
  30. //和地址相关,但不是地址;右移+异或算法
  31. intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
  32. value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;//随机数位移异或计算
  33. } else if (hashCode == 2) {
  34. value = 1 ; // 返回1
  35. } else if (hashCode == 3) {
  36. value = ++GVars.hcSequence ;//返回一个Sequence序列号
  37. } else if (hashCode == 4) {
  38. value = cast_from_oop<intptr_t>(obj) ;//也不是地址
  39. } else {
  40. //常用
  41. // Marsaglia's xor-shift scheme with thread-specific state
  42. // This is probably the best overall implementation -- we'll
  43. // likely make this the default in future releases.
  44. //马萨利亚教授写的xor-shift 随机数算法(异或随机算法)
  45. unsigned t = Self->_hashStateX ;
  46. t ^= (t << 11) ;
  47. Self->_hashStateX = Self->_hashStateY ;
  48. Self->_hashStateY = Self->_hashStateZ ;
  49. Self->_hashStateZ = Self->_hashStateW ;
  50. unsigned v = Self->_hashStateW ;
  51. v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
  52. Self->_hashStateW = v ;
  53. value = v ;
  54. }

5)总结

通过分析虚拟机源码我们证明了hashCode不是直接用的内存地址,而是采取一定的算法来生成

hashcode值的存储在mark word里,与锁共用一段bit位,这就造成了跟锁状态相关性

  • 如果是偏向锁:

一旦调用hashcode,偏向锁将被撤销,hashcode被保存占位mark word,对象被打回无锁状态

  • 那偏偏这会就是有线程硬性使用对象的锁呢?

对象再也回不到偏向锁状态而是升级为重量级锁。hash code跟随mark word被移动到c的object monitor,从那里取

5、equals()方法

1) equals源码

  • equals方法主要是比较两个对象是否相同,Object中的equals方法比较的是对象的地址是否相同。
    1. public boolean equals(Object obj) {
    2. // 比较的是两个对象的内存地址
    3. return (this == obj);
    4. }
    猜想:如果我们不做任何操作,equals将继承object的方法,那么它和==也没啥区别!
    下面一起做个面试题,验证一下这个猜想:
  1. public class DefaultEq {
  2. String name;
  3. public DefaultEq(String name){
  4. this.name = name;
  5. }
  6. public static void main(String[] args) {
  7. DefaultEq eq1 = new DefaultEq("张三");
  8. DefaultEq eq2 = new DefaultEq("张三");
  9. DefaultEq eq3 = eq1;
  10. //虽然俩对象外面看起来一样,eq和==都不行
  11. //因为我们没有改写equals,它使用默认object的,也就是内存地址
  12. System.out.println(eq1.equals(eq2));
  13. System.out.println(eq1 == eq2);
  14. System.out.println("----");
  15. //1和3是同一个引用
  16. System.out.println(eq1.equals(eq3));
  17. System.out.println(eq1 == eq3);
  18. System.out.println("===");
  19. //以上是对象,再来看基本类型
  20. int i1 = 1;
  21. Integer i2 = 1;
  22. Integer i = new Integer(1);
  23. Integer j = new Integer(1);
  24. Integer k = new Integer(2);
  25. //只要是基本类型,不管值还是包装成对象,都是直接比较大小
  26. System.out.println(i.equals(i1)); //比较的是值
  27. System.out.println(i==i1); //拆箱 ,
  28. // 封装对象i被拆箱,变为值比较,1==1成立
  29. //相当于 System.out.println(1==1);
  30. System.out.println(i.equals(j)); //
  31. System.out.println(i==j); // 比较的是地址,这是俩对象
  32. System.out.println(i2 == i); // i2在常量池里,i在堆里,地址不一样
  33. System.out.println(i.equals(k)); //1和2,不解释
  34. }
  35. }

结果

  1. false
  2. false
  3. ----
  4. true
  5. true
  6. ===
  7. true
  8. true
  9. true
  10. false
  11. false
  12. false

结论:

  • “==”比较的是什么?
    用于基本数据(8种)类型(或包装类型)相互比较,比较二者的值是否相等。
    用于引用数据(类、接口、数组)类型相互比较,比较二者地址是否相等。
  • equals比较的什么?
    默认情况下,所有对象继承Object,而Object的equals比较的就是内存地址
    所以默认情况下,这俩没啥区别

    2) 内存地址生成与比较

tips:既然没区别,那我们看一下,内存地址到底是个啥玩意

目标:内存地址是如何来的?

Main.java

  1. public static void main(String[] args) {
  2. User user1=new User("张三");
  3. User user2=new User("张三");
  4. }

1、加载过程(回顾)
从java文件到jvm:
image-20201215141221546-1609839266035.png

tips: 加载到方法区

这个阶段只是User类的信息进入方法区,还没有为两个user来分配内存

2、分配内存空间

在main线程执行阶段,指针碰撞(连续内存空间时),或者空闲列表(不连续空间)方式开辟一块堆内存

每次new一个,开辟一块,所以两个new之间肯定不是相同地址,哪怕你new的都是同一个类型的class。

那么它如何来保证内存地址不重复的呢?(cas画图)
image-20201215135137941-1609839266035.png
3、指向
在栈中创建两个局部变量 user1,user2,指向堆里的内存
归根到底,上面的==比较的是两个对象的堆内存地址,也就是栈中局部变量表里存储的值。

  1. public boolean equals(Object obj) {
  2. return (this == obj);//本类比较的是内存地址(引用)
  3. }

6、Clone()方法

  • clone方法是创建并且返回一个对象的复制之后的结果。复制的含义取决于对象的类定义。
    1. // 一般在原型模式时使用
    2. // 使用时需要让被拷贝的对象所属的类实现Cloneable接口,才能调用该方法进行拷贝。
    3. @HotSpotIntrinsicCandidate
    4. protected native Object clone() throws CloneNotSupportedException;

    6.1 浅拷贝

  • 准备一个房子对象

    1. public class House {
    2. private String addr;
    3. public House(String addr) {
    4. this.setAddr(addr);
    5. }
    6. public String getAddr() {
    7. return addr;
    8. }
    9. public void setAddr(String addr) {
    10. this.addr = addr;
    11. }
    12. }
  • 准备一个人对象,并重写clone()方法

    1. public class Person implements Cloneable {
    2. private String name;
    3. private House house;
    4. @Override
    5. public Object clone() {
    6. // 浅拷贝
    7. Object obj=null;
    8. //调用Object类的clone方法,返回一个Object实例
    9. try {
    10. obj= super.clone();
    11. } catch (CloneNotSupportedException e) {
    12. e.printStackTrace();
    13. }
    14. return obj;
    15. }
    16. public Person(String name, House house) {
    17. this.name = name;
    18. this.house = house;
    19. }
    20. public String getName() {
    21. return name;
    22. }
    23. public void setName(String name) {
    24. this.name = name;
    25. }
    26. public House getHouse() {
    27. return house;
    28. }
    29. public void setHouse(House house) {
    30. this.house = house;
    31. }
    32. }
  • 测试

    1. public class ShallowClone {
    2. public static void main(String[] args) {
    3. House house = new House("地址");
    4. Person person = new Person("名字", house);
    5. Person personClone = (Person) person.clone();
    6. // 未修改的场合
    7. System.out.println(person.getHouse().getAddr());
    8. System.out.println(personClone.getHouse().getAddr());
    9. // 修改原对象属性,发现属性值跟着变化
    10. person.getHouse().setAddr("新地址");
    11. System.out.println(person.getHouse().getAddr());
    12. System.out.println(personClone.getHouse().getAddr());
    13. // 修改复制对象的属性,发现属性值照样跟着变化
    14. personClone.getHouse().setAddr("旧地址");
    15. System.out.println(person.getHouse().getAddr());
    16. System.out.println(personClone.getHouse().getAddr());
    17. }
    18. }
  • 结果

    1. 地址
    2. 地址
    3. 新地址
    4. 新地址
    5. 旧地址
    6. 旧地址
  • 结论

    • 基本数据类型是值传递,所以修改值后不会影响另一个对象的该属性值;
    • 引用数据类型是地址传递(引用传递),所以修改值后另一个对象的该属性值会同步被修改。

      6.2 深拷贝

  • 准备一个房子对象,并重写clone()方法

    1. public class House implements Cloneable{
    2. private String addr;
    3. public House(String addr) {
    4. this.setAddr(addr);
    5. }
    6. public String getAddr() {
    7. return addr;
    8. }
    9. public void setAddr(String addr) {
    10. this.addr = addr;
    11. }
    12. @Override
    13. public Object clone() {
    14. Object obj = null;
    15. try {
    16. obj = super.clone();
    17. } catch (CloneNotSupportedException e) {
    18. e.printStackTrace();
    19. }
    20. return obj;
    21. }
    22. }
  • 创建一个人的对象,并引用房子对象的clone方法

    1. public class Person implements Cloneable {
    2. private String name;
    3. private House house;
    4. @Override
    5. public Object clone() {
    6. // 浅拷贝
    7. Object obj=null;
    8. //调用Object类的clone方法,返回一个Object实例
    9. try {
    10. obj= super.clone();
    11. } catch (CloneNotSupportedException e) {
    12. e.printStackTrace();
    13. }
    14. Person person = (Person) obj;
    15. person.house = (House) person.getHouse().clone();
    16. return obj;
    17. }
    18. public Person(String name, House house) {
    19. this.name = name;
    20. this.house = house;
    21. }
    22. public String getName() {
    23. return name;
    24. }
    25. public void setName(String name) {
    26. this.name = name;
    27. }
    28. public House getHouse() {
    29. return house;
    30. }
    31. public void setHouse(House house) {
    32. this.house = house;
    33. }
    34. }
  • 测试

    1. public class ShallowClone {
    2. public static void main(String[] args) {
    3. House house = new House("地址");
    4. Person person = new Person("名字", house);
    5. Person personClone = (Person) person.clone();
    6. // 未修改的场合
    7. System.out.println(person.getHouse().getAddr());
    8. System.out.println(personClone.getHouse().getAddr());
    9. // 修改原对象属性,只有原对象属性值更改
    10. person.getHouse().setAddr("新地址");
    11. System.out.println(person.getHouse().getAddr());
    12. System.out.println(personClone.getHouse().getAddr());
    13. // 修改复制对象的属性,只有复制对象属性值更改
    14. personClone.getHouse().setAddr("旧地址");
    15. System.out.println(person.getHouse().getAddr());
    16. System.out.println(personClone.getHouse().getAddr());
    17. }
    18. }
  • 结果

    1. 地址
    2. 地址
    3. 新地址
    4. 地址
    5. 新地址
    6. 旧地址
  • 结论

    • 进行了深拷贝之后,无论是什么类型的属性值的修改,都不会影响另一个对象的属性值。

      6.3 浅拷贝和深拷贝区别

      浅拷贝: 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
      深拷贝: 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

      7、toString()方法

    • 返回方法名和十六位进制的hashcode,格式:com.java.lang.obj.Person@1d251891

      1. public String toString() {
      2. // 用于显示该对象的内容
      3. // 一般需要重写
      4. return getClass().getName() + "@" + Integer.toHexString(hashCode());
      5. }

      8、notify()方法

    • final,说明此方法不能被重写

      1. // 唤醒对象的等待队列上的 一个线程
      2. @HotSpotIntrinsicCandidate
      3. public final native void notify();

      9、notifyAll()方法

    • final,说明此方法不能被重写

      1. // 唤醒所有 等待在这个对象的监视器上的线程
      2. @HotSpotIntrinsicCandidate
      3. public final native void notifyAll();

      10、wait()方法

    • final,说明此方法不能被重写

      1. // 使线程进入等待状态,直到被notify or notifyAll调用
      2. public final void wait() throws InterruptedException {
      3. wait(0L);
      4. }
      5. // 使线程在几秒钟内进入等待状态
      6. public final native void wait(long timeoutMillis) throws InterruptedException;
      7. // 使线程进入等待状态,直到被notify or notifyAll调用 or 等待时间达到限定值
      8. public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
      9. if (timeoutMillis < 0) {
      10. throw new IllegalArgumentException("timeoutMillis value is negative");
      11. }
      12. if (nanos < 0 || nanos > 999999) {
      13. throw new IllegalArgumentException(
      14. "nanosecond timeout value out of range");
      15. }
      16. if (nanos > 0) {
      17. timeoutMillis++;
      18. }
      19. wait(timeoutMillis);
      20. }

      11、finalize()方法

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