Object

前言

Object 作为所有类的祖先类,所拥有的方法很多使用 native、也就是调用本地方法实现。本文集合了一些资料,权做记录与了解。

导航

registerNative

1
2
3
4
private static native void registerNatives();
static {
registerNatives();
}
  • 通过 JNI(Java native interface)调用本地代码,将在类的初始化运行。
  • 关于 static 运行时间,参考资料如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static块真正的执行时机。如果了解JVM原理,我们知道,一个类的运行分为以下步骤:

装载
连接
初始化
其中装载阶段又三个基本动作组成:

通过类型的完全限定名,产生一个代表该类型的二进制数据流
解析这个二进制数据流为方法区内的内部数据结
构创建一个表示该类型的java.lang.Class类的实例
另外如果一个类装载器在预先装载的时遇到缺失或错误的class文件,它需要等到程序首次主动使用该类时才报告错误。



连接阶段又分为三部分:

验证,确认类型符合Java语言的语义,检查各个类之间的二进制兼容性(比如final的类不用拥有子类等),另外还需要进行符号引用的验证。
准备,Java虚拟机为类变量分配内存,设置默认初始值。
解析(可选的) ,在类型的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程。

  当一个类被主动使用时,Java虚拟就会对其初始化,如下六种情况为主动使用:

当创建某个类的新实例时(如通过new或者反射,克隆,反序列化等)
当调用某个类的静态方法时
当使用某个类或接口的静态字段时
当调用Java API中的某些反射方法时,比如类Class中的方法,或者java.lang.reflect中的类的方法时
当初始化某个子类时
当虚拟机启动某个被标明为启动类的类(即包含main方法的那个类)
Java编译器会收集所有的类变量初始化语句和类型的静态初始化器,将这些放到一个特殊的方法中:clinit。

实际上,static块的执行发生在“初始化”的阶段。初始化阶段,jvm主要完成对静态变量的初始化,静态块执行等工作。

下面我们看看执行static块的几种情况:

1、第一次new A()的过程会打印"";因为这个过程包括了初始化

2、第一次Class.forName("A")的过程会打印"";因为这个过程相当于Class.forName("A",true,this.getClass().getClassLoader());

3、第一次Class.forName("A",false,this.getClass().getClassLoader())的过程则不会打印""。因为false指明了装载类的过程中,不进行初始化。不初始化则不会执行static块。

参考资料:《深入Java虚拟机》

getClass()

1
public final native Class<?> getClass();
  • 返回运行时 class:Class<? extends |X|>
1
2
3
4
Number n = 0;
Class<? extends Number> c = n.getClass();
//输出class java.lang.Integer
System.out.println(c);
  • getClass()与 .class 语法
1
2
3
4
5
//The Java™ Tutorials
boolean b;
Class c = b.getClass(); // compile-time error
Class c = boolean.getClass() // compile-time error
Class c = boolean.class; // correct

clone()

1
protected native Object clone() throws CloneNotSupportedException;
  • 成员变量是基本数据类型、不可变对象,clone 对象修改不影响原对象
  • 成员变量是可变对象,clone 对象修改影响原对象——需要”deep copy”

notify()/wait()

1
2
3
4
5
6
7
public final native void notify();

public final void wait() throws InterruptedException {
wait(0);
}

public final native void wait(long timeout) throws InterruptedException;

在 JAVA 中,是没有类似于 PV 操作、进程互斥等相关的方法的。JAVA 的进程同步是通过 synchronized()来实现的,需要说明的是,JAVA 的 synchronized()方法类似于操作系统概念中的互斥内存块,在 JAVA 中的 Object 类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现 JAVA 中简单的同步、互斥操作。明白这个原理,就能理解为什么 synchronized(this)与 synchronized(static XXX)的区别了,synchronized 就是针对内存区块申请内存锁,this 关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而 static 成员属于类专有,其内存空间为该类所有成员共有,这就导致 synchronized()对 static 成员加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例。如果只是简单的想要实现在 JAVA 中的线程互斥,明白这些基本就已经够了。但如果需要在线程间相互唤醒的话就需要借助 Object.wait(), Object.nofity()了。
Obj.wait(),与 Obj.notify()必须要与 synchronized(Obj)一起使用,也就是 wait,与 notify 是针对已经获取了 Obj 锁进行操作,从语法角度来说就是 Obj.wait(),Obj.notify() 必须在 synchronized(Obj){…}语句块内。从功能上来说 wait 就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的 notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的 notify()就是对对象锁的唤醒操作。但有一点需要注意的是 notify()调用后,并不是马上就释放对象锁的,而是在相应的 synchronized(){}语句块执行结束,自动释放锁后,JVM 会在 wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与 Object.wait()二者都可以暂停当前线程,释放 CPU 控制权,主要的区别在于 Object.wait()在释放 CPU 同时,释放了对象锁的控制。

finalize()

1
protected void finalize() throws Throwable { }
  • JVM 判断该对象不可到达且未死亡时调用
  • 调用完成后,JVM 再次扫描,该对象仍不可到达且未死亡才可能被回收
  • 抛出异常,该异常将会无视,并且 finalize()终止