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

Java类加载分析

2016-07-20 14:54 357 查看
Java类加载
一.类加载生命周期:

类加载生命周期包括加载,验证,准备,解析,初始化,使用,卸载

 


 

 

二.加载:

*过程

1.获取定义此类的二进制字节流(使用类加载器)

2.把类信息存与方法区

3.在内存中(可以是Java堆或方法区)生成一个该类的java.lang.Class对象,作为方法区中该类信息的访问入口

*要点

1.非数组类由类加载器加载

2.数组类由虚拟机直接创建,如果数组类的类型是引用类型,则递归加载该类,如果是基本数据类型,则由引导类加载 器加载。

三.验证:(安全性)

1.文件格式验证:

1.验证字节流是否符合class文件格式的规范。如魔数开头,版本号等

2.这部分的验证是基于二进制字节流的验证,经过这部分验证后就加入方法区。

2.元数据验证:

1.这部分验证是根据Java语法规范进行验证。

3.字节码验证:

1.这部分同样是Java语言的验证,主要验证方法体,避免其做出危害虚拟机安全的事件。

4.符号引用的验证:

1.因为符号引用在后面会被转换为直接引用,用来调用其他类的方法等。所以符号引用的验证可以看做是对类相关 联的信息的验证。

四.准备:(类变量分配内存与初值)

为类变量在方法区分配内存并设置初始值。类变量指的是static修饰变量,对其赋的值不是程序里指定的值,而是0值。要注意的是实例变量不在这时候开辟内存,要在类的对象实例化的时候才在Java堆中分配内存。有一个特例,即如果有一个基本数据类型的static
final变量,则会在这时候赋上程序中指定的值。

 

五.解析:(将符号引用替换为直接引用)

1.类或接口的解析:

对于类或接口的解析又会触发其他类的加载。这里其他类的加载会先通过当前类的加载器去加载(涉及双亲机制)。

2.字段解析(字段就是类里面定义的成员变量):

要查找类ClassA里的变量A,则先会在ClassA本身搜索,如果搜索不到就在该类实现的接口中搜索(从下到
上) 索不到就在该类的父类搜索(从下到上)。如果还搜索不到,就 会抛出java.lang.NoSuchFieldError异常。CassA的 实现的接口都有相同的子段而ClassA本身没有,则在编译器会拒绝编译。

3.类方法(不只是指static方法)解析:

要查找类ClassA里的方法a(),先在ClassA里查找,找不到则到其父类中
找(从下到上),否则再在接口中找, 在接口中找到,则说明a()是一个未实现的接口方法,则抛出 java.lang.AbstractMethodError异常。如果连在接口 不到,则宣布查找失败,抛出java.lang.NoSuchMethodError异常。当然查找到后还有进行访问权限(public,private
的验证。

4.接口方法解析:

从下到上的接口进行搜索。

六.初始化:

1.类的初始化时机:(有且仅有如下5种情况需要初始化)

1遇到new,getstatic,putstatic,invokestatic这四条字节码指令时,如果该类还没加载,则进行初始化。而这四条字
现的场景为:new一个对象;读取或设置一个静态变量;调用一个类的静态方法;

2.使用放射的时候,如果类没有初始化,则触发其初始化。

3.当初始化一个类的时候,如果其父类还没初始化,则触发其父类的初始化。

4.当虚拟机启动时,主类(包含main()的类)会先进行初始化
4000


5.使用JDK1.7动态语言时,解析结果的方法的类需要初始化。

除了上面5中情况会触发类的初始化,其他情况都不会触发类的初始化。如:1.通过子类引用父类的静态变量时,并不 会触发子类的初始化。2.常量(static
final修饰的变量)不属于定义它的类,而是在编译阶段存入调用类的常量池,所以 调用一个类里面定义的常量不会触发该类的初始化。3.当初始化一个类的时候如果其父类还没初始化,则父类会被初始 化,当是接口不同,只有真正使用到接口的时候(如引用接口中定义的常量),才会初始化该接口

2.类初始化所做的事:

1.执行类构造器<clinit>()方法。clinit方法执行类中所有类变量(static)的赋值动作和静态语句块(static{})

静态语句块可以正常访问在其之前的静态变量,但是只能对在其之后的静态变量赋值,而不能取值。

2.虚拟机会保证子类的<clinit>( )方法执行之前其父类的该方法先执行。所以父类的静态语句块会比子类的静态语句 先执行。

3.接口虽然不能有静态语句块,但是可以定义变量。所以接口也有<clinit>()方法,与类不同的是,在执行接口的 <clinit>()方法是不需要先执行其父接口的该方法,只有在需要使用时才会调用。

4.类的<clinit>()方法在执行的时候会被加锁,一个类只能执行一次<clinit>()方法,当多个线程同时要初始化一
个类时,只有一个线程在执行该类的<clinit>()方法,其他线程阻塞,所以,当一个类的静态语句块里有很耗时的 操作,甚至是死循环时,其他线程都会长时间阻塞。

七.类的使用:只有初始化后的类才能使用。

 

八.类的卸载:

类在加载的时候会把类信息存放在方法区。而方法区也是有垃圾回收机制的,当一个类被回收时就是一个类的卸载 了。一个类在回收很严格,只有当一个类被评定为无用的类时才会被回收,只有同时满足一下3个条件才能算是无 用的类。(要注意,一个被被评定为无用不一定就会马上被回收)

1.该类的所以实例对象都被回收了,即Java堆里不存在该类的实例。

2.加载该类的ClassLoader已经被回收。

3.该类对应的java.lang.Class对象没有在如何地方被引用,无法在任何地方通过反射访问该类的方法。

 

九.类加载器:

两个类要被判为是同一个类,必须是来自同一个类文件,且是由同一个类加载器加载的。

1.双亲委派模型:



1.双亲委派,即类的加载要先由父 类加载器来加载,依次类推到启动类加载器。如果当排到当前类加载器来加载类 是无法加载,才交给其子 类加载器去加载。这样做的好处是,保证类具有优先级关系,即相同的类会被同一个类 加载器加载,如java.lang.Object类,不管在哪里加载该类,都会被启动类加载器加载,从而保证虚拟机里只有一个
java.lang.Object类,从而保证Java的基本体系。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: