Java程序的运行过程
用Java编写的.java的源程序,编译器通过Javac.exe将之编译成.class字节码文件,字节码文件经类加载器加载到JVM中进行解析生成二进制码通过底层操作系统与硬件实现交互。
类加载过程
类加载过程,本质上来说就是将各种来源的字节码文件经类加载器加载入JVM的内存中,将字节码文件转化为静态的数据结构存储在方法区中,并在堆中形成相应的java.lang.Class对象,进行该字节码代表的类层面的初始化操作。
类的加载过程分为加载,连接,初始化;
1.加载:类加载的第一步,将各种来源的包括本地、网络等字节码文件转化为静态的数据结构存储在方法区中,并在堆中形成相应的java.lang.Class对象实列;
2.连接:分为三个阶段:验证,准备与解析;
验证:对方法区中的静态结构进行元数据和字节的验证,保证其不会对虚拟机产生危害 ,验证的其实分布在类加载的各个过程,例如在编译的时候进行语义语法的验证,在解析的时候进行引用的验证;
准备:对该类中定义的静态变量赋初始值 ;
解析:把符号引用替换为直接引用,当一个类A中用到了类B,A的字节码文件中会有一串字符串(我的理解类似于java源程序中的import)指向类B,称之为符号引用,类A进行类加载,进入解析阶段发现类B并没有进行类加载,会触发类B的一个加载,并用类B的真实的内存地址替换符号引用,此时称之为直接引用。如果类B是一个具体的实体类那么该过程就称之为静态解析,也叫静态绑定,如果类B是一个接口,并不知道其具体的实现方式,那就会等到程序运行时栈中明确了B的具体实现类型再进行动态的解析,称之为动态绑定。
3.初始化:
当一个类解析完毕后,说明这个类被JVM接受了,要进行初始化操作,这里的初始化是进行类.class层面的一些操作比如这里的给成员变量和静态变量赋值以及静态代码块的执行;
public class Student {
int id=1;
static String name="Max";
static {
System.out.println("新建学生!");
}
}
值得注意的是,如果你仅仅想让一个类的静态代码块执行,那么就可以将该类进行类加载即可,可以通过java.lang.Class中Class.forName()方法实现类加载。
类加载器
基于HotSpot的JVM类加载器分为两种:Bootstrap ClassLoader(启动类加载器)和其他类加载器;
1.C/C++实现的嵌套在虚拟机内部的Bootstrap ClassLoader(启动类加载器),用于加载JAVA_HOME/lib目录下的核心类库中的类,并且只能加载包名在白名单的类,如(java,javax,sun开头的包)该加载器无法作为对象被引用;
2.其他类加载器:通过Java来实现,继承java.lang.ClassLoader,可以作为对象被程序引用;
Extension ClassLoader(扩展类加载器):用于加载JAVA_HOME/lib/ext目录下的类,加载Java核心类库的的补充扩展内容;
Application ClassLoader(应用类加载器):用于加载class_path环境变量中的类,主要加载的是程序员编写的类,以及第三方的类库,如数据库公司依照java接口编写的实现类;
User ClassLoader(自定义类加载器):加载自己定义来源的类;
双亲委派机制
每一种类加载器都有自己加载的限定范围,当一个类加载器接到类加载请求后,他不会首先加载该类,而是调用自己的父加载器进行加载,如果父加载器在其限定名范围内没有找到该类,那么就会委派其子加载器进行加载,向下直到加载该类成功;所以每一个类的加载过程都会自上而下,首先由Bootstrap ClassLoader进行加载,其次是Extension ClassLoader,最后才是Application ClassLoader和User ClassLoader,该过程称之为双亲委派机制;
-----------------------------------------------
那么问题来了,自定义一个java.lang.String类,当你调用String的时候JVM会加载自定义的String类吗???
答案肯定是不行,如果行的话Java的安全性肯定无法保障,就上面提到,Java程序中的每一个类都会自上而下依次由父、子加载器加载,直到加载到所需类为止,当程序需要调用String类时候首先会通过Bootstrap ClassLoader在核心类库中其相应的限定名范围内加载,自己编写的String类肯定不在核心类库中,也不在Bootstrap ClassLoader限定名范围之中,因此不会加载。