类加载和类加载器



装载,连接,初始化

  1. java类型(类和接口)的生命周期(从它进入虚拟机开始一直到最终退出)为例来讨论开始阶段的装载,连接,初始化

  2. 装载就是把二进制形式的java类读入java虚拟机中,而连接就是把这种已经读入虚拟机的二进制形式的类型数据合并到虚拟机的运行时状态中去。

  3. 验证:确保了java类型数据格式正确

    初始化:java虚拟机明确规定在每个类或接口首次主动使用时初始化

 


 

 

装载:要装载一个类型,java虚拟机必须:

  1. 通过该类型的完全限定名,产生一个代表该类型的二进制数据流

  2. 解析这个二进制数据流为方法区内的内部数据结构

  3. 创建一个表示该类型的java.lang.Class的实例

 

 

装载的最终产品是Class类的实例对象,就是把一个类型的二进制数据解析为方法区中的内部数据结构,并在堆上建立一个Class对象的过程,不同版本或博客上说的不一致(并没有明确规定是在Java堆中,对于HotSpot虚拟机而言,Class对象比较特殊,它虽然是对象,但是存放在方法区里面)

 

Java类型要么由启动类装载器装载,要么通过用户自定义的类装载器装载,启动类装载器是java虚拟机实现的一部分,它与实现无关的方式装载类型,如果一个类装载器在预先装载时遇到问题,它必须等到程序首次主动使用该类时才报告错误。如果这个类一直没有被程序主动使用,那么就不会报告错误

 

非数组类:是由类加载器来完成

 

数组类:数组类本身不通过类加载器创建,它是由java虚拟机直接创建,但数组类与类加载器有很密切的关系,因为数组类的元素类型最终要靠类加载器创建。

连接

验证:连接的第一步,文件格式验证,元数据验证,字节码验证,符号引用验证

  1. 文件格式验证,验证是否以魔数开头,常量池中的常量是否有不被支持的类型,这阶段的验证是基于二进制流进行的,只有通过该阶段验证后,字节流才会进入内存的方法区进行存储。

  2. 元数据验证,这个类是否有父类,是否继承了不被允许的类或接口

  3. 字节码验证,确保语义是合法的

  4. 符号引用验证,符号引用验证的目的是确保解析动作能正常执行,如果无法通过符号引用验证,那么将会抛出一个java.lang.IncompatibleClassChangeError异常的子类,如java.lang.IllegalAccessErrorjava.lang.NoSuchFieldErrorjava.lang.NoSuchMethodError

 

准备:正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都在方法区中进行分配。实例变量是在对象实例化时随对象一起分配在堆中,通常情况类变量在准备阶段的初始化都为零值,特殊阶段是当变量被final修饰,static final,则准备阶段的值就是代码里的值。

 

解析:就是将虚拟机常量池内的符号引用替换为直接引用的过程。

 

          类或接口的解析,字段解析

 

 

初始化:类初始化阶段是类加载过程的最后一步,除了在加载阶段用户应用程序可以通过自定义加载器参与外,其余动作全部由虚拟机主导和控制,为类的静态变量赋予正确的初始值;如static inta=50;这时50才赋给a这个静态变量,所有java虚拟机在每个类或接口被java程序首次主动使用时才初始化它们,就是执行类构造器<clinit>的过程。

 

类加载器:

加载阶段中通过类型的全限定名,加载类的二进制流的动作由虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块称为 类加载器。

 

Eclipse里面使用1.6jdk运行和1.6版本编译

演示:两个类来源于同一个class文件,被同一个虚拟机加载,加载他们的类加载器不同(一个是系统应用程序类加载器,一个是自定义的加载器),则加载的两个类是独立的。

 

package eclipsestarttime.actions;

 

import java.io.IOException;

import java.io.InputStream;

 

public class ClassLoaderTest {

   public static void main(String[] args) throws Exception {

       ClassLoader myLoader = new ClassLoader() {

           @Override

           public Class<?> loadClass(String name) throwsClassNotFoundException {

                try {

                    String fileName =name.substring(name.lastIndexOf(".") + 1) + ".class";

                    InputStream is =getClass().getResourceAsStream(fileName);

                    if (is == null) {

                        returnsuper.loadClass(name);

                    }

                    byte[] b = newbyte[is.available()];

                    is.read(b);

                    return defineClass(name, b,0, b.length);

                } catch (IOException e) {

                    throw newClassNotFoundException(name);

                }

           }

       };

       Object obj =myLoader.loadClass("eclipsestarttime.actions.ClassLoaderTest").newInstance();

       System.out.println(obj.getClass());

       System.out.println(obj instanceofeclipsestarttime.actions.ClassLoaderTest);

    }

}

 

 

双亲委派模型:

  

 

应用程序中如果没有自定义的类加载器,一般情况下的就是使用默认的应用程序加载器进行加载。

启动类加载器是加载 JAVA_HOME\lib下的被虚拟机识别的jar 或者被–Xbootclasspath指定路径中的类,比如:rt.jar,jsse.jar

 

扩展类加载器是加载 JAVA_HOME\lib\ext 目录中的,或者 java.ext.dirs 系统变量所指定的路径中的所有类库

 

 

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己

去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是

如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈

自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自

己去加载。

 

 

双亲委派模型被破坏:

  1. 典型的JNDIJDBCJCE,还有OSGI

 

虚拟机字节码执行引擎

 

解释执行和编译执行的区别:

计算机所能识别的语言只有机器语言,即01构成的,但通常人们编程时,不采用机器语言,因为它非常难于记忆和识别。

目前通用的编程语言有两种形式:汇编语言和高级语言。

 

汇编语言的实质和机器语言是相同的,都是直接对硬件操作,只不过指令采用的了英文缩写的标识符,更容易识别和记忆。

        

高级语言是目前绝大多数编程者的选择,和汇编语言相比,它所编制的程序不能直接被计算机识别,必须经过转换才能被执行,按转换方式分为两类:

 

解释执行:由解释器根据输入的数据当场执行而不生成任何的目标程序。

编译执行:先将源代码编译成目标语言之后通过连接程序到生成的目标程序执行。

 

Java 程序既是编译型的(转换为java字节码的中间语言),又是解释型的(JVM对字节码进行解析和运行),只编译一次,解释在每次运行程序时都会进行。

 

运行时栈帧结构

    栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素,

 

 

 

重载:

重写:

packageeclipsestarttime.actions;

 

/**

 * 方法动态分派演示

 * @author

 *

 */

public classStaticDispatch {

 

    staticabstract class Human {

       protected abstract void sayHello();

    }

 

    staticclass Man extends Human {

       @Override

        protected void sayHello(){

           System.out.println("man say hello");

       }

    }

 

    staticclass Woman extends Human {

       @Override

       protected void sayHello() {

           System.out.println("woman say hello");

       }

    }

 

    publicstatic void main(String[] args) {

       Human man = new Man();

       Human woman = new Woman();

       man.sayHello();

       woman.sayHello();

       man = new Woman();

       man.sayHello();

    }

}

 

结果:man say hello

woman say hello

woman say hello

 

 

Tomcat:正统的类加载器结构

 

Tomcat 4个目录结构:

放置在/common目录中:类库可被tomcat 和所有的web应用程序共同使用。

放置在/server目录中:类库可被tomcat使用,对所有的web应用程序都不可见。

放置在/shared:类库可被所有的web应用程序共同使用,但仅对tomcat自己不可见。

放置在/webapp/web-inf:类库仅仅可以被此web应用程序使用,对tomcat和其他web应用程序都不可见

 

Tomcat 自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现。其结构如下:


 

 

 

 

Osgi类加载器结构:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值