Java类加载过程及static详解
类从被加载到JVM中开始,到卸载为⽌,整个⽣命周期包括:加载、验证、准备、解析、初始化、使⽤和卸载七个阶段。
其中类加载过程包括加载、验证、准备、解析和初始化五个阶段。
类加载器的任务就是根据⼀个类的全限定名来读取此类的⼆进制字节流到JVM中,然后转换为⼀个与⽬标类对应的java.lang.Class对象实例。BootstrapClassLoader、ExtClassLoader和AppClassLoader
defineClass⽅法将字节码的byte数组转换为⼀个类的class对象实例,如果希望在类被记载到JVM时就被链接,那么可以调⽤resolveClass⽅法。
⾃定义类加载器需要继承抽象类ClassLoader,实现findClass⽅法,该⽅法会在loadClass调⽤的时候被调⽤,findClass默认会抛出异常。
findClass⽅法表⽰根据类名查类对象
loadClass⽅法表⽰根据类名进⾏双亲委托模型进⾏类加载并返回类对象
defineClass⽅法表⽰跟根据类的字节码转换为类对象
双亲委托模型,约定类加载器的加载机制
当⼀个类加载器接收到⼀个类加载的任务时,不会⽴即展开加载,⽽是将加载任务委托给它的⽗类加载器去执⾏,每⼀层的类都采⽤相同的⽅式,直⾄委托给最顶层的启动类加载器为⽌。如果⽗类加载器⽆法加载委托给它的类,便将类的加载任务退回给下⼀级类加载器去执⾏加载。
双亲委托模型的⼯作过程是:如果⼀个类加载器收到了类加载的请求,它⾸先不会⾃⼰去尝试加载这
个类,⽽是把这个请求委托给⽗类加载器去完成,每⼀个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当⽗类加载器反馈⾃⼰⽆法完成这个加载请求(它的搜索范围中没有到所需要加载的类)时,⼦加载器才会尝试⾃⼰去加载。
使⽤双亲委托机制的好处是:能够有效确保⼀个类的全局唯⼀性,当程序中出现多个限定名相同的类时,类加载器在执⾏加载时,始终只会加载其中的某⼀个类。
使⽤双亲委托模型来组织类加载器之间的关系,有⼀个显⽽易见的好处就是Java类随着它的类加载器⼀起具备了⼀种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,⽆论哪⼀个类加载器要加载这个类,最终都是委托给处于模型最顶端的启动类加载器进⾏加载,因此Object类在程序的各种加载器环境中都是同⼀个类。相反,如果没有使⽤双亲委托模型,由各个类加载器⾃⾏去加载的话,如果⽤户⾃⼰编写了⼀个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的⾏为也就⽆法保证,应⽤程序也将会变得⼀⽚混乱。如果⾃⼰去编写⼀个与rt.jar类库中已有类重名的Java类,将会发现可以正常编译,但永远⽆法被加载运⾏。
11.14什么情人节双亲委托模型对于保证Java程序的稳定运作很重要,但它的实现却⾮常简单,实现双亲委托的代码都集中在java.lang.ClassLoader的loadClass()⽅法中,逻辑清晰易懂:先检查是否已经被加载过,若没
有加载则调⽤⽗类加载器的loadClass()⽅法,若⽗加载器为空则默认使⽤启动类加载器作为⽗加载器。如果⽗类加载器加载失败,抛出ClassNotFoundException异常后,再调⽤⾃⼰的findClass⽅法进⾏加载。
1、加载
简单的说,类加载阶段就是由类加载器负责根据⼀个类的全限定名来读取此类的⼆进制字节流到JVM内部,并存储在运⾏时内存区的⽅法区,然后将其转换为⼀个与⽬标类型对应的java.lang.Class对象实例(Java虚拟机规范并没有明确要求⼀定要存储在堆区中,只是hotspot选择将Class对戏那个存储在⽅法区中),这个Class对象在⽇后就会作为⽅法区中该类的各种数据的访问⼊⼝。
2、链接
链接阶段要做的是将加载到JVM中的⼆进制字节流的类数据信息合并到JVM的运⾏时状态中,经由验证、准备和解析三个阶段。
1)、验证
验证类数据信息是否符合JVM规范,是否是⼀个有效的字节码⽂件,验证内容涵盖了类数据信息的格式验证、语义分析、操作验证等。
格式验证:验证是否符合class⽂件规范
语义验证:检查⼀个被标记为final的类型是否包含⼦类;检查⼀个类中的final⽅法视频被⼦类进⾏重写;确保⽗类和⼦类之间没有不兼容的⼀些⽅法声明(⽐如⽅法签名相同,但⽅法的返回值不同)
操作验证:在操作数栈中的数据必须进⾏正确的操作,对常量池中的各种符号引⽤执⾏验证(通常在解析阶段执⾏,检查是否通过富豪引⽤中描述的全限定名定位到指定类型上,以及类成员信息的访问修饰符是否允许访问等)胡玛丽个人资料
2)、准备
为类中的所有静态变量分配内存空间,并为其设置⼀个初始值(由于还没有产⽣对象,实例变量不在此操作范围内)
被final修饰的静态变量,会直接赋予原值;类字段的字段属性表中存在ConstantValue属性,则在准备阶段,其值就是ConstantValue的值
3)、解析
拴的拼音
将常量池中的符号引⽤转为直接引⽤(得到类或者字段、⽅法在内存中的指针或者偏移量,以便直接调⽤该⽅法),这个可以在初始化之后再执⾏。
可以认为是⼀些静态绑定的会被解析,动态绑定则只会在运⾏是进⾏解析;静态绑定包括⼀些final⽅法(不可以重写),static⽅法(只会属于当前类),构造器(不会被重写)
3、初始化
将⼀个类中所有被static关键字标识的代码统⼀执⾏⼀遍,如果执⾏的是静态变量,那么就会使⽤⽤户指定的值覆盖之前在准备阶段设置的初始值;如果执⾏的是static代码块,那么在初始化阶段,JVM就会执⾏static代码块中定义的所有操作。
所有类变量初始化语句和静态代码块都会在编译时被前端编译器放在收集器⾥头,存放到⼀个特殊的⽅法中,这个⽅法就是<clinit>⽅法,即类/接⼝初始化⽅法。该⽅法的作⽤就是初始化⼀个中的变量,使⽤⽤户指定的值覆盖之前在准备阶段⾥设定的初始值。任何invoke之类的字节码都⽆法调⽤<clinit>⽅法,因为该⽅法只能在类加载的过程中由JVM调⽤。
如果⽗类还没有被初始化,那么优先对⽗类初始化,但在<clinit>⽅法内部不会显⽰调⽤⽗类的<clinit>⽅法,由JVM负责保证⼀个类的<clinit>⽅法执⾏之前,它的⽗类<clinit>⽅法已经被执⾏。
JVM必须确保⼀个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中⼀个线程对其执⾏初始化操作,其余线程必须等待,只有在活动线程执⾏完对类的初始化操作之后,才会通知正在等待的其他线程。
下⾯是关于static
⼀、static代表着什么
在Java中并不存在全局变量的概念,但是我们可以通过static来实现⼀个“伪全局”的概念,在Java中static表⽰“全局”或者“静态”的意思,⽤来修饰成员变量和成员⽅法,当然也可以修饰代码块。
Java把内存分为栈内存和堆内存,其中栈内存⽤来存放⼀些基本类型的变量、数组和对象的引⽤,堆内存主要存放⼀些对象。在JVM加载⼀个类的时候,若该类存在static修饰的成员变量和成员⽅法,则会为这些成员变量和成员⽅法在固定的位置开辟⼀个固定⼤⼩的内存区域(只要这个类被加载,Java虚拟机就能根据类名在运⾏时数据区的⽅法区内定到他们),有了这些“固定”的特性,那么JVM就可以⾮常⽅便地访问他们。同时如果静态的成员变量和成员⽅法不出作⽤域的话,它们的句柄都会保持不变。同时static所蕴含“静态”的概念表⽰着它是不可恢复的,即在那个地⽅,你修改了,他是不会变回原样的,你清理了,他就不会回来了。
同时被static修饰的成员变量和成员⽅法是独⽴于该类的,它不依赖于某个特定的实例变量,也就是说它被该类的所有实例共享。所有实例的引⽤都指向同⼀个地⽅,任何⼀个实例对其的修改都会导致其他实例的变化。
public class User {
private static int userNumber  = 0 ;
public User(){
userNumber ++;
}
public static void main(String[] args) {
User user1 = new User();
好玩网络游戏排行榜User user2 = new User();
System.out.println("user1 userNumber:" + User.userNumber);
System.out.println("user2 userNumber:" + User.userNumber);
}
}
-
-----------
Output:
user1 userNumber:2
user2 userNumber:2
⼆、怎么使⽤static
static可以⽤于修饰成员变量和成员⽅法,我们将其称之为静态变量和静态⽅法,直接通过类名来进⾏访问。
ClassName.propertyName
static修饰的代码块表⽰静态代码块,当JVM装载类的时候,就会执⾏这块代码,其⽤处⾮常⼤。
1、static变量
static修饰的变量我们称之为静态变量,没有⽤static修饰的变量称之为实例变量,他们两者的区别是:
静态变量是随着类加载时被完成初始化的,它在内存中仅有⼀个,且JVM也只会为它分配⼀次内存,同时类所有的实例都共享静态变量,可以直接通过类名来访问它。但是实例变量则不同,它是伴随着实例的,每创建⼀个实例就会产⽣⼀个实例变量,它与该实例同⽣共死。魔兽争霸3冰封王座秘籍大全
所以我们⼀般在这两种情况下使⽤静态变量:对象之间共享数据、访问⽅便。
public class TestStatic {
public static int count = 0;
public static void main(String[] args){
TestStatic test1=new TestStatic();
System.out.unt);
TestStatic test2=new TestStatic();
System.out.unt+" "+unt+" "+unt);
}
}
梦想的声音微博
输出结果:
1 1 1
可见,static变量并不是所在类的某个具体对象所有,⽽是该类的所有对象所共有的,静态变量既能被对象调⽤,也能直接拿类来调⽤。
2、static⽅法
static⽅法⼀般称作静态⽅法,由于静态⽅法不依赖于任何对象就可以进⾏访问,因此对于静态⽅法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态⽅法中不能访问类的⾮静态成员变量和⾮静态成员⽅法,因为⾮静态成员⽅法/变量都是必须依赖具体的对象才能够被调⽤。
  但是要注意的是,虽然在静态⽅法中不能访问⾮静态成员⽅法和⾮静态成员变量,但是在⾮静态成员⽅法中是可以访问静态成员⽅法/变量的。
因为static⽅法独⽴于任何实例,因此static⽅法必须被实现,⽽不能是抽象的abstract。
总结⼀下,对于静态⽅法需要注意以下⼏点:
(1)它们仅能调⽤其他的static ⽅法。
(2)它们只能访问static数据。
(3)它们不能以任何⽅式引⽤this 或super。
举个简单的例⼦:
  在上⾯的代码中,由于print2⽅法是独⽴于对象存在的,可以直接⽤过类名调⽤。假如说可以在静态⽅法中访问⾮静态⽅法/变量的话,那么如果在main⽅法中有下⾯⼀条语句:
  MyObject.print2();