我先梭哈了,你们随意
文章内容不够严谨,可能有错误的地方,大家最好系统的去学习。
一、类加载的简单描述
1.三种类加载器:
1.根加载器(Bootstrap ClassLoader):最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar等。
core java libraries,比如比如 java.util.*、java.io.*、java.nio.*、java.lang.*等;
它是由 C语言代码实现的;
2.扩展类加载器(Extention ClassLoader):负责加载 JVM 扩展类加载,JAVA_HOME/lib/ext/*.jar;
3.应用程序加载器(App ClassLoader):负责加载当前应用的classpath的所有类,由软件开发人员编写的应用程序。
2.类加载:
JVM 运行并不是一次性加载所需要的全部类的,它是按需加载,也就是延迟加载。程序在运行的过程中会逐渐遇到很多不认识的新类,这时候就会调用 ClassLoader 来加载这些类。加载完成后就会将 Class 对象存在 ClassLoader 里面,下次就不需要重新加载了。
双亲委派模式:
工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,
如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
为什么会使用这种模式?
1.避免类重复加载;
2.为了防止核心API库被篡改(防止恶意写出一个和api类同名的类,加载到JVM中,威胁系统安全)。
3.类加载方式:
1.隐式加载:程序在运行过程中当碰到通过new等方式生成对象时,隐式调用类装载器加载对应的类到jvm中;
2.显式装载, 通过class.forName()等方法,显式加载需要的类。
4.类加载过程:
1.加载:类加载阶段就是由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例,这个Class对象在日后就会作为方法区中该类的各种数据的访问入口。
2.链接:
(1)验证:
为了防止错误,加载进来的字节流符合虚拟机规范。
文件格的验证,验证是否符合class文件规范,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?
语义验证,检查一个被标记为final的类型是否包含子类;类中的字段,方法是否与父类冲突?确保父类和子类之间没有不兼容的一些方法声明(比如方法签名相同,但方法的返回值不同),是否出现了不合理的重载?
字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性;
符号引用验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?
操作验证,在操作数栈中的数据必须进行正确的操作,对常量池中的各种符号引用执行验证。
(2)准备:
为类中的所有静态变量分配内存空间,并为其设置一个初始值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值,比如八种基本类型的初值,默认为0;引用类型的初值则为null;
被final修饰的静态变量,会直接赋予原值;主要是为类变量(静态变量)分配内存,并且赋予初值。例如:final static a = 1;这时赋值就是1;
(3)解析:
将常量池内的符号引用替换为直接引用的过程。在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
可以认为是一些静态绑定的会被解析,动态绑定则只会在运行时进行解析;静态绑定包括一些final方法(不可以重写),static方法(只会属于当前类),构造器(不会被重写)。
(4)初始化:
这个阶段主要是对类变量初始化,是执行类构造器的过程。
只对static修饰的变量或语句进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。