美文网首页
《深入理解JVM虚拟机》 - 类加载

《深入理解JVM虚拟机》 - 类加载

作者: 陈菲TW | 来源:发表于2020-02-25 17:40 被阅读0次

    类加载是指处理类文件并加载到虚拟机内存中。类的生命周期,从加载到内存,到卸载出内存,分为以下阶段:加载-验证-准备-解析-初始化-使用-卸载,其中验证-准备-解析是连接过程。解析可能发生在初始化之后,称为late binding。类加载称为动态加载,连接称为动态连接,因为类加载在运行时进行,比如面向接口编程运行时才确定具体实现类型。

    一、类加载

    哪些类要被加载?虚拟机规范要求,对类进行主动引用时立即启动类加载:1.new指令;getstatic/putstatic指令;读取/设置静态字段(final除外)invokestatic指令;调用类的静态方法;2.类的反射调用;3. 初始化类的父类还没初始化;4. 主类,即main方法所在的类。

    宏观来讲,类加载包含5个阶段:加载-验证-准备-解析-初始化

    1. 加载:通过类的全限定名获取类的二进制字节流-->将字节流转化为方法区数据结构-->堆中生成类的Class对象,作为方法区类信息的访问入口。读取字节流两种方式:从压缩包读取(如JAR/WAR);运行时生成(动态代理技术Proxy)

    2. 验证:保证字节流的符合虚拟机要求,不会危害虚拟机安全。危险操作如数组访问越界/无效的强制类型转换/跳转到不存在的代码行,虽然java语言安全,但class文件不一定安全,校验是必要的。

    虚拟机规范对类文件进行验证:1.文件格式验证:魔数/主次版本号验证;基于方法区存储结构的验证,验证通过才进入方法区;2.元数据验证:是否继承了final类,继承abstract类是否实现抽象方法等;3. 字节码验证(复杂):方法语义验证,类型转换合理,代码跳转合理; 4. 符号引用验证:验证字符串的全限定名能否找到类;类中是否存在符合描述的方法/字段;符号引用中的类/字段/方法的访问性是否足够。

    3. 准备:为类变量分配内存(方法区)并初始为零值。

    4. 解析:把类文件常量池中的符号引用替换为内存直接引用,如Student类包含Book类字段,S类文件中包含符号引用(B类全限定名);加载S类期间的解析过程把B类全限定名发送到S类加载器,加载器加载B类到内存(包含验证步骤验证B类访问符能否支撑S类的引用)并返回B类的内存地址。

    5. 初始化:执行类构造器clinit,编译器收集类变量赋值动作putstatic和静态语句块static{}生成。不同于实例构造器init,clinit无需调用父类clinit,虚拟机保证执行顺序。

    二、类加载器

    类加载器和类共同确定了类在虚拟机中的唯一性。同一个类被不同的加载器加载,就是不同的类对象。

    类加载器的分类:1. 启动类加载器(bootstrap/C++):虚拟机的一部分,用于加载java_home/lib中虚拟机可识别的类库,如rt.jar;2. 扩展类加载器:用于加载java_home/lib/ext目录下的类库;3. 应用程序类加载器(Java):不属于虚拟机,用于加载用户类路径classpath上的类。

    双亲委派模型,通过组合的方式复用父加载器的代码。当类加载器收到加载请求,首先委派给父加载器,只有父加载器说它无法加载时,子加载器才尝试自己加载。采用双亲委派模型好处是保证使用不同类加载器最终得到的都是同一个对象,从而保证Java 核心库的类型安全。

    配置:TraceClassLoading/TraceClassUnloading用于查看类加载/卸载信息。

    三、案例与实战

    1. Tomcat(开源)

    主流java web服务器如tomcat/jetty/weblogic/websphere都实现了自定义的类加载器。

    Tomcat用于存放java类库的目录:/common:tomcat和web应用共同使用;/server:tomcat使用,对web应用不可见;/shared:所有web应用程序共同使用,tomcat不可见;应用本身的/WebApp/WEB-INF/*:被该应用程序使用,对tomcat和其他应用不可见

    Tomcat类加载器架构-双亲委派模型:启动类加载器-扩展类加载器-应用程序类加载器-common类加载器-(catalina类加载器(对应server/)+shared类加载器-webapp类加载器-jsp类加载器)。webapp类加载器和jsp类加载器会存在多个实例,每个web应用对应一个webapp类加载器,每个jsp文件对应一个jsp类加载器。

    2. OSGi:open service gateway initiative

    SGi联盟制定的基于java语言的动态模块化规范。

    模块(Bundle):每个模块有自己的类加载器;模块类似普通的java类库,用jar封装,内部是java package和class;模块可以声明导入依赖package和导出package,通过导出控制可见性。优势在于当程序升级/调试除错时,可以只停用程序的一部分。

    类加载器架构:父类加载器/各个模块类加载器。父类加载器负责java.*开头的类;各个模块加载器是平时是平级关系,只在运行时具体使用package时,才根据导入导出关系进行委派。模块A1声明依赖package A,模块A2声明发布package A,那么所有对这个package的类加载动作都会委派模块A2的类加载器完成。

    3. 字节码生成技术和动态代理的实现

    字节码生成:javac,动态代理,AOP/反射都会在运行时生成字节码来提高执行速度。

    动态代理

    代理:客户端通过代理发送请求到服务端,这里的服务端是原始类中的方法,发送请求就是方法调用,调用方通过调用代理提供的invoke方法来调用原始类中的方法。java中的代理需要实现接口InvokationHandler,还需要用到Proxy.newProxyInstance方法,这个方法会生成一个目标对象的实例,对于客户端,调用代理方法和调用实际服务端方法是一样的。代理做中间处理:log

    动态:不会为原始类编写静态的代理类,而是实现了在原始类和接口未知的情况下确定代理类的代理行为,当代理类和原始类脱离,就可以灵活应用在各种场景。

    字节码生成:调用ProxyGenerator.generateProxyClass方法完成生成字节码的动作。生成代理类:$Proxy0.class。思考Spring的bean组织管理

    相关文章

      网友评论

          本文标题:《深入理解JVM虚拟机》 - 类加载

          本文链接:https://www.haomeiwen.com/subject/nhjxchtx.html