Java Class文件
1、Class文件中的内容结构列表
--magic(魔数) OXCAFEBABE 用于区分JAVA Class文件和非JAVA Class文件
--minor version/major version 用于检测Class文件版本号是否属于JVM可以处理的范围
--constant pool count/constant pool 常量池
--access flags 访问标志
--this class 当前类的一个对常量池的索引
--super class 超类
--interfaces count/interfaces 所实现接口
--fields count/fields 字段信息
--methods count/methods 方法信息
--attributes count/attributes 属性信息
2、Java数组最多有255维
类型的生命周期
3、动态连接阶段:验证(数据是否合法),准备(为类分配内存),解析(符号引用转为直接引用,可选操作)
任何类的初始化都要求他所有它的祖先类(而非祖先接口)预先初始化,而一个接口的初始化并不需要它所有的祖先接口预先初始化。只有在某个接口所声明的非常量字段被使用时,该接口才会被初始化。
4、Java编译器把类变量初始化语句和静态初始化语句的代码都放到class文件的<clinit>()方法中,顺序就按照它们在类或者接口中声明的顺序。
并非所有的类都需要在它们的class文件中拥有一个<clinit>()方法。如果类没有声明类变量也没有静态初始化语句,那么它就不会有<clinit>()方法。如果类声明了类变量,但是没有明确使用类变量初始化语句或者静态初始化语句初始化它们,那么类不会有<clinit>()方法。如果类仅包含静态final变量的类变量初始化语句,而且这些类变量初始化语句采用编译时常量表达式,类也不会有<clinit>()方法。只有那些的确需要执行Java代码来赋予类变量正确初始值的类才会有类初始化方法。
例如:
class Example{
static final int angle = 35;
static final int length = angle * 2;
}
Example类在被装载的时候,angle和length并没有作为类变量保存在方法区中。因此不需要<clinit>()方法来初始化他们,它们并非类变量,而是常量,被Java编译器特殊处理了。Java虚拟机在使用它们的任何类的常量池或者字节码流中直接存放的是它们表示的常量的int值,而不是一个指向Example类的angle字段的符号引用。
5、所有在接口中声明的隐式公开(public)、静态(static)和最终(final)字段都必须在字段初始化语句中初始化。
6、主动使用和被动使用--Java虚拟机首次使用类型时初始化它们
--6中活动被认为主动使用:创建类的新实例 调用类中声明的静态方法 操作类或者接口中声明的非常量静态字段 调用Java API中特定的反射方法 初始化一个类的子类 指定一个类作为Java虚拟机启动时的初始化类
使用一个非常量的静态字段只有当类或者接口的确声明了这个字段时才是主动使用。比如类中声明的字段可能会被子类引用;接口中声明的字段可能会被子接口或者实现了这个接口的类引用。对于子类、子接口和实现了接口的类来说,这就是被动使用——使用它们并不会触发它们的初始化。只有当字段的确是被类或者接口声明的时候才是主动使用。
例如:
class NewParent{
static int hoursOfSleep = (int)(Math.random()*3.0);
static{
System.out.println("NewParent was initiaized");
}
}
NewBornBady extends NewParent{
static int hoursOfCrying = 6 + (int)(Math.random()*2.0);
static{
System.out.println("NewBornBady was initialized");
}
}
class Example{
public static void main(String[] args){
int hours = NewBornBady.hoursOfSleep;
System.out.println(hours);
}
static{
System.out.println("Example was initialized");
}
}
执行Example中的main只会导致Example和NewParent被初始化,NewBornBady没有被初始化,也不需要被装载。
输出:
Example was initialized
NewParent was initialized
2
7、对象的生命周期
--实例化一个类的四种途径:明确的使用new操作符 调用Class或者java.lang.reflect.Constructor对象的newInstance()方法 调用任何现有对象的clone()方法 通过java.io.ObjectInputStream类的getObject()方法反序列化
--如果构造方法没有明确地从this()或者super()调用开始,对应的<init>()方法默认会调用超类的无参数<init>()方法
--<init>()方法不允许捕捉由它们所调用的<init>()方法抛出的任何异常。比如如果子类的<init>()方法调用一个被意外中止的超类的<init>()方法,那么子类的<init>()方法也必须同样被意外中止。
--finalize方法:如果类声明了finalize方法,垃圾收集器会在释放这个实例所占据的内存空间之前执行这个方法一次。终结方法可以被程序直接调用,但不会影响垃圾收集器的自动调用过程。垃圾收集器(最多)只会调用一个对象的终结方法一次——在对象变成不再被引用的之后的某个时候,在占据的对象被重用之前。如果终结方法代码执行后,对象重新被引用了(复活了),随后再次变得不被引用,垃圾收集器不会第二次调用终结方法。
垃圾收集器自动调用终结方法抛出的任何异常都将被忽略。
--对象不再被引用时会被垃圾收集器回收以释放堆空间;类不再被引用时则会被卸载类型,这样类型所占据的方法区内存空间就会被释放。使用启动类装载器装载的类型永远是可触及的,不会被卸载。只有使用用户自定义的类装载器装载的类型才会变成不可触及的,从而被虚拟机回收。如果某个类型的Class实例被发现无法通过正常的垃圾收集堆触及,那么这个类型就是不可触及的。
判断动态装载的类型的Class实例在正常的垃圾收集过程中是否可以触及有两种方式:第一,程序保持对Class实例的明确引用;其次,如果堆中还存在一个可触及的对象,在方法区中它的类型数据指向一个Class实例,那么这个Class实例就是可触及的。