您的位置:首页 > 其它

虚拟机加载机制

2015-08-08 13:18 393 查看
java中的类并不是在运行或编译时就被初始化的,而是在运行的过程中需要的时候才被初始化,一个类的生命周期包括下面7个阶段:
加载->验证->准备->解析->初始化->使用->卸载
其中,加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,“解析”可能在初始化之前或之后进行,“使用”应该也能在初始化之前或之后进行。

类何时被初始化

一个类当且仅当在下面4种情况之一发生时才会被初始化:
1、遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,最常见的java代码场景是:使用new实例化对象时、读取或设置一个类的静态字段(被final修饰、在编译期已经放入常量池的静态字段除外,也就是说,读取或设置该字段不会导致类的初始化,所以使用可能才初始化之前)时,以及调用一个类的静态方法时。
2、虚拟机启动时,用户需要指定一个包含main函数的主类,这个主类会先被初始化
3、初始化一个类时,如果发现其父类还没初始化,则先初始化其父类
4、使用java.lang.reflect包的方法对类进行反射调用时
例子:
1、static块或static变量(除了final修饰以外)不会在程序启动就执行的,它们要等到类初始化时才被执行:

class A{
static{
System.out.println("I
am A!");
}
}
publicclass test1
{
publicstaticvoid main(String[]args){}
}

没有任何输出,A类没有被初始化
2、

class SuperClass{
static{
System.out.println("SuperClass
init!");
}
}
class SubClass extends SuperClass{
static{
System.out.println("SubClass
init!");
}
}
publicclass test1
{
publicstaticvoid main(String[]args){
SubClass subClass = new SubClass();
}
}

输出:
SuperClass init!
SubClass init!
两个类都被初始化了
3、

class SuperClass{
static{
System.out.println("SuperClass
init!");
}
}
class SubClass extends SuperClass{
static{
System.out.println("SubClass
init!");
}
}
publicclass test1
{
publicstaticvoid main(String[]args){
System.out.println(SubClass.class);
}
}

输出:class SubClass
两个类都没有被初始化
4、

class SuperClass{
static{
System.out.println("SuperClass
init!");
}
public static int value =
10;//该变量会放入方法区
}
class SubClass extends SuperClass{
static{
System.out.println("SubClass
init!");
}
}
publicclass test1
{
public static void main(String[]args){
System.out.println(SubClass.value);
}
}

输出:
SuperClass init!
10
SuperClass被初始化,SubClass没有被初始化
5、

class SuperClass{
static{
System.out.println("SuperClass
init!");
}
publicstaticfinalintfinalValue =
20;//该变量会放入方法区中的常量池里
}
class SubClass extends SuperClass{
static{
System.out.println("SubClass
init!");
}
}
publicclass test1
{
publicstaticvoid main(String[]args){
System.out.println(SubClass.finalValue);
}
}

输出:20
两个类都没有被初始化,因为finalValue这个变量在程序启动时就已经放到常量池中,以后直接从常量池中取出便可
6、

class SuperClass{
static{
System.out.println("SuperClass
init!");
}
}
class SubClass extends SuperClass{
static{
System.out.println("SubClass
init!");
}
}
publicclass test1
{
publicstaticvoid main(String[]args){
SuperClass[]sup = new SuperClass[5];
SubClass[]sub = new SubClass[5];
}
}

没有任何输出,直到执行sup[i] = new SuperClass()时SuperClass才被初始化,静态代码块只执行一次,也就是说,后面再加上下面语句:
sup[0] = new SuperClass();
sub[0] = new SubClass();
打印结果为:
SuperClass init!
SubClass init!

注意:
Java中,创建元素类型为类类型的数组不会导致类的构造函数被调用,也不会导致类的初始化,但C++会导致构造函数被调用:

#include<iostream>
usingnamespace std;
classA
{
public:
A()
{
cout
<< "A构造函数被调用" <<
endl;
}
};
void main()
{
A a[3];
}

输出:
A构造函数被调用

A构造函数被调用

A构造函数被调用

内部类不会因外部类初始化而被初始化

publicclass test1
{
static{
System.out.println("The
main class is inited!");
}
staticclass innerClass{
static{
System.out.println("The
innerClass is inited!");
}
}
publicstaticvoid main(String[]args){
}
}

输出:The main class is inited!
可见innerClass不会被初始化

publicclass test1
{
staticclass innerClass{
staticintA =
0;
static{
System.out.println("The
innerClass is inited!");
}
}
publicstaticvoid main(String[]args){
System.out.println(innerClass.A);
}
}

输出:
The innerClass is inited!
0

publicclass test1
{
staticclass Parent{
publicstaticintA =
0;
static{
System.out.println("Parent
is inited!");
}
}
staticclass Child extends Parent{
static{
System.out.println("Child
is inited!");
}
}
publicstaticvoid main(String[]args){
System.out.println(Child.A);
}
}

输出:
Parent is inited!
0
可见内部类的初始化也要满足前面提到的初始化4个条件

publicclass test1
{
staticclass Parent{
publicstaticintA =
0;
static{
A =
2;
}
}
staticclass Child extends Parent{
publicstaticintB = A;
}
publicstaticvoid main(String[]args){
System.out.println(Child.B);
}
}

输出:2

初始化执行顺序
类初始化是类加载过程的最后一步,类初始化时会执行静态变量初始化、静态代码块。
注意:构造函数不一定被调用!!因为构造函数需要显式调用的。

总的执行顺序:
总是先执行静态代码部分再执行构造函数(如果会执行的话),最后才是普通代码块:
例子:

publicclass Test{
public Test(){
System.out.println("构造函数");
}
static{
System.out.println("static块");
}
publicstaticvoid main(String
[]args){
Test
test = new Test();
}
}

输出:
static块
构造函数

public
class
Person {


static
{


System.out.println(
"person static"
);


}


public
Person(){


System.out.println(
"person structor"
);


}


}


public
class
Test
extends
Person{


static
{


System.out.println(
"test static"
);


}


public
Test(){


System.out.println(
"test constructor"
);


}


public
static
void
main(String[] args) {}


}

输出:
person static
test static
上面例子如果在main函数中加上下面语句:
new Test();
则输出:
person static
test static
person structor
test constructor

静态部分的顺序:

按照代码的顺序执行,前面的不能访问后面声明的静态变量,但可以对后面声明的静态变量赋值(普通方法对后面的非静态变量既不能访问也不能赋值)

例子:

publicclass Test{
static{
n =
2;//如果是System.out.println(n);则报错,因为n还没定义
}
publicstaticintn =
1;
publicstaticvoid main(String
[]args){
System.out.println(Test.n);
}
}

输出1,如果两个静态部分位置交换,则输出2

易错

public
class
Test2 {


private
String baseName =
"base"
;


public
Test2() {


callName();


}


public
void
callName() {


System.out.println(baseName);


}


static
class
Sub
extends
Test2 {


private
String baseName =
"sub"
;


public
void
callName() {


System.out.println(baseName);


}


}


public
static
void
main(String[] args) {


new
Sub();


}


}


上面代码输出:null

new Sub();在创造派生类的过程中首先创建基类对象,然后才能创建派生类。创建基类即默认调用Test2()方法,在方法中调用callName()方法,由于派生类中存在此方法,则被调用的callName()方法是派生类中的方法,此时派生类还未构造,所以变量baseName的值为null。如果把Sub类中的callName方法删掉,则输出base。
JVM会保证一个类的初始化被加锁
如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,<clinit>()方法指的是全部的static成员及static块,其它线程都需要阻塞等待,所以,如果一个类的初始化耗时比较长的话,会影响到别的线程的执行。

public
class
Person {


static
{


System.out.println(Thread.currentThread().getId()+
"....."
);


}


static
{


System.out.println(Thread.currentThread().getId()+
"....."
);


}


static
{


System.out.println(Thread.currentThread().getId()+
"....."
);


}


.......

}


public
class
Test{


public
static
void
main(String[] args) {


Runnable runnable =
new
Runnable() {


@Override


public
void
run() {


new
Person();


}


};


Thread thread1 =
new
Thread(runnable);


Thread thread2 =
new
Thread(runnable);


thread1.start();


thread2.start();


System.out.println(
"main finish"
);


}


}


输出:
main finish
1.....
1.....
1.....
......
有多个线程导致类的初始化,但只有一个线程真正使得类被初始化,其它线程一直等待直到类被初始化完成。

public
class
Person {


static
{


System.out.println(
"static..."
);


if
(
true
){


while
(
true
){}


}


}


}


public
class
Test{


public
static
void
main(String[] args) {


Runnable runnable =
new
Runnable() {


@Override


public
void
run() {


new
Person();


System.out.println(
"thread finish"
);


}


};


Thread thread1 =
new
Thread(runnable);


Thread thread2 =
new
Thread(runnable);


thread1.start();


thread2.start();


System.out.println(
"main finish"
);


}


}

输出:
main finish
static...
可见另一个线程也被阻塞。

类何时被卸载

这里说的是类的卸载,而不是类对象或实例的内存回收,java中采用的是双亲委派机制实现类的加载,同一个加载器对一个类只会加载一次,加载之后可以创建多个实例,如果希望对一个类加载多次,则需要实现自定义的加载器,然后破坏双亲委派机制。类的实例的回收也就是对象的回收,根据根搜索如果没找到一条与之连通路径,则便可标记为垃圾,便可以等待被回收,但类本身什么时候才可以被回收呢?首先判断一个类是否是“无用的类”要同时满足下面3个条件:
1、该类的所有实例都已经被回收;
2、加载该类的ClassLoader已经被回收,这里应该是说ClassLoader的实例,如果是ClassLoader被卸载,则加载该ClassLoader的ClassLoader也要被卸载,依次类推,直至jvm的系统类加载器,这样便无法回收类;
3、该类对应的java.lang.Class对象没有在任何地方被引用,也就是说无法在任何地方通过反射来访问该类;
满足上面3个条件仅仅说明虚拟机“可以”对该类进行回收,并不说明一定会被回收,具体什么时候回收与虚拟机的实现有关,通常为了让加载的类可以被卸载,需要自定义ClassLoader,所以很多使用反射、动态代理、CGLib等bytecode的框架以及OSGI都频繁使用自定义类加载器。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: