虚拟机加载机制
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块
构造函数
输出:
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
易错
上面代码输出:null
new Sub();在创造派生类的过程中首先创建基类对象,然后才能创建派生类。创建基类即默认调用Test2()方法,在方法中调用callName()方法,由于派生类中存在此方法,则被调用的callName()方法是派生类中的方法,此时派生类还未构造,所以变量baseName的值为null。如果把Sub类中的callName方法删掉,则输出base。
JVM会保证一个类的初始化被加锁
如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,<clinit>()方法指的是全部的static成员及static块,其它线程都需要阻塞等待,所以,如果一个类的初始化耗时比较长的话,会影响到别的线程的执行。
.......
输出:
main finish
1.....
1.....
1.....
......
有多个线程导致类的初始化,但只有一个线程真正使得类被初始化,其它线程一直等待直到类被初始化完成。
输出:
main finish
static...
可见另一个线程也被阻塞。
类何时被卸载
这里说的是类的卸载,而不是类对象或实例的内存回收,java中采用的是双亲委派机制实现类的加载,同一个加载器对一个类只会加载一次,加载之后可以创建多个实例,如果希望对一个类加载多次,则需要实现自定义的加载器,然后破坏双亲委派机制。类的实例的回收也就是对象的回收,根据根搜索如果没找到一条与之连通路径,则便可标记为垃圾,便可以等待被回收,但类本身什么时候才可以被回收呢?首先判断一个类是否是“无用的类”要同时满足下面3个条件:
1、该类的所有实例都已经被回收;
2、加载该类的ClassLoader已经被回收,这里应该是说ClassLoader的实例,如果是ClassLoader被卸载,则加载该ClassLoader的ClassLoader也要被卸载,依次类推,直至jvm的系统类加载器,这样便无法回收类;
3、该类对应的java.lang.Class对象没有在任何地方被引用,也就是说无法在任何地方通过反射来访问该类;
满足上面3个条件仅仅说明虚拟机“可以”对该类进行回收,并不说明一定会被回收,具体什么时候回收与虚拟机的实现有关,通常为了让加载的类可以被卸载,需要自定义ClassLoader,所以很多使用反射、动态代理、CGLib等bytecode的框架以及OSGI都频繁使用自定义类加载器。
加载->验证->准备->解析->初始化->使用->卸载
其中,加载、验证、准备、初始化和卸载这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都频繁使用自定义类加载器。
相关文章推荐
- STM32F4xx -- Cortex M4
- EditText光标不显示
- win10如何安装python?win10安装python图文教程
- (一)初识HTTP消息头
- dfs寻路算法---迷宫问题实现
- Codeforces Round #245 (Div. 1)D(最近点对)
- poj 2828 Buy Tickets(动态队列·线段树单点更新)
- 综合简单聊天系统
- 方法分派
- [leedcode 224] Basic Calculator
- Android自定义控件—-RadioGroup实现APP首页底部Tab的切换
- 线段树 csu1542 Flipping Parentheses
- bs4 的一个报错
- 类加载器
- HEVC码率控制算法研究与HM相应代码分析(二)——新的码率控制模型
- 设计模式04: Factory Methord 工厂方法模式(创建型模式)
- day07-tomcat
- 9.7数学与概率(七)——检查n能否被素数整除
- [原创]SSH中HibernateTemplate与HibernateDaoSupport关系
- JAVA内存区域