您的位置:首页 > 编程语言 > Java开发

java的类加载机制之案例分析

2018-02-27 16:44 274 查看

一.什么是java的类加载机制

1.概念

个人理解,所谓java的类加载,就是将编译完的class文件,加载进jvm中,成为可以被引用的class对象的过程.

2.过程



加载(装载):查找和导入Class文件

验证:检查载入Class文件数据的正确性,看看是否会有将jvm干掉的危险数据

准备:为类变量分配内存并设置类变量初始值()的阶段

这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中;这里所说的初始值“通常情况”是数据类型的零值,假如:

static int value = 100;

value在准备阶段过后的初始值为0而不是100,而为value赋值的putstatic指令将在初始化阶段才会被执行

解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行

初始化: 对类的静态变量,静态代码块执行初始化操作,也就是执行类构造器()方法的过程:

()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问

文字部分到此结束,我不想再敲了.说实话,我不喜欢看长篇的文字,相比较来说,我更喜欢写代码实践和理解.所以,下面我会用一个案例,来简单说一下自己的理解.

二.案例分析

1.经典案例

案例一

package jvmload;
public class StaticTest {
public static void main(String[] args) {
staticFunction();
}

static StaticTest st = new StaticTest();//注意这点

static {
System.out.println("1");
}

{
System.out.println("2");
}

StaticTest() {
System.out.println("3");
System.out.println("a=" + a + ",b=" + b);
}

public static void staticFunction() {
System.out.println("4");
}

int a = 110;
static int b = 112;
}


这段代码.不管大家有没有看过,我就当大家都没看过.

运行结果:

2

3

a=110,b=0

1

4

如果是第一次看这个案例的小伙伴,一定会觉得很奇怪,为什么输出结果是这样的?为什么是2先打印出来,为什么a有值,而b=0?不急,先来看一段代码:

案例二

package jvmload;
public class StaticTest1 {
public static void main(String[] args) {
staticFunction();
}
static {
System.out.println("1");
}

{
System.out.println("2");
}

StaticTest1() {
System.out.println("3");
System.out.println("a=" + a + ",b=" + b);
}

public static void staticFunction() {
System.out.println("4");
}

int a = 110;
static int b = 112;
static StaticTest1 st = new StaticTest1();//注意这点
}


大家猜猜看这段代码的运行结果又是什么呢?

运行结果:

1

2

3

a=110,b=112

4

我相信大家已经看出来了,第二段代码和第一段代码的区别其实是static StaticTest1 st = new StaticTest1()的位置不同而已.

这时候大家可能又有疑问了,为什么只移动了一行代码的位置,运行结果却天差地别?

下面,请听我慢慢解释:我只解释案例一的,案例二的大家自行理解.

我这本章开头就说了,加载的过程是:装载–>连接(验证,准备,解析)–>初始化.

1.在准备阶段,会为类变量设置默认值,所以在案例一中:st=null,b=0,

2.在初始化阶段,会先执行类构造器,

类构造器是编译器收集所有静态语句块和类变量的赋值语句按语句在源码中的顺序合并生成类构造器,对象的构造方法是(),类的构造方法是().

换句话说,就是执行static修饰的代码块和为static修饰的变量赋值而已.而static修饰的代码块和类变量的执行顺序是按照它在文件中的先后顺序执行的.而static StaticTest st = new StaticTest()排在第一,所以会执行 new StaticTest(),也就是进行对象的初始化

2.1.在对象的初始化过程中,会先执行成员变量(代码块),然后再执行构造方法.成员变量的执行顺序也是谁先声明,谁先执行,所以排在第一的代码块

{

System.out.println(“2”);

}

会先执行,所以会先打印2.,接着,就是执行

int a = 110;

2.2成员变量执行完后,执行构造方法.此时,a=110,b=0;

StaticTest() {

System.out.println(“3”);

System.out.println(“a=” + a + “,b=” + b); }

所以,控制台会接着打印出3,a=110,b=0

3.new StaticTest()执行完后,继续往下执行

static {

System.out.println(“1”);

}

所以,接下来控制台会打印1.

4.,最后,到达b的位置

static int b = 112;


只有到了这个时候,b才会被赋值.所以,案例二中,将st放在static int b=123的下面,运行的结果中b=123,也就是这个原因了.

大家如果不信,看一下下面的案例:

案例三

package jvmload;
public class StaticTest {
public static void main(String[] args) {
staticFunction();
}

static StaticTest st = new StaticTest();

static {
System.out.println("1");
}

{
System.out.println("2");
}

StaticTest() {
System.out.println("3");
System.out.println("a=" + a + ",b=" + b);
}

public static void staticFunction() {
System.out.println("4");
System.out.println( "第二个b=" + b);//注意点
}

int a = 110;
static int b = 112;
}


案例三和案例一的区别:我只是在staticFunction()方法中,打印b,我们来看一下结果

2

a=110,b=0 //构造方法中的b

1

4

第二个b=112//staticFunction()方法中的b

一前一后,同样的b.,值却不同.

5.初始化结束后,main方法启动,接着调用staticFunction()方法,所以控制台最后输出的是4.

所以,案例一的运行结果才会是:

2

3

a=110,b=0

1

4

如果大家对我的解释不满意或者是不理解的话,可以自己去实践一下,用debug调试一下.

题外话:静态代码块和静态方法比较

静态代码块:有些代码必须在项目启动的时候就执行,这种代码是主动执行的(当类被载入时,静态代码块被执行,且只被执行一次,静态块常用来执行类属性的初始化)

静态方法:需要在项目启动的时候就初始化,在不创建对象的情况下,这种代码是被动执行的(静态方法在类加载的时候就已经加载 可以用类名直接调用)。

两者的区别是:

静态代码块是自动执行的,

静态方法是被调用的时候才执行的.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jvm 类加载机制