Java源码分析 | Object
本文基于 OracleJDK 11, HotSpot 虚拟机。
Object 定义
Object 类是类层次结构的根。每个类都有 Object 类作为超类。所有对象,包括数组等,都实现了这个类的方法。
静态代码块
在Object类的最开始部分,有如下四行代码:
private static native void registerNatives();
static {
registerNatives();
}
native 方法主要用于通过调用 C 或 C++ 实现的本地方法来对底层操作系统的访问。
扩展
native 关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如 C 和C++)实现的文件中。
Java 语言本身不能对操作系统底层进行访问和操作,但是可以通过 JNI(Java Native Interface)接口调用其他语言来实现对底层的访问。
**JNI **全称是 Java Native Interface,即 Java 本机接口,是 Java 和 Native 间的通信桥梁。Java 调用 Native,可以去调用非 Java 实现的库,扩充 Java 的使用场景;反之 Native 调用 Java,可以在别的语言里面调用 Java。
类的 static 静态代码块会在类初始化时调用,其目的是为该类中包含的除了registerNatives()方法以外的所有本地方法(被 native 关键字修饰的方法)进行注册。
构造函数
@HotSpotIntrinsicCandidate
public Object() {}
- 无参构造函数主要是创建一个新的 Object 对象。
@HotSpotIntrinsicCandidate注解特定于 HotSpot 虚拟机,它表明带注解的方法可能被 HotSpot 内在化,在虚拟机内存在更高效的实现来替换被注解修饰的方法以提高性能。该注解是 Java 库内部的,与应用程序没有任何关联。
方法
getClass()
@HotSpotIntrinsicCandidate
public final native Class<?> getClass();
该方法是 native 方法,作用是返回当前对象运行时的类。返回的 Class 对象是被static synchronized方法锁定的对象。
实际的结果类型是 Class<? extends |X|>,X 是对调用 getClass 的表达式的静态类型擦除。如下所示,代码中不需要强制转换:
Number n = 0;
Class<? extends Number> c = n.getClass();
Class 对象表示运行时此对象的类。
hashCode()
public native int hashCode();
该方法是 native 方法,作用是返回当前对象的哈希码值。支持此方法是为了便于对 java.util.HashMap 等提供的哈希表。
hashCode 的通用规则是:
- 每当在 Java 应用程序执行期间对同一对象多次调用它时,该方法始终返回相同的整数。如果
equals()中使用的信息没有被修改。从应用程序的一次执行到相同的应用程序的一次执行,此整数不必保持一致。 - 如果两个对象根据调用
equals()方法相等,则在每个对象上调用hashCode()方法必须产生相同的整数结果。 - 如果两个对象根据调用
equals()方法不相等,在每个对象上调用hasCode()方法不要求必须产生不同的整数结果(因为可能存在哈希碰撞)。
equals()
public boolean equals(Object obj) {
return (this == obj);
}
该方法指示其他对象是否“等于”当前对象,判断两个对象是否具有相同的引用(对象的内存地址)。
equals() 函数必须满足以下五点条件:
- 反身性:对于任何 x,
x.equals(x)应该返回 true。 - 对称性:对于任何 x 和 y,
x.equals(y)应该返回 true 当且仅当y.equals(x)返回 true 。 - 传递性:对于任何 x,y, 还有 z,如果
x.equals(y)返回 true 并且y.equals(z)返回 true,那么x.equals(z)应该返回 true。 - 一致性:对于任何 x 和 y,在对象没有被改变的情况下,多次调用
x.equals(y)应该总是返回 true 或者 false。 - 对于任何非 null 的 x,
x.equals(null)应该返回 false。
类 Object 的equals()方法在对象上实现了最有区别的等价关系,也就是说,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用的是同一对象的时候,x==y 返回 true。
默认情况下从超类 Object 继承而来的 equals() 方法与 == 是完全等价的,比较的都是对象的内存地址,但我们可以重写 equals()方法,使其按照我们的需求的方式进行比较。
注意
每当重写
equals()方法时,通常都需要重写hashCode()方法,以便维护hashCode()方法的常规约定,该方法申明相等的对象必须具有相同的 hashCode 值。如果子类不重写,将会默认使用父类对象的hashCode()方法,导致对象内容一致但对象内存地址不一致。因为
equals()方法比较消耗性能且效率低,一般有大量数据需要快速的对比的话,会先比对hashCode,hashCode相等的再使用equals进行比较。Java 中常见的一些默认类都会重写
hashCode()和equals()方法,如 String 类等。
clone()
@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
该方法是 native 方法,作用是创建并返回此对象的副本。
注意:clone 方法本身没有实现 Cloneable 接口,但在调用 clone 方法时需要实现 Cloneable 接口并重写,否则会抛出 CloneNotSupportedException 异常以表示无法克隆。
通常重写克隆需要满足以下条件:
x.clone() != x为 true (对象引用指向堆内存地址不同)x.clone().getClass() == x.getClass()为 true(相同的运行时类)x.clone().equals(x)为 true (对象属性内容相同,由于 Object 的equals()默认为this == obj,所以需要重写)
但这不是绝对的,因为浅克隆和深克隆的区别。
扩展
浅克隆
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换而言之,浅拷贝仅仅复制所拷贝的对象,而不复制它所引用的对象.
深克隆
被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
toString()
返回对象的字符串表示形式。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
此方法默认返回 类名@无符号十六进制表示形式组成对象的哈希码。该方法是为了更简洁清晰且信息丰富的表示对象内容,易于阅读,所以建议所有子类重写该方法。
notify()、notifyAll()
@HotSpotIntrinsicCandidate
public final native void notify();
@HotSpotIntrinsicCandidate
public final native void notifyAll();
这两个方法都是 native 方法,作用都是唤醒正在此对象的监视器上等待的线程,区别在于 notify() 是唤醒单线程,而 notifyAll()是唤醒所有线程。
如果有任何线程正在等待该对象,则选择其中一个被唤醒。该选择是任意的,并由实施自行决定。线程通过调用 wait 方法之一在对象的监视器上等待。
在当前线程放弃对该对象的锁定之前,被唤醒的线程将无法继续。被唤醒的线程将以通常的方式与可能正在积极竞争以在此对象上同步的任何其他线程竞争
该方法只能由作为该对象监视器所有者的线程调用,否则会抛出 IllegalMonitorStateException 异常。
扩展
每个对象都有一个”锁“,即监视器 Monitor,而且每个对象都有一个同步队列(EntrySet)和等待队列(WaitSet),同步队列和等待队列里面都存放着线程对象的引用。
notify()是对notifyAll()的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死锁。正确的场景应该是WaitSet中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果唤醒的线程无法正确处理,务必确保继续notify()下一个线程,并且自身需要重新回到WaitSet中。
wait()、wait(long timeout)、wait(long timeout, int nanos)
public final void wait() throws InterruptedException {
wait(0L);
}
public final native void wait(long timeoutMillis) throws InterruptedException;
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);
}
wait(long timeout) 方法是 native 方法,作用是让当前线程释放 CPU 占用资源并且释放对象的”锁“,直到该对象执行了 notify()/noyifyAll()方法,或者过了 timeoutMillis 的等待时间(单位:毫秒),或者被其他线程调用了该线程的 interrupt()方法打断,该线程会被唤醒。
wait()方法就是调用了 wait(0L),代表无限等待,只能通过该对象执行了 notify()/noyifyAll()方法、被打断,该线程会被唤醒。
wait(long timeoutMillis, int nanos) 方法只是添加了一个范围在0~999999纳秒的附加时间int nanos,本质上还是调用了 wait(long timeout)。
如果当前线程不是对象监视器的所有者,则会抛出 IllegalMonitorStateException 异常。
如果当前线程在等待之前或期间被任何线程中断,则会抛出 InterruptedException 异常。
线程也可以在没有被通知、中断或超时的情况下唤醒,即所谓的虚假唤醒。虽然这在实践中很少发生,但应用程序必须通过测试应该导致线程被唤醒的条件来防范它,可以将 wait 配合 while 循环使用,苏醒后进行条件检查,如果不满足则 继续 wait() 直至条件满足再往下执行。
finalize()
@Deprecated(since="9")
protected void finalize() throws Throwable { }
当垃圾收集确定不再有对该对象的引用时,由对象上的垃圾收集器调用。子类覆盖finalize()方法来处理系统资源或执行其他清理。
每个对象的 finalize() 方法只能被系统执行一次,该方法类似析构函数但不等价。
死亡逃逸,可以在对象被回收之前在 finalize 方法里面重新与其他对象(其他对象不能是即将被回收的对象)建立关联即可,但机会只有一次。
在JDK9及其之后已被废弃使用。原因是最终确定机制本质上是有问题的。最终确定会导致性能问题、死锁和挂起。终结器中的错误可能导致资源泄漏;如果不再需要,则无法取消最终确定;并且在对不同对象的 finalize 方法的调用之间没有指定顺序。此外,无法保证最终确定的时间。finalize() 方法可能仅在无限期延迟之后才在可终结对象上调用
对象持有非堆资源的类应该提供一种方法来启用这些资源的显式释放,它们可以实现 java.lang.AutoCloseable。
java.lang.ref.Cleaner 和 java.lang.ref.PhantomReference 提供了更灵活、更有效的方法来在对象变得无法访问时释放资源。
Java源码分析 | Object的更多相关文章
- Java源码分析 | CharSequence
本文基于 OracleJDK 11, HotSpot 虚拟机. CharSequence 定义 CharSequence 是 java.lang 包下的一个接口,是 char 值的可读序列, 即其本身 ...
- Java源码分析:关于 HashMap 1.8 的重大更新(转载)
http://blog.csdn.net/carson_ho/article/details/79373134 前言 HashMap 在 Java 和 Android 开发中非常常见 而HashMap ...
- Java源码分析之LinkedList
LinkedList与ArrayList正好相对,同样是List的实现类,都有增删改查等方法,但是实现方法跟后者有很大的区别. 先归纳一下LinkedList包含的API 1.构造函数: ①Linke ...
- Java源码之Object
本文出自:http://blog.csdn.net/dt235201314/article/details/78318399 一丶概述 JAVA中所有的类都继承自Object类,就从Object作为源 ...
- Java源码分析:Guava之不可变集合ImmutableMap的源码分析
一.案例场景 遇到过这样的场景,在定义一个static修饰的Map时,使用了大量的put()方法赋值,就类似这样-- public static final Map<String,String& ...
- JAVA源码分析-HashMap源码分析(二)
本文继续分析HashMap的源码.本文的重点是resize()方法和HashMap中其他的一些方法,希望各位提出宝贵的意见. 话不多说,咱们上源码. final Node<K,V>[] r ...
- JAVA源码分析-HashMap源码分析(一)
一直以来,HashMap就是Java面试过程中的常客,不管是刚毕业的,还是工作了好多年的同学,在Java面试过程中,经常会被问到HashMap相关的一些问题,而且每次面试都被问到一些自己平时没有注意的 ...
- 【转】【java源码分析】Map中的hash算法分析
全网把Map中的hash()分析的最透彻的文章,别无二家. 2018年05月09日 09:08:08 阅读数:957 你知道HashMap中hash方法的具体实现吗?你知道HashTable.Conc ...
- 【Java源码分析】LinkedList类
LinkedList<E> 源码解读 继承AbstractSequentialList<E> 实现List<E>, Deque<E>, Cloneabl ...
随机推荐
- git实战-多分支开发-2022新项目
现在开发中大多数公司中都在使用Git这个代码版本管理工具,几乎可以说是已经成为标配,刚入职不久的这家新公司也不例外. 去公司没多久,开始搭建项目,然后创建开发分支,有多少个后端人员就创建多少个开发分支 ...
- Anaconda新建虚拟环境并添加到Jupyter Notebook
可参考:https://www.jianshu.com/p/ab9ae548b253 虚拟环境是Python的隔离工作副本.这意味着每个环境都可以具有自己的依赖关系,甚至可以具有自己的Python版本 ...
- cool-admin vite-vue3 打包部署 nginx代理设置
location /api {rewrite ^/api/(.*)$ /$1 break;proxy_pass http://xxx.com;}location /socket.io {rewrite ...
- 【Spring】事务的执行原理(三)
事务的回滚 如果获取事务属性不为空,并且抛出的异常是RuntimeException或者Error类型,调用事务管理器中的rollback方法进行回滚 如果事务属性为空或者抛出的异常不是Runtime ...
- Leetcode----<Re-Space LCCI>
题解如下: /** * 动态规划解法: * dp[i] 表示 0-i的最小不能被识别的字母个数 * 求 dp[k] 如果第K个字母 不能和前面的字母[0-{k-1}]合在一起被识别 那么dp[k] = ...
- Redis基础与性能调优
Redis是一个开源的,基于内存的结构化数据存储媒介,可以作为数据库.缓存服务或消息服务使用. Redis支持多种数据结构,包括字符串.哈希表.链表.集合.有序集合.位图.Hyperloglogs等. ...
- rhel挂载本地光盘为yum源
挂载光盘 mount /dev/sr0 /mnt/cdrom mkdir /mnt/cdrom 临时挂载 mount /dev/sr0 /mnt/cdrom 永久挂载光盘 mount -a 执行挂载 ...
- 单片机 MCU 固件打包脚本软件
1 前言 开发完 MCU 软件后,通常都会生成 hex 文件或者 bin 文件,用来做固件烧录或者升级,如果用来做产品开发,就涉及到固件版本的问题,初学者通常采用固件文件重命名来区分版本. 如果需 ...
- RocketMQ 集群的搭建部署 以及rocketmq-console-ng仪表台的安装部署
在 RocketMQ 主要的组件如下. NameServerNameServer 集群,Topic 的路由注册中心,为客户端根据 Topic 提供路由服务,从而引导客户端向 Broker 发送消息.N ...
- 数学公式 Latex 练习
\[1+x+x^2+x^3+\cdots=\frac{1}{1-x}\quad x\in(-1, 1) \] 证明:设左边式子项数为 \(n\) 那么可以得到: \[\begin{split} S & ...