Java ClassLoader笔记
.java:保存需要执行的程序逻辑 编译 .class:保存java代码转换后的虚拟机指令
要使用某个类时,虚拟机将加载.class文件,并创建对应的class对象。
将class文件加载到虚拟机的内存的过程叫类加载,过程如下:
①加载Loading:利用class文件创建class对象
②验证Verification:确保class文件的字节流中包含信息符合虚拟机要求,不会危害虚拟机本身安全,包括四种验证:文件格式验证;元数据验证;字节码验证;符号引用验证。
③准备Preparation:为类变量(static修饰的变量)分配内存并设置该类变量的初始值0
(static int i = 5;将只会把i初始化为0,5的值在初始化时赋值)。注意:智利不包含用final修饰的static,因为final在编译时就会分配了;这里不会为实例变量分配初始化,连变量会分配在方法区中,而实例变量是会随着对象一起分配到java堆中。
④解析Resolution:将常量池中的符号引用替换为直接引用的过程。
⑤初始化Initialization:执行静态初始化器和静态初始化成员变量。
其中②③④被称为链接(Linking)过程。
符号引用:符号引用就是字符串,这个字符串包含足够的信息,以供实际使用时可以找到相应的位置。你比如说某个方法的符号引用,如:“java/io/PrintStream.println:(Ljava/lang/String;)V”。里面有类的信息,方法名,方法参数等信息。当第一次运行时,要根据字符串的内容,到该类的方法表中搜索这个方法。运行一次之后,符号引用会被替换为直接引用,下次就不用搜索了。直接引用就是偏移量,通过偏移量虚拟机可以直接在该类的内存区域中找到方法字节码的起始位置。
直接引用:
(1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
(3)一个能间接定位到目标的句柄
符号引用和直接引用的解释:
https://www.zhihu.com/question/30300585
https://blog.csdn.net/kkdelta/article/details/17752097
启动(BootStrap)类加载器:
加载JVM自身需要的类,是虚拟机自身的一部分,会将JAVA_HOME/lib下的核心类库或-Xbootclasspath参数指定路径下的jar包加载到内存中(出于安全考虑,bootstrap类加载器只加载包名为java、javax、sun等开头的类)。
扩展(Extension)类加载器:
Sun公司实现的sum.misc.Luncher$ExtClassLoader类,是Luncher的静态内部类,负责加载JAVA_HOME/lib/ext下或由系统变量-Djava.ext.dir指定路径中的类库,开发者可以直接使用标准扩展类加载器。
系统(System)类加载器:
也称应用程序加载器,指sun公司实现的sum.misc.Launcher$AppClassLoader。负责加载系统类路径java –classpath或 –Djava.class.path下的类库。一般情况下该类加载器是程序默认的类加载器,可以通过ClassLoader#getSystemClassLoader()方法获取到该类加载器。
双亲委派模式加载:
原理说明:一个类加载器收到了类加载请求,不会自己先加载,而是把这个请求委派给父类加载器去执行,如果父类还有父类则继续向上委托,最终达到顶层的启动类加载器,从启动类加载器开始进行加载,如果成功则返回,否则子加载器才会尝试自己加载。
好处:
①防止重复加载:如果父加载器已经加载了此类子加载器就没必要再加载了
②安全考虑:如果运行时要加载一个java.lang.Integer类,会传递到启动类加载器,而启动类加载器发现在核心java API中有这个类已被加载,就直接返回已经加载了的Integer.class。再例如:要加载一个java.lang.FakeInteger,启动,扩展类加载器的路径下都没有该类,不会加载,会反向委托给系统类加载器加载,但是这样是不行的,因为java.lang是核心API包,需要访问权限,强制加载会报如下异常:
java.lang.SecurityException: Prohibited package name: java.lang
类加载器常见类:
通过上面的报错堆栈,可以看出几个ClassLoader相关类的关系如下:
①ClassLoader
抽象类,定义了几个基本的类加载相关方法。
②SecureClassLoader
扩展了ClassLoader,新增了几个与使用相关的代码源和权限定义类验证。
③URLClassLoader
实现了ClassLoader没实现的几个方法,如findClass 等,有一个相关类URLClassPath负责获取Class字节码流,一般自定义类加载器继承URLClassLoader即可。这两个类的构造方法都有一个必填参数URL,该参数是一个路径,可能是文件或jar包,然后根据不同路径创建FileLoader或JarLoader或默认的Loader去加载相应路径下的class文件。
类加载器常用方法:
①loadClass
②findClass
③defineClass
将byte字节流解析成JVM能够识别的Class对象,一般defineClass()方法通常与findClass()方法一起使用,一般在自定义类加载器时会覆盖ClassLoader类的findClass方法并编写加载规则,取的要加载类的字节码后转换成流,然后调用defineClass方法成成类的Class对象。
④resolveClass(Class c)
对Class对象进行解析。
ExtClassLoader和AppClassLoader
他们都继承自URLClassLoader,是sun.misc.Launcher的静态内部类。类结构如下:
类加载器关系:
①启动类加载器:由C++实现,没有父加载器
②扩展类加载器(ExtClassLoader)由Java实现,父加载器为null
③系统类加载器(AppClassLoader),父加载器为ExtClassLoader
④自定义类加载器,父加载器为AppClassLoader
我们实现的自定义类加载器的父加载器都是AppClassLoader。Launcher类的源码如下:
如上:Launcher初始化时会创建ExtClassLoader,然后把ExtClassLoader作为parent入参构造AppClassLoader,然后将AppClassLoader默认设置为线程上下文加载器。而ExtClassLoader的parent为null,如下代码:
类与类加载器
JVM中两个class对象是否是统一个类对象的必要条件为:
①类的完整类名必须一致,包括包名
②加载这个类的ClassLoader必须相同
实现自己的ClassLoader
public class MyClassLoader extends ClassLoader
{
private String classPath; public MyClassLoader(){ super(); } public MyClassLoader(String classPath){ this.classPath = classPath; } public static void main(String[] args) { try { String path = "F:\\Learn\\Java\\workspace\\spring\\MyClassLoader\\bin"; MyClassLoader mcl1 = new MyClassLoader(path); // 自定义类加载器实例1 Class clazz1 = mcl1.findClass("com.classloader.MyClassLoader"); // 自定义类加载器加载自身类 MyClassLoader mcl2 = new MyClassLoader(path); // 自定义类加载器实例2 Class clazz2 = mcl2.findClass("com.classloader.MyClassLoader"); // 自定义类加载器加载自身类 Object inst = clazz1.newInstance(); //new一个自身类的实例 System.out.println(inst.toString()); System.out.println(mcl1.getClass().hashCode());//系统默认加载器加载的class类的实例是一样的 System.out.println(mcl2.getClass().hashCode());//系统默认加载器加载的class类的实例是一样的 System.out.println(clazz1.hashCode());//自定义加载器两次加载的class实例是不一样的(使用findClass) System.out.println(clazz2.hashCode());//自定义加载器两次加载的class实例是不一样的(使用findClass) System.out.println(mcl1.loadClass("com.classloader.MyClassLoader").hashCode());//loadClass和上面findClass的class对象实例是一样,因为loadClass时会先findClass,检查类是否已经被加载 System.out.println(mcl2.loadClass("com.classloader.MyClassLoader").hashCode());//loadClass和上面findClass的class对象实例是一样,因为loadClass时会先findClass,检查类是否已经被加载 System.out.println(mcl1.getSystemClassLoader());//系统类加载器是AppClassLoader System.out.println(mcl1.getParent());//自定义类加载器的父加载器是AppClassLoader System.out.println(mcl1.getSystemClassLoader().getParent());//系统类加载器的父加载器是ExtClassLoader System.out.println(mcl1.getSystemClassLoader().getParent().getParent());//ExtClassLoader的父加载器是null } catch (Exception e) { e.printStackTrace(); } } @Override public String toString() { return "MyClassLoader"; } public Class<?> findClass(String name) throws ClassNotFoundException { // 获取class文件的byte[]数组 try { byte[] classBytes = findClassBytes(name); if(null == classBytes){ throw new ClassNotFoundException(); } return defineClass(name,classBytes,0,classBytes.length); } catch (FileNotFoundException e) { e.printStackTrace(); } return null; } public byte[] findClassBytes(String className) throws FileNotFoundException { String path = this.classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; InputStream is = null; if(this.classPath.startsWith("http")){ try { /*URL u = new URL(path); is = u.openStream();*/ } catch (Exception e) { e.printStackTrace(); } }else{ is = new FileInputStream(path); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] bs = new byte[1024]; int bytesNum = 0; try { while((bytesNum = is.read(bs)) != -1){ baos.write(bs,0,bytesNum); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } finally{ try { is.close(); baos.close(); } catch (IOException e) { e.printStackTrace(); } } return null; }
}
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java基础巩固-了解Java中的反射机制
最近有空的时候会看下jdk和spring的源码,发现反射的使用是非常频繁的。之前也对反射或多或少有过了解,但也只是停留在了解的阶段,总结一下来加深自己的印象。 反射的基本概念:程序可以访问、检测和修改其本身状态或行为的一种能力。 反射机制是java的特性之一,指的是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。(摘自百度) 反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。例如它允许一个java的类获取它所有的成员变量和方法并且显示出来(官方概念) 常见应用场景 1.各种框架中,比如spring的IOC(控制反转) spring ioc的思想是将设计好的对象交给容器控制,帮我们实例化,其中就有用到反射来实现。 大致步骤(伪代码) ①spring配置好bean <bean id="courseDao" class="com.qcjy.learning.Dao.impl.CourseDaoIm...
- 下一篇
redis集群(redis + cluster + sentinel)
概述说明 说明:本次实验采用c1、c2、c3三台虚拟机完成,每台服务器上都部署一个master、一个slave和一个sentinel。当某主节点的挂了,相应的从节点替位;当某主节点及主节点对应的从节点同时挂了,将造成数据的丢失!故生产环境都采用一主多从的集群模式! 搭建环境 服务器信息如下: c1 192.168.10.11 c2 192.168.10.12 c3 192.168.10.13 每台需要部署redis的服务器上配置系统参数,执行以下脚本 # cat xitongcanshu.sh #!/bin/bash echo 'net.core.somaxconn=512' >> /etc/sysctl.conf echo 'vm.overcommit_memory=1' >> /etc/sysctl.conf echo never > /sys/kernel/mm/transparent_hugepage/enabled echo 'echo never > /sys/kernel/mm/transparent_hugepage/enable...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Linux系统CentOS6、CentOS7手动修改IP地址
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS6,CentOS7官方镜像安装Oracle11G
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作