Java程序运行时,有两个阶段,编译和运行。编译阶段将Java文件编译成class字节码文件,然后在运行阶段将class文件加载到内存,并对class文件解释执行。
Java跨平台原因
Java语言跨平台的主要原因就是JVM以及字节码文件。
C语言是将高级语言直接解释成机器码,通过CPU指令集执行机器码,但是,不同架构的CPU,他的指令集可能是不一样的,这样就造成C语言难以跨平台。可是Java就不一样了,Java在机器和程序之间加了一次抽象的虚拟机。直接将Java程序编译成字节码,由Java虚拟机来解释字节码文件,对于不同的平台,解释器是不同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。
编译阶段

将Java文件转换成字节码文件。字节码文件存储了所有信息。
class文件中有一个class常量池,里面存放了字符串。
所以,类似于String s = “a”+”b”;在编译阶段会直接被优化为”ab”。
类加载阶段
类加载就是将class文件加载到内存,并对数据进行校验,转换解析和初始化,最终转化成可被虚拟机使用的Java类型。类加载阶段是在运行期间完成的。
类加载阶段主要包括五个阶段:加载,验证,准备,解析,初始化。。
加载
- 通过一个类的全限定名获取字节码文件。
- 将字节码文件静态存储结构转化为运行时内存区域。
- 生成这个类的java.lang.class对象。存在于方法区(元空间)。
验证
确保字节码文件信息是否合法。
准备
为类变量(static 修饰)分配内存并设置初始值的阶段。
注意,这个阶段只是类变量设置初始值,而不包括实例变量。
设置初始值通常情况下指的是数据类型的零值。
1 | public static int a = 23; |
这里只会将a设置为0,将a设置为23需要等到初始化阶段。
解析
将class常量池内的符号引用替换为直接引用的过程。
初始化
执行clinit()方法的过程,初始化类变量以及执行静态语句块。
clinit是类初始化的方法,init是对象初始化的方法,static静态语句块在clinit阶段执行,所以静态语句块肯定是先于构造函数执行的。
类初始化时机
这几个阶段类必须要初始化,而不是类加载。
- 遇到new实例化对象,读取或者设置static字段。
- 反射调用。
- 初始化一个类,若父类还没有初始化,需要先初始化父类。
- 初始化主类(执行main方法的)。
类加载器
对于任何一个类,都需要由加载他的类加载器以及这个类本身来确立他在JVM中的唯一性。比较两个类是否相等,首先应该是建立在同一个类加载器上的。两个类即使来自于同一个class文件,由不同的类加载器加载,这两个加载的类也是不相等的。
双亲委派模型

双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务(搜索范围内没有这个类),子加载器才会尝试自己去加载,这就是双亲委派模式。
避免了类加载的混乱。比如说,在classpath下定义了一个java.lang.Object,要是没有双亲委派模型,那么应用程序类加载器会加载这个类,那么就会与启动类加载器加载的Object类冲突。但是要是有类加载机制,那么将会交给启动类加载器,这样加载的还是java自带的Object类,就不会产生冲突。
破坏双亲委派模型
如何破坏双亲委派模型?
继承ClassLoader,自己重写loadClass方法,然后加入自己逻辑,特定某些class可以按照你的方式处理。。
重写loadClass:
findLoadedClass
委托parent加载器加载(这里注意bootstrap加载器的parent为null)
自行加载
打破委派机制要做的就是打乱2和3的顺序,通过类名筛选自己要加载的类,其他的委托给parent加载器
JDBC破坏
因为类加载器受到加载范围的限制,在某些情况下父类加载器无法加载到需要的文件,这时候就需要委托子类加载器去加载class文件。
JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,比如MySQL驱动包。DriverManager 类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap类加载器加载,而其Driver接口的实现类是位于服务商提供的 Jar 包,根据类加载机制,当被装载的类引用了另外一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。也就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类。我们知道,BootStrap类加载器默认只负责加载 $JAVA_HOME中jre/lib/rt.jar 里所有的class,所以需要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。
通过线程上下文类加载器,默认是应用程序类加载器,可以通过Thread的方法进行设置。
Tomcat破坏
OSGI
类加载器不再是双亲委派模型的树状结构,而是网状结构。