【Java】【JVM基础】详解class文件与JVM的关系
一、类的加载时机
我们通过javac将Java文件编译成的.class文件什么时候会被加载到JVM中?
在虚拟机规范中严格定义了有且只有五种情况必须立即对类进行初始化(也就是将class文件加载到JVM中):
- 创建类的实例(new的方式)。访问某个类或接口的静态变量,或者对该静态变量进行赋值,调用类的静态方法等
- 反射的方式
- 初始化某个类的子类,则其父类也会被初始化
- Java虚拟机启动的时候被标明为启动类的类,直接使用java.exe命令来运行某个主类(包括main方法的那个类)
- 当使用JDK1.7的动态类型 语言支持(java.lang.invoke包下)时
所以说java类的加载是动态的,它并不是一次性将所有类全部加载后再运行,而是保留程序运行的基础类(也就是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载,这就是为了节省内存开销。
二、如何将类加载到jvm中
class文件是通过类加载器加载到jvm中的。
那么java默认有三种类型的加载器:应用类、拓展类和引导类加载器
各个加载器的工作职责:
- 启动类加载器:负责加载$JAVA_HOME中jre/lib/rt.jar里面的所有class,由C++实现,不是ClassLoader子类
- 拓展类加载器:负责加载Java平台中拓展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext/*.jar或-Djava.ext.dirs指定目录下的jar包
- 应用类加载器:负责加载classpath中指定的jar包及目录中class
类加载器工作过程:
- 当应用类加载器加载一个class的时候,它首先自己不会尝试加载这个类,而是把class请求委派给父类加载器-拓展类加载器去完成。
- 当拓展类加载器加载一个class的时候,它首先也不会自己去加载这个class,而是把这个class委派给引导类加载器。
- 如果引导类加载器加载class失败(例如在$JAVA_HOME/jre/lib里面未找到该class),会使用拓展类加载器来尝试加载。
- 如果拓展类加载器也加载失败,则会使用引导类加载器来加载。
- 如果引导类也加载失败,则会报ClassNotFoundException异常
上面的工作过程其实就是双亲委派模型,如果一个类加载器加收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把类加载这个请求委托给父类加载器去完成,一次向上。这样可以防止内存中出现多份同样的字节码(安全性角度)。
类加载器在加载成功这个类之后,会把得到的java.lang.class类实例缓存起来,下次再请请求加载该类的时候,类加载器会直接使用缓存的类实例了,而不会尝试再次加载。
类加载到JVM中详细过程
- 加载,查找并加载类的.class字节码二进制数据,同时在java堆中创建一个java.lang.Class类的对象(该对象在编译期就存在.class文件中,一个类的实例对象只能对应一个Class对象,所以在共享堆中创建一份)。
- 连接,分为三块内容:验证、准备、解析。
- 验证,包括文件格式、元数据、字节码和符号引用等验证
- 准备,为类的静态变量分配内存,并将其初始化为默认值
- 解析,把类中的符号引用转换为直接引用
- 初始化,为类的静态变量赋予正确的初始值
类编译优化-JIT即时编译器
在JVM加载这些class文件之后,对这些字节码如果是逐条取出,逐条执行(解析器解析)的话,会很慢,JVM会做进一步的优化:
拿到这些字节码之后,会对这些Java字节码进行重新编译优化,生成机器码,让CPU直接执行,这样编译出来的代理效率更高,但重新编译也是需要时间的,所以JVM会对热点代码做重新编译,而非热点代码就直接解析好了,重新编译热点代码就使用JIT即时编译期来实现。
那么检测是否为热点代码的方式有:采样和计数器。
HotSpot VM使用的是计数器的方法检测热点代码,其具体实现是通过两个计数器:方法调用计数器和回边计数器;在确定虚拟机运行参数的前提下,这两个计数器都有一个阈值,当计数器超过阈值溢出了,就会触发JIT编译。
三、总结-一个对象的加载过程
- 一个.java文件经过javac命令编译成功后,会得到一个.class字节码文件
- 当我们执行了初始化操作(上面五种情况:new操作、子类初始化【父类也会一同初始化】,反射等等),会将读取.class字节码文件,取出二进制数据,通过类加载器装载到jvm内存中
- 将.class文件加载到jvm中需要经过加载,连接(验证、准备、解析class文件信息)和初始化。
- 在加载的时候,会在Java堆中创建生成一个java.lang.class类的对象,这个Class对象代表着类相关的信息,只要是类有什么东西(包括成员变量,方法和构造器等该Class对象都有对应的方法getFields(),getMethods(),getConstructors()),在Class对象中都可以找得到,然后就可以使用Class对象创建类的具体实例来进行调用实现的具体功能,我们还可以通过Class对象来判断对象的真正类型
- jvm还会对热点代码进行重新编译优化,使用JIT编译技术进行优化,提高运行效率
- 点赞
- 收藏
- 关注作者
评论(0)