java虚拟机启动顺序:
java类加载顺序:
public class SSClass
{ static
{
System.out.println( "SSClass" );
}
} public class SuperClass extends SSClass
{ static
{
System.out.println( "SuperClass init!" );
}
public static int value = 123 ;
public SuperClass()
{
System.out.println( "init SuperClass" );
}
} public class SubClass extends SuperClass
{ static
{
System.out.println( "SubClass init" );
}
static int a;
public SubClass()
{
System.out.println( "init SubClass" );
}
} public class NotInitialization
{ public static void main(String[] args)
{
System.out.println(SubClass.value);
}
} |
运行结果:
1
2
3
|
SSClass SuperClass init! 123 |
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段
一加载:
- 通过一个类的全限定名来获取定义此类的二进制字节流(可以从一个Class文件、网络、动态生成、数据库等中获取);
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
二验证:
三准备:
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:
1
|
public static int value= 123 ;
|
那变量value在准备阶段过后的初始值为0而不是123.因为这时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。
至于“特殊情况”是指:public static final int value=123,即当类字段的字段属性是ConstantValue时,会在准备阶段初始化为指定的值,所以标注为final之后,value的值在准备阶段初始化为123而非0.
四解析:
五初始化:
真正开始执行类中定义的java程序代码(准备阶段变量已经赋过一次系统要求的初始值,此阶段是根据程序猿通过程序制定的主观计划去初始化类变量和其他资源)
初始化阶段是执行类构造器<clinit>()方法的过程.编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。如下:
1
2
3
4
5
6
7
8
9
|
public class Test
{ static
{
i= 0 ;
System.out.println(i); //这句编译器会报错:Cannot reference a field before it is defined(非法向前应用)
}
static int i= 1 ;
} |
<clinit>()方法与实例构造器<init>()方法不同,它不需要显示地调用父类构造器,虚拟机会保证在子类<init>()方法执行之前,父类的<clinit>()方法方法已经执行完毕,回到本文开篇的举例代码中,结果会打印输出:SSClass就是这个道理。
由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
<clinit>()方法对于类或者接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生产<clinit>()方法。
接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>()方法。但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有好事很长的操作,就可能造成多个线程阻塞,在实际应用中这种阻塞往往是隐藏的。
package jvm.classload;
public class DealLoopTest
{ static class DeadLoopClass
{
static
{
if ( true )
{
System.out.println(Thread.currentThread()+ "init DeadLoopClass" );
while ( true )
{
}
}
}
}
public static void main(String[] args)
{
Runnable script = new Runnable(){
public void run()
{
System.out.println(Thread.currentThread()+ " start" );
DeadLoopClass dlc = new DeadLoopClass();
System.out.println(Thread.currentThread()+ " run over" );
}
};
Thread thread1 = new Thread(script);
Thread thread2 = new Thread(script);
thread1.start();
thread2.start();
}
} |
运行结果:(即一条线程在死循环以模拟长时间操作,另一条线程在阻塞等待)
1
2
3
|
Thread[Thread- 0 , 5 ,main] start
Thread[Thread- 1 , 5 ,main] start
Thread[Thread- 0 , 5 ,main]init DeadLoopClass
|
需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>()方法的那条线程退出<clinit>()方法后,其他线程唤醒之后不会再次进入<clinit>()方法。同一个类加载器下,一个类型只会初始化一次。
将上面代码中的静态块替换如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
static {
System.out.println(Thread.currentThread() + "init DeadLoopClass" );
try
{
TimeUnit.SECONDS.sleep( 10 );
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
|
运行结果:
1
2
3
4
5
|
Thread[Thread- 0 , 5 ,main] start
Thread[Thread- 1 , 5 ,main] start
Thread[Thread- 1 , 5 ,main]init DeadLoopClass (之后sleep 10s)
Thread[Thread- 1 , 5 ,main] run over
Thread[Thread- 0 , 5 ,main] run over
|
虚拟机规范严格规定了有且只有5中情况(jdk1.7)必须对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):
- 1.遇到new,getstatic,putstatic,invokestatic这失调字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
- 2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 3.当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
- 5.当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。
开篇已经举了一个范例:通过子类引用赋了的静态字段,不会导致子类初始化。
这里再举两个例子。
1. 通过数组定义来引用类,不会触发此类的初始化:(SuperClass类已在本文开篇定义)
1
2
3
4
5
6
7
|
public class NotInitialization
{ public static void main(String[] args)
{
SuperClass[] sca = new SuperClass[ 10 ];
}
} |
运行结果:(无)
2. 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class ConstClass
{ static
{
System.out.println( "ConstClass init!" );
}
public static final String HELLOWORLD = "hello world" ;
} public class NotInitialization
{ public static void main(String[] args)
{
System.out.println(ConstClass.HELLOWORLD);
}
} |
运行结果:hello world
特殊顺序
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
;
}
相关推荐
- **类加载器**:理解不同类加载器(启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器)的作用及使用场景。 #### J2EE 1. **Web应用框架** - **JSP**:掌握JSP内置对象(如`request`、`response`...
- **Jasper**:Tomcat的JSP编译器,将JSP页面转换为Java类,然后由Java虚拟机执行。 - **Connector**:负责与网络通信,处理客户端的HTTP请求,并将响应返回给客户端。Tomcat 4.1.40中主要使用的是 Coyote 连接器...
这类应用程序是指可以通过Web浏览器访问的应用程序,无需下载或安装客户端软件。 **Bug 缺陷错误** 软件中的错误或缺陷称为bug,它们可能导致程序崩溃或表现异常。 **Build 编连(专指编译和连接)** 编连是指将源...
它负责加载Java类、执行字节码、管理内存等。 #### Java程序执行过程(Compile/Run) - **Compile**: 编译。将源代码转换成机器可识别的形式(在Java中是字节码)的过程。 - **Run**: 运行。执行编译后的代码(Java...
以下程序使用内部类实现线程,对j增减的时候没有考虑顺序问题。 107 84.4. 现在输入n个数字,以逗号”,”分开;然后可选择升或者降序排序;按提交键就在另一页面显示按什么排序,结果为,提供reset 108 84.5. 金额...
17. **JVM结构与类加载原理**:包括堆、栈、方法区、本地方法栈等,类加载涉及加载、验证、准备、解析和初始化阶段。 18. **并发下单与超卖防护**:使用分布式锁、乐观锁、版本号机制等防止超卖。 19. **分布式防...
11、有数组a[n],用java代码将数组元素顺序颠倒 80 12.金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 81 三. html&JavaScript&ajax部分 82 1. 判断第二个日期比第一...
Android 是一个专门针对移动设备的软件集,它包括一个操作系统,中间件和一些重要的应用程序。 Beta 版 的 Android SDK 提供了在 Android 平台上使用 JaVa 语言进行 Android 应用开发必须的工具和 API 接口。 特性 ...
11、有数组a[n],用java代码将数组元素顺序颠倒 80 12.金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 81 三. html&JavaScript;&ajax;部分 82 1. 判断第二个日期...
11、有数组a[n],用java代码将数组元素顺序颠倒 80 12.金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 81 三. html&JavaScript;&ajax;部分 82 1. 判断第二个日期比第...
11、有数组a[n],用java代码将数组元素顺序颠倒 80 12.金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 81 三. html&JavaScript&ajax部分 82 1. 判断第二个日期比第一...
11、有数组a[n],用java代码将数组元素顺序颠倒 80 12.金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 81 三. html&JavaScript&ajax部分 82 1. 判断第二个日期比第一...
11、有数组a[n],用java代码将数组元素顺序颠倒 80 12.金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 81 三. html&JavaScript;&ajax;部分 82 1. 判断第二个日期比第...
11、有数组a[n],用java代码将数组元素顺序颠倒 87 12.金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 88 三. html&JavaScript;&ajax;部分 89 1. 判断第二个日期比第...
真正重要的只是方法要与委托的类型兼容, 这使委托非常适合“匿名”调用。可选的形参表指定委托的参数,而返回类型则指示委托的返回类型。如果下面两个条件都为真,则方法和委托类型是兼容的:(兼容的概念就是可以用...