网站首页 > 基础教程 正文
大家好,我是小图灵视界,最近在分析Java8源码,使用的JDK是OpenJDK8,打算将分析源码的笔记都会分享出来,在头条上代码排版比较难看,想要笔记的可以关注并私信我。
Object 源码
位置:java.lang包
Object类是Java中最基本的类,是所有类的根。也就说,所有的类默认都会继承它,包括数组等,都要继承Object中的所有方法。Object类中大多数都是native方法,native就是本地方法,由关键native字修饰,这些方法不在java语言中实现,底层实现是的c/c++代码。主要的native方法如下:
//加载本地方法
private static native void registerNatives();
//获取对象的类型 Class
public final native Class<?> getClass();
//获取hash码
public native int hashCode();
//对象拷贝
protected native Object clone() throws CloneNotSupportedException;
//跟线程有关的方法
//唤醒线程
public final native void notify();
//唤醒所有的线程
public final native void notifyAll();
//线程等待
public final native void wait(long timeout) throws InterruptedException;
private static native void registerNatives()
在Object类中,有static代码块,这个静态代码块调用了registerNatives()方法:
private static native void registerNatives();
static {
registerNatives();
}
registerNatives方法的作用是加载和注册本地C、C++语言函数,将Java的本地方法与JVM底层的C、C++语言函数对应起来,是连接java语言与底层语言的桥梁,registerNatives方法在其他类中也可能存在,如Class类。Java类的本地方法对应C、C++函数的规则是Java_包名_方法名,包名以下划线分隔,Object类的registerNatives对应着C语言函数是Java_java_lang_Object_registerNatives,java_lang_Object是java全类名以下划线连接,registerNatives是Java中的方法,其中Java_java_lang_Object_registerNatives这个C语言函数如下:
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};
JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls,
methods, sizeof(methods)/sizeof(methods[0]));
}
Java_java_lang_Object_registerNatives主要是注册Object类的hashCode、wait、notify、notifyAll、clone等方法,而getClass方法却不在这里加载。在Object类中,这些本地方法不是在Java层面实现的,所有在调用这些方法的时候,是调用了底层语言的具体实现。
在methods[]数组中,有多个Java本地方法对应JVM层面的函数,如Object中hashCode方法对应JVM中的JVM_IHashCode函数,返回的类型是()I,即返回的是整数类型。
public final native Class<?> getClass()
getClass()方法的作用是返回对象运行时的Class类型,java编译会将java类编译成以.class结尾的文件,这是编译生成的二进制字节码文件,用于JVM加载,Java跨平台是因为使用与平台无关的class二进制字节码文件,可以通过Jjava中的Class类获取对象的构造器、方法、属性、注解等相关信息:
Object object=new Object();
System.out.println(object.getClass());
Class<?> objectClass=object.getClass();
//构造器
Constructor<?>[] constructors=objectClass.getConstructors();
System.out.println("Object 类的构造器:");
for (Constructor constructor:constructors){
System.out.print(constructor);
}
//方法
Method[] methods=objectClass.getMethods();
System.out.println("Object 类的方法:");
for (Method method:methods){
System.out.println(method);
}
//属性
Field[] fields=objectClass.getFields();
System.out.println("Object 类的属性:");
for (Field field:fields){
System.out.println(field);
}
getClass方法对应的底层C语言函数为Java_java_lang_Object_getClass,具体实现为:
JNIEXPORT jclass JNICALL
Java_java_lang_Object_getClass(JNIEnv *env, jobject this)
{
if (this == NULL) {
JNU_ThrowNullPointerException(env, NULL);
return 0;
} else {
return (*env)->GetObjectClass(env, this);
}
}
当传入的对象this为null,直接抛出NullPointerException异常,否则调用GetObjectClass函数,GetObjectClass函数如下:
JNI_ENTRY(jclass, jni_GetObjectClass(JNIEnv *env, jobject obj))
JNIWrapper("GetObjectClass");
#ifndef USDT2
DTRACE_PROBE2(hotspot_jni, GetObjectClass__entry, env, obj);
#else /* USDT2 */
HOTSPOT_JNI_GETOBJECTCLASS_ENTRY(env, obj);
#endif /* USDT2 */
//主要作用是根据java对象引用找到JVM中引用对象,将Java对象转换为JVM中的引用对象,然后调用klass()方法找到元数据
Klass* k = JNIHandles::resolve_non_null(obj)->klass();
jclass ret =(jclass) JNIHandles::make_local(env, k->java_mirror());
#ifndef USDT2
DTRACE_PROBE1(hotspot_jni, GetObjectClass__return, ret);
#else /* USDT2 */
HOTSPOT_JNI_GETOBJECTCLASS_RETURN(
ret);
#endif /* USDT2 */
return ret;
JNI_END
上述的核心代码为:
Klass* k = JNIHandles::resolve_non_null(obj)->klass();
resolve_non_null方法主要作用是根据java对象引用找到JVM中引用对象oop,将Java对象转换为JVM中的引用对象,然后调用klass()方法找到元数据,resolve_non_null的方法为:
inline oop JNIHandles::resolve_non_null(jobject handle) {
assert(handle != NULL, "JNI handle should not be null");
oop result = *(oop*)handle;
assert(result != NULL, "Invalid value read from jni handle");
assert(result != badJNIHandle, "Pointing to zapped jni handle area");
// Don't let that private _deleted_handle object escape into the wild.
assert(result != deleted_handle(), "Used a deleted global handle.");
return result;
};
resolve_non_null方法的核心代码是oop result = (oop)handle;这句代码的意思是先将传入的Java对象转为oop实例,((oop*)handle),然后再获取oop的指针,这个指针就是引用对象实例的地址(*(oop*)handle)。然后调用klass()获取对象实例所属的元数据Klass,Klass是指向Class类型的指针。
inline Klass* oopDesc::klass() const {
if (UseCompressedClassPointers) {
return Klass::decode_klass_not_null(_metadata._compressed_klass);
} else {
//返回元数据
return _metadata._klass;
}
}
jclass ret =(jclass) JNIHandles::make_local(env, k->java_mirror())分为两步,先调用Klass的java_mirror()方法,java_mirror方法的作用是返回Klass元数据的镜像(oop),对应着jjava/lang/Class类的实例,ava_mirror方法如下:
// java/lang/Class instance mirroring this class
oop _java_mirror;
oop java_mirror() const { return _java_mirror; }
最后通过 JNIHandles::make_local处理oop,然后返回处理过后的oop,make_local的代码如下:
jobject JNIHandles::make_local(oop obj) {
if (obj == NULL) {
//返回null
return NULL; // ignore null handles
} else {
//获取当前线程
Thread* thread = Thread::current();
assert(Universe::heap()->is_in_reserved(obj), "sanity check");
//利用当前线程活跃的handles对oop进行处理。
return thread->active_handles()->allocate_handle(obj);
}
}
在JNIHandles::make_local函数中,当传入的oop实例obj为空时,直接返回空,否则将处理过的结果。
public native int hashCode()
hashCode方法返回对象的哈希码,以整数形式返回。哈希表在java集合中如HashMap、HashSet中被广泛使用,主要作用是为了提高查询效率。hashCode方法一些规定/约定如下:
hashCode方法的使用:
Object o1=new Object();
Object o2=new Object();
Object o3=o1;
System.out.println("o1的hash的哈希码:"+ o1.hashCode());
System.out.println("o2的hash的哈希码:"+ o2.hashCode());
System.out.println("o3的hash的哈希码:"+ o3.hashCode());
//我机器上的结果,不同机器结果不同
o1的hash的哈希码:399573350
o2的hash的哈希码:463345942
o3的hash的哈希码:399573350
o1和o2是不同的对象,hashCode方法产生不同的哈希码, o1与o3是不同的对象,它们的哈希码不一样。hashCode的底层C++实现
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
//OpenJdk 6 &7的默认实现,此类方案返回一个Park-Miller伪随机数生成器生成的随机数
value = os::random() ;
} else
if (hashCode == 1) {
//此类方案将对象的内存地址,做移位运算后与一个随机数进行异或得到结果
intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
} else
if (hashCode == 2) {
//此类方案将对象的内存地址,做移位运算后与一个随机数进行异或得到结果
value = 1 ; // for sensitivity testing
} else
if (hashCode == 3) {
//此类方案返回一个自增序列的当前值
value = ++GVars.hcSequence ;
} else
if (hashCode == 4) {
//此类方案返回当前对象的内存地址
value = cast_from_oop<intptr_t>(obj) ;
} else {
//OpenJdk 8 默认hashCode的计算方法
//通过和当前线程有关的一个随机数+三个确定值
//运用Marsaglia's xorshift scheme随机数算法得到的一个随机数
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 ;
}
value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash, "invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}
get_next_hash函数根据hashCode的值,来采用不同的hashCode计算方法,当hashCode=0时,是OpenJDK6、7的默认实现方法,此类方案返回一个Park-Miller伪随机数生成器生成的随机数;当hashCode=1时,通过将对象的内存地址,做移位运算后与一个随机数进行异或得到结果;当hashCode == 3,返回一个自增序列的当前值;当hashCode == 4时,返回当前对象的内存地址;当hashCode 为其他值时,是openJDK8 的hashCode 方法的默认实现。
openJDK8 的默认hashCode的计算方法是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia's xorshift scheme随机数算法得到的一个随机数。xorshift算法是通过移位和与或计算,能够在计算机上以极快的速度生成伪随机数序列。算法如下:
unsigned long xor128(){
static unsigned long x=123456789,y=362436069,z=521288629,w=88675123;
unsigned long t;
t=(x?(x<<11));x=y;y=z;z=w;
return( w=(w?(w>>19))?(t?(t>>8)) );
}
Self->hashStateX 是随机数、Self->hashStateY 、Self->hashStateZ、Self->hashStateW 是三个确认的数,分别对应xorshift 算法的x、y、z、w。
在启动JVM时,可以通过设置-XX:hashCode参数,改变默认的hashCode的计算方式。
public boolean equals(Object obj)
public boolean equals(Object obj) {
return (this == obj);
}
equals方法判断两个对象是否相等,在这个方法中,直接用this==obj进行比较,返回this和obj比较的结果,“==“符号是比较两个对象是否是同一个对象,比较的是两个对象内存地址是否相等。子类在继承Object类的时候,一般需要重写equals方法,如果不重写,那么就默认父类Object的方法,在JDK源码中,很多对象都重写equals方法,比如String类。
对于非空的对象引用,equals方法有几个性质:
子类在继承Object,重写equals方法时,必须重写hashCode方法,在hashCode约定中,如果两个对象相等,hashCode必须返回相同的哈希码。很多时候,子类在重写父类Object的equals方法时,往往会忘了重写hashCode,当重写了equals但没有重写hashCode方法时,那么该子类无法结合java集合正常运行,因为java集合如HashMap、HashSet等都是基于哈希码进行存储的。
protected native Object clone() throws CloneNotSupportedException;
clone()本地方法,创建并返回此对象的副本,该方法使用protected 修饰,Object不能直接调用clone()方法,会编译错误:
Object o=new Object();
//编译错误
Object o1= o.clone();
如果想要使用clone()方法,子类必须重写这个方法,并用public修饰。如果子类没有实现clone(),子类默认调用父类的clone方法,如下:
public class ObjectCloneTest {
public static void main(String [] args) throws CloneNotSupportedException {
ObjectCloneTest o=new ObjectCloneTest();
ObjectCloneTest cloneTest= (ObjectCloneTest) o.clone();
System.out.println(cloneTest);
}
}
但是运行时发生异常:
Exception in thread "main" java.lang.CloneNotSupportedException: com.lingheng.java.source.ObjectCloneTest
at java.lang.Object.clone(Native Method)
at com.lingheng.java.source.ObjectCloneTest.main(ObjectCloneTest.java:12)
如果子没有实现Cloneable接口,当子类调用clone()方法时,会抛出CloneNotSupportedException异常。当子类实现Cloneable接口,程序运行会成功:
//实现Cloneable接口
public class ObjectCloneTest implements Cloneable{
public static void main(String [] args) throws CloneNotSupportedException {
ObjectCloneTest o=new ObjectCloneTest();
ObjectCloneTest cloneTest= (ObjectCloneTest) o.clone();
System.out.println("打印cloneTest:"+cloneTest);
}
}
//结果
打印cloneTest:com.lingheng.java.source.ObjectCloneTest@6d6f6e28
所以,想要使用clone()方法,除了继承Object外(默认继承),还需要实现Cloneable接口。所有的数组默认是实现了Cloneable接口的,数组的clone()返回数组类型:
public void arrayClone(){
//字符串数组
String[] s=new String[]{"hello","world"};
System.out.println(s);
System.out.println(s.clone());
//整数数组
int[] num=new int[]{1,3};
System.out.println(num);
System.out.println(num.clone());
}
//结果
[Ljava.lang.String;@17d10166
[Ljava.lang.String;@1b9e1916
[I@ba8a1dc
[I@4f8e5cde
对于任何对象x,具有以下几个约定:
- x.clone() != x 为true
- x.clone().getClass() == x.getClass() 为true,但这不是必须满足的要求,也就是说可能是false。按照约定,返回的对象应该通过调用super.clone来获得。如果一个类和它所有的超类(除了Object) 遵循这个约定,那就会x.clone().getClass() == x.getClass()。
- x.clone().equals(x)通常情况下为true,这不是必须满足的要求。
对于约定1,因为拷贝是创建一个新的对象,所以拷贝的对象和原对象是不相等的。
对于约定2,这个比较好理解,一个类和它所有的超类的clone方法都调用super.clone()获取返回对象,所以这个类的Class个原对象的Claas是一样的。clone返回的类型是Object类,也就是说,在clone中可以返回只要是Object的子类就可以了,这样的话,x.clone().getClass() == x.getClass()的结果为false;
对于约定3,这不是必须满足的条件,当在clone中改变了对象的属性值,那么x.clone().equals(x)的结果就可能不是true了。
分析了Java层面的clone的约定,我们从JVM层面看看clone底层的实现,在registerNatives函数中会加载clone的JVM实现JVM_Clone,JVM_Clone的实现如下:
//JVM_Clone 的实现
JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))
JVMWrapper("JVM_Clone");
Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
const KlassHandle klass (THREAD, obj->klass());
JvmtiVMObjectAllocEventCollector oam;
#ifdef ASSERT
// Just checking that the cloneable flag is set correct
//如果是传入的是数组
if (obj->is_array()) {
//所有的数组都默认实现了Cloneable接口
guarantee(klass->is_cloneable(), "all arrays are cloneable");
} else {
//是否是实例
guarantee(obj->is_instance(), "should be instanceOop");
bool cloneable = klass->is_subtype_of(SystemDictionary::Cloneable_klass());
//判断是否是合法的cloneable
guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");
}
#endif
----------------------------------分割线1-------------------------------------------------
// Check if class of obj supports the Cloneable interface.
// All arrays are considered to be cloneable (See JLS 20.1.5)
//如果没有实现Cloneable接口,抛出CloneNotSupportedException异常
if (!klass->is_cloneable()) {
ResourceMark rm(THREAD);
THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
}
// Make shallow object copy
//浅拷贝
const int size = obj->size();
oop new_obj = NULL;
if (obj->is_array()) {//数组
//数组的长度
const int length = ((arrayOop)obj())->length();
//申请内存,创建新的数组
new_obj = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL);
} else {
//申请内存,创建新的对象
new_obj = CollectedHeap::obj_allocate(klass, size, CHECK_NULL);
}
----------------------------------分割线2-------------------------------------------------
// 4839641 (4840070): We must do an oop-atomic copy, because if another thread
// is modifying a reference field in the clonee, a non-oop-atomic copy might
// be suspended in the middle of copying the pointer and end up with parts
// of two different pointers in the field. Subsequent dereferences will crash.
// 4846409: an oop-copy of objects with long or double fields or arrays of same
// won't copy the longs/doubles atomically in 32-bit vm's, so we copy jlongs instead
// of oops. We know objects are aligned on a minimum of an jlong boundary.
// The same is true of StubRoutines::object_copy and the various oop_copy
// variants, and of the code generated by the inline_native_clone intrinsic.
assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned");
//原子性拷贝,可能另外一个线程会修改clone引用的字段
Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj,
(size_t)align_object_size(size) / HeapWordsPerLong);
// Clear the header
new_obj->init_mark();
// Store check (mark entire object and let gc sort it out)
BarrierSet* bs = Universe::heap()->barrier_set();
assert(bs->has_write_region_opt(), "Barrier set does not have write_region");
bs->write_region(MemRegion((HeapWord*)new_obj, size));
// Caution: this involves a java upcall, so the clone should be
// "gc-robust" by this stage.
//当java子类实现了finalize方法,已经调用finalize,注册finalizer方法
if (klass->has_finalizer()) {
assert(obj->is_instance(), "should be instanceOop");
new_obj = InstanceKlass::register_finalizer(instanceOop(new_obj), CHECK_NULL);
}
//创建拷贝的对象返回
return JNIHandles::make_local(env, oop(new_obj));
JVM_END
JVM_Clone的实现过程比较长,我将分割成三部分,分割线1上部分主要是检查拷贝的一些标志是否正确,判断传入的是数组还是对象,在上面我讲过所有的数组都默认实现了Cloneable接口,在这里就可以证明了。然后还判断了传入的对象是否继承Cloneable接口,没有继承的话,就是非法的,这是第一段逻辑。
分割线中间部分的逻辑,主要是申请内存,供拷贝的时候使用。首先先判断是否实现Cloneable接口,如果没有抛出CloneNotSupportedException异常,然后根据传入的是数组还是对象,调用不同的方法进行申请不同大小的内存。代码的有段注释是”Make shallow object copy “,就是说这里的拷贝是浅拷贝。
最后的逻辑是,做一些检查,最后返回新创建的拷贝对象。因为对象的属性可能被另外一个线程改变,所以进行的是原子性的拷贝,最后创建拷贝的对象进行返回。这些源码的注释很详细,有兴趣的可以具体看看其他的一些逻辑。
public String toString()
oString()返回对象的字符串表示形式,最好所有Object的子类都重写这个方法,Object类的toString方法返回的是"类名@哈希码的十六进制,代码实现如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
当我们在代码输入使用System.out.println输入对象的时候,会调用这个对象的toString方法,返回的是toString的字符串。一般在重写的toString的过程,返回的字符串要易读的。
Object类的源码先分析到这里,因为分析的笔记会比较长,我将用两篇文章来发表。想要笔记的可以关注并私信我。
猜你喜欢
- 2024-11-13 Java:Java多线程接口和类 java多线程三种实现方法
- 2024-11-13 jdk源码学习(一):万物皆对象,解密java中所有对象的父类Object
- 2024-11-13 Java类和对象的概念 java类和对象的概念区别
- 2024-11-13 Java开发中常用的框架有哪些? java开发主流框架
- 2024-11-13 Java引用数据类型 Java引用数据类型是什么意思
- 2024-11-13 c#入门教程(二十七)object类型 c# object reference not set
- 2024-11-13 70分享(4):关于Java中的类 java常用的类和方法
- 2024-11-13 从JVM层面带你分析Java的Object类源码第二部分
- 2024-11-13 JAVA你可能不知道的事——继承&抽象类
- 2024-11-13 Java,Objects,常用操作,判空、equals、compare、hash等的使用
- 最近发表
- 标签列表
-
- jsp (69)
- gitpush (78)
- gitreset (66)
- python字典 (67)
- dockercp (63)
- gitclone命令 (63)
- dockersave (62)
- linux命令大全 (65)
- pythonif (86)
- location.href (69)
- dockerexec (65)
- tail-f (79)
- queryselectorall (63)
- location.search (79)
- bootstrap教程 (74)
- deletesql (62)
- linuxgzip (68)
- 字符串连接 (73)
- html标签 (69)
- c++初始化列表 (64)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)