资讯专栏INFORMATION COLUMN

java类加载相关

sixleaves / 1880人阅读

摘要:标准扩展类加载器,它负责加载或由系统变量指定位置中的类库加载到内存中。系统类加载器,它负责将类路径中的类库加载到内存。

类加载机制大家应该已经非常熟悉了,采取双亲委派机制,当加载一个类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成加载任务,就成功返回;如果父类无法加载,才由自己加载。

双亲委派机制的作用:防止内存中出现多份相同的字节码。

其他规则
1.隐式加载:在当前类中所有new的对象,如果没有被加载,则使用当前类的类加载器加载 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B
2.不同类加载器加载的类是不同的,通过类加载器+类全路径来唯一标识一个类

JVM预定义的三种类加载器
1.Bootstrap ClassLoader:启动类加载器,它负责将JAVA_HOME/lib下面的类库加载到内存中,如rt.jar;启动类加载器是由C++写的二进制代码,不是java类,在JVM启动的时候Bootstrap就已经启动。
2.Extension ClassLoader:标准扩展类加载器,它负责加载JAVA_HOME/lib/ext或由系统变量java.ext.dir指定位置中的类库加载到内存中。
3.APP ClassLoader:系统类加载器(System ClassLoader),它负责将类路径CLASSPATH中的类库加载到内存。

加载顺序图如下:

图中的BootStrapClassLoader、ExtClassLoader、APPClassLoader不是真正的继承关系,只是逻辑上的上下级类加载器;

实际上的类关系如下图:

可以看到ExtClassLoader和APPCLassLoader都继承自URLClassLoader,也就证实了二者并非真正的继承关系;

通过上图可以看到最顶层的类是抽象类ClassLoader:是所有类加载器的基类(除了启动类加载器),定义了类加载最核心的操作;
SecureClassLoader:添加了关联类源码、关联系统权限支持
URLClassLoader:支持从jar文件和文件夹中获取class
ExtClassLoader:扩展类加载器Extension ClassLoader
APPClassLoader:系统类加载器,也称为System ClassLoader

ClassLoader:

父子类加载器是通过ClassLoader一个parent属性来标识,APPClassLoader的父加载器是ExtClassLoader,ExtClassLoader的父加载器是null。

ClassLoader提供了两个构造器,一个是没有参数的,一个是有参数的;如下图:

没有参数的构造器默认将系统类加载器作为parent加载器;
有参数的构造器将参数指定的加载器作为父类加载器;

Launcher:

ExtClassLoader和AppClassLoader都是Launcher的子类,在ClassLoader初始化或者直接通过ClassLoader的getSystemClassLoader()获取的时候会调用initSystemClassLoader(),从而调用sun.misc.Launcher.getLauncher(),将系统类加载器赋值给ClassLoader的scl变量;

我们看下Launcher类初始化的时候都做了什么工作,如图:

主要是三部工作:
1.创建ExtClassLoader
2.创建AppClassLoader
3.将线程系统类加载器设置为线程上下文类加载器,什么是上线文类加载器?

线程上下文类加载器
java提供了为很多服务商提供了接口,简称SPI(Service Provider Interface),具体的实现由各厂商提供,例如mysql驱动,oracle驱动等。例如:mysql驱动加载接口类在rt.jar中,由启动类加载器加载,具体实现类在mysql驱动包中,驱动包一般放到我们自己的程序路径lib下,应该由系统类加载器加载;但是在使用如下代码进行数据库连接使用操作的时候,就会出现在rt.jar中要加载驱动包里代码的情况(类加载器是启动类加载器),由隐式加载规则可知,驱动包也要使用启动类加载器加载,由类加载机制可知,是无法通过启动类加载器来加载的;那这种情况怎么办呢,就要通过线程上下文类加载器来解决。

上面描述的情况如下:使用jdbc进行数据库操作如下

1.Class.forName("com.mysql.jdbc.Driver");// 加载mysql驱动
2.Connection conn = DriverManager.getConnection(url);//创建连接
3.Statement stmt = conn.createStatement();//得到statement对象
4.操作数据库  关闭连接。。

第一步在实例化Driver时,会调用DriverManager的registerDriver()方法收集divers,将驱动类注册到DriverManager容器中,DriverManage的drivers容器:

注册的代码:

Class.forName("com.mysql.jdbc.Driver")相当于:
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class driversClass = loader.loadClass("com.mysql.jdbc.Driver");
driversClass.newInstance();
由此可见com.mysql.jdbc.Driver是由我们自己应用类加载器AppClassLoader进行加载;

第二步通过DriverManager.getConnection(url),会循环获取drivers中的driver,调用具体driver实现里的cnnect()方法,进行连接

caller.getClassLoader()是启动类加载器为null,因此callerCL为系统类加载器

getConnection通过isDriverAllowed方法校验类是否有权限被加载

通过AppClassLoader来加载Driver看是否和已注册的Driver是同一个类,如果是则调用driver的connect方法

在java6以后,引入了service provider概念,在/META-INF/services/java.sql.Driver文件中配置需要加载的驱动类,
在DriverManager初始化的时候会调用loadInitialDrivers方法,

会使用AppClassLoader进行加载,所以在自己程序中可以不用Class.forName显示调用。

上面包类结构如下图:

tomcat类加载
我们运行tomcat的多个实例,不想安装tomcat软件副本,我们可以配置多个工作目录,每个运行实例独占一个工作目录,但是共享一个安装目录。
变量解释:
CATALINA_HOME:tomcat安装目录,多个工作目录可共享安装目录
CATALINA_BASE:tomcat工作目录,tomcat每个运行实例需要使用自己的conf、logs、temp、webapps、work、shared目录,CATALINA_BASE就是指向这个目录

如下图:两个应用公用CATALINA_HOME,CATALINA_BASE指向各自工作目录


首先看下tomcat在启动时类初始化类加载器过程

首先是创建commonClassLoader,commonClassLoader加载的是配置文件catalina.properties中配置的

common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=
例如在我们的服务器上路径是:
/opt/soft/tomcat/lib、/opt/soft/tomcat/lib/*.jar

因为server.loader和shared.loader未配置具体加载目录信息,catalinaLoader和sharedLoader默认为commonLoader(在tomcat5以后catalinaLoader和sharedLoader默认不启用)

tomcat一共定义了两种类加载器
StandardClassLoader:实例化commonloader、catalinaLoader、sharedLoader,不提供热部署功能,遵循双亲委派机制
WebappClassLoader:和context级容器相关联,加载web程序,支持其加载路径下资源改变后重新加载,不遵循双亲委派机制。

其类继承关系如下:

除此之外还有两个类:WebappLoader和VirtualWebappLoader,该两个类不是类加载器,只是对WebappClassLoader做了封装,对热部署、生命周期控制等功能做了一些控制;
VirtualWebappLoader是WebappLoader的子类,主要是和conf/context.xml这个文件相关联,主要功能是加载context.xml配置文件中设置的一些java类库,由于WebappClassLoader只能加载WEB-INF/class和WEB-INF/lib下的类库。而想扩展一下加载路径又不想添加到WEB-INF/lib中的时候,可以配置在context.xml文件中。

自己实现类记载器只要实现findclass即可,这里为了实现特殊目的而override了loadClass();WebappClassLoader重写了loadClass方法,先自己加载,如果加载不了再进行其他操作。

所以在Tomcat 6中默认情况下,不是完全按照先Tomcat的lib再Web应用的lib这种顺序去加载类。
Jar包的加载顺序是:
1)JRE中的Java基础包
2)Web应用WEB-INF/lib下的包
3)Tomcat/lib下的包

如果想要在Web应用间共享一些Jar包,则不仅需要将公共包放在Tomcat的lib下,还要删掉Web应用lib中的包,否则Tomcat启动时还是会优先加载Web应用lib下的包的。

如果想要自己指定一个Tomcatlib和Web应用lib之外的ClassPath,除了修改Tomcat启动脚本外,可以为不同Web应用的Context指定一个VirtualWebappLoader,但源码注释中写到不推荐在生产环境中使用。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/35503.html

相关文章

  • 我的面试准备过程--JVM相关

    摘要:程序计数器程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。它的主要缺点有两个一个是效率问题,标记和清除过程的效率都不 Jvm 相关  类加载机制 本段参考 http://www.importnew.com/2374... 类加载概念 类加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个ja...

    Towers 评论0 收藏0
  • java加载相关

    摘要:标准扩展类加载器,它负责加载或由系统变量指定位置中的类库加载到内存中。系统类加载器,它负责将类路径中的类库加载到内存。 类加载机制大家应该已经非常熟悉了,采取双亲委派机制,当加载一个类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成加载任务,就成功返回;如果父类无法加载,才由自己加载。 双亲委派机制的作用:防止内存中出现多份相同的字节码。 其他规则:1.隐式加载:...

    el09xccxy 评论0 收藏0
  • Java 虚拟机总结给面试的你(中)

    摘要:验证过程验证过程的目的是为了确保文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。二虚拟机字节码执行引擎虚拟机的执行引擎自行实现,可以自行制定指令集与执行引擎的结构体系。 本篇博客主要针对Java虚拟机的类加载机制,虚拟机字节码执行引擎,早期编译优化进行总结,其余部分总结请点击Java虚拟总结上篇 。 一.虚拟机类加载机制 概述 虚拟机把描述类的数据从Clas...

    MRZYD 评论0 收藏0
  • Java魔法——Unsafe应用解析

    摘要:典型应用锁和同步器框架的核心类,就是通过调用和实现线程的阻塞和唤醒的,而的方法实际是调用的方式来实现。 前言 Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C语言指针...

    reclay 评论0 收藏0
  • Java虚拟机加载过程

    摘要:二验证验证主要是为了确保文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。五初始化类的初始化阶段是类加载过程的最后一步,该阶段才真正开始执行类中定义的程序代码或者说是字节码。 关注我,每天三分钟,带你轻松掌握一个Java相关知识点。 虚拟机(JVM)经常出现在我们面试中,但是工作中却很少遇到,导致很多同学没有去了解过。其实除了应付面试,作为java程序员,了解...

    lentoo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<