获取源码的方式:https://blog.csdn.net/weixin_35946648/article/details/114849170
注意:后文中提到的所有路径均为源码根目录的相对路径。
1、registerNatives()
方法作用:向JVM注册Object类中的其他一系列native方法。
源码实现(jdk/src/share/native/java/lang/Object.c):
源码分析:
(1)JNIEnv 即 JNI 环境,用于在 c/c++ 代码中调用 java 代码,例如获取 java 类信息、修改 java 类变量的值……
(2)methods数组中指定了Object类中其他的native方法(getClass()方法除外,后文会讲到)所对应的c/c++实现函数名。例如:hashCode的实现方法为c++函数JVM_IHashCode,该函数位于:hotspot/src/share/vm/prims/jvm.cpp。
(3)Java_java_lang_Object_registerNatives函数内部调用了c++实现的RegisterNatives函数。
(4)RegisterNatives函数(hotspot/src/share/vm/prims/jni.cpp)实现如下图所示:
该函数内部遍历了methods数组,实现对methods数组中的每个native方法的注册。
对比分析:
JVM调用native方法的过程简述:
(1)根据方法名定位对应的 so(linux)/ddl(windows)文件(后文统称为动态链接库),并加载文件内容(方法实现)到内存中。
(2)连接 java native 方法声明和 so/ddl方 法实现。
相比依赖JVM调用native方法,使用自定义的 registerNatives 方法的好处:
(1)可以通过 static 静态代码块在类加载之后立刻注册 native 方法,而不需要在调用 native 方法时才注册,能加快运行期间的程序执行速度(以空间换时间)。
(2)能够自定义c/c++实现函数的函数名,而不需要严格遵循JNI方法命名规则,且能够在 java 程序运行期间,通过更新动态链接库中的函数名映射,能够修改 native 方法对应的函数实现。
关于JNI方法命名规则:native 方法的c/c++实现函数名称必须是“Java_包路径('.'替换成_)_类名_native方法名”。
例如:Object 类的 getClass() 方法对应的实现方法名为 Java_java_lang_Object_getClass
2、getClass()
方法声明:public final native Class<?> getClass()
方法作用:获取当前对象对应的 Class 对象。
关于 Class 对象:Class 对象是 class 文件的运行时数据结构。一个类在被应用程序使用之前,需要经历加载、连接(验证,准备,解析)、初始化阶段,Class 对象会在加载阶段被开始创建,然后在连接阶段逐渐将 class 字节码中的内容转换为 Class 对象的属性,最终在初始化阶段完成 Class 对象的初始化(对父类和自身静态属性的赋予应用程序指定的初始值,并执行static代码块)。
源码实现(jdk/src/share/native/java/lang/Object.c):
源码分析:
(1)参数 this 即调用对象实例指针,this为空(即 null.getClass())时抛出空指针异常。
(2)this 不为空时调用 c++ 实现的 GetObjectClass 函数,该函数位于:hotspot/src/share/vm/prims/jni.cpp,函数实现如下:
其中的关键代码:
(1)Klass* k = JNIHandles::resolve_non_null(obj)->klass():根据传入的 java 对象引用找到对象实例,然后根据对象实例找到该对象类型的元数据。
(2)jclass ret = (jclass) JNIHandles::make_local(env, k->java_mirror()):根据元数据找到其镜像,即 Class 对象实例。
和其他获取 Class 对象的方式作对比:
package com.cc.kit.java8.usage.base.lang.object;
import org.junit.jupiter.api.Test;
class A {
static int a = 10;
static {
System.out.println("A.static: " + a);
}
}
class B {
static int b = 100;
static {
System.out.println("B.static: " + b);
}
}
class C {
static int c = 1000;
static {
System.out.println("c.static: " + c);
}
}
public class GetClassUsage {
@Test
public void getClassUsage() throws ClassNotFoundException {
Class<A> aClass = A.class;
Class bClass = Class.forName("com.cc.kit.java8.usage.base.lang.object.B");
Class cClass = new C().getClass();
System.out.println(aClass.getName());
}
}
打印结果:
可以发现,通过“类名.class”获取 Class 对象时,不会进行类的初始化。
3、hashCode()
方法声明:public native int hashCode()
方法作用:计算当前对象的哈希值。
方法约定:
- 在java程序执行过程中,在一个对象没有被改变的前提下,无论这个对象被调用多少次,hashCode方法都会返回相同的整数值。对象的哈希码没有必要在不同的程序中保持相同的值。
- 如果2个对象使用equals方法进行比较并且相同的话,那么这2个对象的hashCode方法的值也必须相等。
- 如果根据equals方法,得到两个对象不相等,那么这2个对象的hashCode值不需要必须不相同。因为,不相等的对象的hashCode值不同的话可以提高哈希表的性能。
- 默认情况下,如果一个 java 类不重写 equals() 方法和 hashCode 方法,则该类的每个对象调用 hashCode() 方法将返回该对象的内存地址的转换结果。
4、equals(Object obj)
方法声明:public boolean equals(Object obj)
方法作用:判断两个对象是否相等。默认情况下比较两个对象的内存地址是否相等,除非重写该方法。
注意:重写 equals 方法时,应同时重写 hashCode 方法!理由请参考 hashCode 方法约定。
5、clone()
方法声明:protected native Object clone() throws CloneNotSupportedException;
方法作用:对象浅拷贝。拷贝策略:拷贝所有基本数据类型的成员变量值,拷贝所有引用数据类型变量的引用地址。即,对于引用类型的对象而言,拷贝的结果仅仅是对象引用地址,而实际的对象实例是同一个。
举例说明:
package com.cc.kit.java8.usage.base.lang.object;
import org.junit.jupiter.api.Test;
public class CloneUsage {
class A implements Cloneable {
int i;
B b;
public A clone() throws CloneNotSupportedException {
return (A) super.clone();
}
}
class B {
String s;
}
@Test
public void cloneUsage() throws CloneNotSupportedException {
A a1 = new A();
a1.i = 10;
a1.b = new B();
a1.b.s = "b1";
A a2 = a1.clone();
System.out.println(a1.i == a2.i);
System.out.println(a1.b == a2.b);
System.out.println(a2.b.s);
a1.b.s = "b2";
System.out.println(a2.b.s);
}
}
打印结果:
6、notify()
方法声明:public final native void notify()
方法作用:唤醒一个持有当前对象 monitor 锁的线程,使其能去竞争该对象的 monitor 锁。
唤醒原则:在 hotspot 虚拟机中,notify() 遵循先进先出(FIFO)的唤醒原则,即,先进入等待状态的线程先被唤醒。
注意:notify() 方法必须在 synchronized 同步代码块/方法中调用,否则会抛出 IllegalMonitorStateException 异常。
7、wait()
方法声明:public final native void wait(long timeout) throws InterruptedException
方法作用:释放当前对象 monitor 锁,使当前线程进入该 monitor 锁的等待池中,并释放 CPU 资源,直到被唤醒。
注意:wait() 方法必须在 synchronized 同步代码块/方法中调用,否则会抛出 IllegalMonitorStateException 异常。
notify() 与 wait() 联合使用示例:
/**
* 两个线程,一个线程打印1-52,另一个线程打印A-Z,打印结果为12A34B...5152Z
*/
public static void main(String[] args) {
final boolean[] flag = {false};
new Thread(() -> {
synchronized (flag) {
for (int i = 1; i <= 52; i++) {
System.out.print(i);
System.out.print(++i);
if (!flag[0]) {
flag[0] = true;
}
flag.notify();
try {
flag.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 先结束的线程必须在退出之前唤醒其他线程,否则会造成其他线程一直处于等待状态而无法结束
flag.notify();
}
}).start();
new Thread(() -> {
synchronized (flag) {
// 等待前一个线程先打印:12
while (!flag[0]) {
try {
flag.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (char i = 'A'; i <= 'Z'; i++) {
System.out.print(i);
flag.notify();
try {
flag.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 在这个例子中,下面这行语句是可以没有的,因为这个线程后结束。但是如果这个线程打印到 Z 之前的字母,则该线程会先结束,此时必须唤醒前一个线程,否则会造成前一个线程一直处于等待状态
// flag.notify();
}
}).start();
}
8、notifyAll()
方法声明:public final native void notifyAll()
方法作用:唤醒所有持有当前对象 monitor 锁的线程,使这些线程能去竞争该对象的 monitor 锁。
9、wait(long timeout)
方法声明:public final native void wait(long timeout) throws InterruptedException
方法作用:释放当前对象 monitor 锁,使当前线程进入该 monitor 锁的等待池中,并释放 CPU 资源,直到被唤醒或等待时间达到 timeout 值(单位:毫秒)。
10、wait(long timeout, int nanos)
方法声明:public final void wait(long timeout, int nanos) throws InterruptedException
方法作用:释放当前对象 monitor 锁,使当前线程进入该 monitor 锁的等待池中,并释放 CPU 资源,直到被唤醒或等待时间达到等待时间。该方法首先会根据参数值计算等待时间,然后调用 wait(long timeout) 方法。
等待时间:如下图 timeout 的计算过程所示。可以发现,当 timeout>=0 且 nanos>500000纳秒(500微秒/0.5毫秒)时,等待超时时间为 timeout+1 毫秒;当 timeout=0 且 nanos 在 (0,999999] 之间时,等待超时时间为 1 毫秒。
注意:上图所示为文章开头所声明的指定版本的 JDK 源码实现,不同版本 JDK 对该方法中 timeout 的计算有不同的实现,使用之前应仔细阅读源代码和注释。