在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
加载 class 文件有以下几种方式:
从 ZIP 压缩包中读取,这很常见,成为日后 jar、war格式的底子;
通过网络获取,范例场景:Web Applet;
运行时盘算生成,利用最多的是:动态署理技术;
由其他文件生成,范例场景是 JSP 应用;
从加密文件中获取,范例的防 Class 文件被反编译的掩护措施。
验证
验证是毗连阶段的第一步,这一阶段的目的是确保 class 文件里的字节省信息符合当前捏造机的要求,不会危害捏造机的安全。
从代码量和泯灭的执行性能的角度上讲,验证阶段的工作量在捏造机的类加载过程中占了相称大的比重。
假如输入的字节省如不符合 Class 文件格式的束缚,将抛出一个 java.lang.VerifyError 非常或其子类非常。
从团体上看,验证阶段大抵会完成如下四个阶段的查验动作:文件格式验证、元数据验证、字节码验证和符号引用验证。
文件格式验证:告急验证字节省是否符合 Class 文件格式的规范,并且能被当前版本的捏造机处理处罚。比如:
假设一个类变量的界说如下:
public static int value = 123;在准备阶段的值会被初始化为 0,背面在类初始化阶段才会执行赋值为 123;但是下面假如利用 final 修饰静态常量,某些 JVM 的行为就不一样了。
假设上面类变量 value 的界说修改为:
public static final int value = 123;编译时 Javac 将会为 value 生成 ConstantValue 属性,在准备阶段捏造机就会根据 ConstantValue 的设置将 value 赋值为 123。
下面我们看一段代码,这是一道口试题,各人可以思索一下,下面的代码,会输出什么?
public class A { static int a = 0 ; static { a = 1; b = 1; } static int b = 0; public static void main(String[] args) { System.out.println(a); System.out.println(b); } }运行结果
10a 和 b 唯一的区别就是它们的 static 代码块的位置。
这就引出一个规则:static 语句块,只能访问到界说在 static 语句块之前的变量。
以是下面的代码是无法通过编译的。
static { b = b + 1;}static int b = 0;规则二:Java 捏造机遇包管在子类的 <clinit>() 方法执行前,父类的 <clinit>() 方法已经执行完毕。
因此在 Java 捏造机中第一个被执行的 <clinit>()方法的范例肯定是 java.lang.Object。正因云云,下面的代码字段 B 的值将会是 2 而不是 1。
public class Parent { public static int A = 1; static { A = 2; } static class Sub extends Parent{ public static int B = A; } public static void main(String[] args) { System.out.println(Sub.B); }}clinit 和 init 方法
<clinit> 是类(Class)初始化执行的方法,<init> 是对象初始化执行的方法(构造函数)。
看下面一段代码,告急是为了让各人弄明白类的初始化和对象的初始化之间的差异。
public class A { static { System.out.println("1"); } public A(){ System.out.println("2"); } } public class B extends A { static{ System.out.println("a"); } public B(){ System.out.println("b"); } public static void main(String[] args){ A ab = new B(); ab = new B(); } }打印结果
1a2b2b此中 static 字段和 static 代码块,是属于类的,在类的加载的初始化阶段就已经被执行。类信息会被存放在方法区,在同一个类加载器下,这些信息有一份就够了,以是上面的 static 代码块只会执行一次,它对应的是 <clinit> 方法。
而对象初始化就不一样了。
通常,我们在 new 一个新对象的时间,都会调用它的构造方法,就是 <init>,用来初始化对象的属性。每次新建对象的时间,都会执行。