讲解之前先引入几个问题:
1.jvm如何识别.class文件?
2.jvm如何加载.class文件里面的字节码?
3.jvm如何创建类、对象、方法、属性?
上一章中讲到,jvm是通过.class文件的二进制流转换成16进制,得到字符串cafebabe认为这是一个.class文件;若任意文件本身不是由javac生成的.class文件,即使更改文件名为.class后缀的文件,不能被认定为cafebabe(6f6b);
由上图可知类加载器是JVM的一部分,主要作用是将字节码加载进入执行引擎,以供执行。当调用ava.exe执行一个.class文件时,从而根据%JAVA_HOME%\jre\lib\i386\jvm.cfg配置来选择激活jvm,初始化工作完成之后便启动Bootstrap Loader(引导类)加载器,它由C++编写。JVM中另外两个内置类加载器是ExtClassLoader和AppClassLoader,它们定义在sun.misc.Launcher.class中,为内部类,且由Bootstrp Loader加载进入虚拟机。
启动(Bootstrap)类加载器:引导类装入器是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib 下面的类库加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
标准扩展(Extension)类加载器:扩展类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将 < Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
系统(System)类加载器:系统类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。
除了以上列举的三种类加载器,还有一种比较特殊的类型就是线程上下文类加载;
类加载器也是Java类,本身也要被类加载器加载,第一个类加载器不是java类,而是BootStrap。Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
1、类加载器的委派模型:假设AppClassLoader需要加载一个类,它会首先委托其父加载器ExtClassLoader来加载此类,ExtClassLoader也会递归性的委托其父加载器Bootstrap Loader来加载此类,如果Bootstrap Loader在sun.boot.class.path下找到被加载类时即加载,如果无法找到时再依次由子类加载器去加载。委派模型是针对Java安全而设计的,这也印证了Java语言的设计初衷:面向网络的编程语言。
2、由同一个类加载器所加载的类只能引用该加载器和其父加载器所加载的其他类。
两种方法可以在运行时动态加载.class文件:1) Class clazz = Class.forName("类名称"); 2) 自定义类加载器,然后调用loadClass(“类名称”)方法;
一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 .class 文件,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。
基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。
&:类是Class的实例,类加载器是抽象类ClassLoader的实例(除了启动类加载器BootStrap);
java.lang.ClassLoader
类的基本功能就是根据一个指定的类名称,找到或者生成其对应的字节码(.class文件),然后从这些字节代码中创建一个 java.lang.Class
类的一个实例。除此之外,ClassLoader
还负责加载 Java 应用所需的资源,如图像文件和配置文件等。ClassLoader 中与加载类相关的方法:
//加载指定名称(包括包名)的二进制类型,供用户调用的接口 public Class<?> loadClass(String name) throws ClassNotFoundException{ … } //加载指定名称(包括包名)的二进制类型,同时指定是否解析(但是这里的resolve参数不一定真正能达到解析的效果),供继承用 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ … } //findClass方法一般被loadClass方法调用去加载指定名称类,供继承用 protected Class<?> findClass(String name) throws ClassNotFoundException { … } //定义类型,一般在findClass方法中读取到对应字节码后调用,可以看出不可继承 //(说明:JVM已经实现了对应的具体功能,解析对应的字节码,产生对应的内部数据结构放置到方法区,所以无需覆写,直接调用就可以了) protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ … }
不同的类加载器为相同名称的类创建了不同的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。
类加载器 加载类的时候,首先会让父类加载器来加载这个类。这就意味着:完成类的加载工作的类加载器和启动这个加载过程的类加载器(&:加载一个类是分为两个步骤完成的),可能不是同一个。真正完成类的加载工作是通过调用 defineClass来实现的;而启动类的加载过程是通过调用 loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,是哪个类加载器启动了的加载过程并不重要,重要的是最终是那个类加载器定义了这个类。
方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是 java.lang.NoClassDefFoundError异常。
public static void main(String[] args) { try { System.out.println(ClassLoader.getSystemClassLoader()); System.out.println(ClassLoader.getSystemClassLoader().getParent()); System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent()); } catch (Exception e) { e.printStackTrace(); } } sun.misc.Launcher$AppClassLoader@6d06d69c sun.misc.Launcher$ExtClassLoader@70dea4e null
通过以上的代码输出,我们可以判定系统类加载器的父加载器是标准扩展类加载器,但是我们试图获取标准扩展类加载器的父类加载器时确得到了null,由于jvm对于父类加载器是引导类加载器的情况,getParent()
方法返回 null
。
我们首先看一下java.lang.ClassLoader抽象类中默认实现的两个构造函数(大楷意思,与源码不太一致):标准扩展类加载器和系统类加载器及其父类(java.net.URLClassLoader和java.security.SecureClassLoader)都没有覆写java.lang.ClassLoader中默认的加载委派规则---loadClass(…)方法。
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 首先判断该类型是否已经被加载 Class c = findLoadedClass(name); if (c == null) { //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载 try { if (parent != null) { //如果存在父类加载器,就委派给父类加载器加载 c = parent.loadClass(name, false); } else { //如果不存在父类加载器,就检查是否是由启动类加载器加载的类, //通过调用本地方法native findBootstrapClass0(String name) c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用。
动态加载类:
1.调用class.forName(String);
public static Class<?> forName(String className) throws ClassNotFoundException { return forName0(className, true, ClassLoader.getCallerClassLoader()); //ClassLoader }设置为true表示强制加载同时完成初始化。例如典型的就是利用DriverManager进行JDBC驱动程序类注册的问题。因为每一个JDBC驱动程序类的静态初始化方法都用DriverManager注册驱动程序,这样才能被应用程序使用。这就要求驱动程序类必须被初始化,而不单单被加载。Class.forName的一个很常见的用法就是在加载数据库驱动的时候。如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()用来加载 Apache Derby 数据库的驱动。
2.自定义类加载器
通过前面的分析,除了启动类加载器之外,标准扩展类加载器和系统类加载器都可以当做自定义类加载器,唯一区别是是否被虚拟机默认使用。自定义的类加载器必须符合以下要求:
继承ClassLoader类;
覆写findClass方法
package org.Smart; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class FileSystemClassLoader extends ClassLoader { private String rootDir; public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; } protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); //ClassLoader里的方法:创建对象 } } private byte[] getClassData(String className) { String path = classNameToPath(className); try { InputStream ins = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } String classNameToPath(String className) { StringBuffer s=new StringBuffer(rootDir+"/"+className.replace('.', '/')+".class"); return s.toString(); } } 测试: public static void main(String...s) throws ClassNotFoundException { @SuppressWarnings("rawtypes") Class app=new FileSystemClassLoader("E:/side1/Smart/target/classes").findClass("org.Smart.App"); System.out.println(app.getClassLoader()); //org.Smart.FileSystemClassLoader@3020ad System.out.println(App.class.getClassLoader()); //sun.misc.Launcher$AppClassLoader@15253d5 }自定义的类加载器测试成功!!
线程上下文类加载器(context class loader)
是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。
Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers包中。这些 SPI 的实现代码很可能是作为 Java 应用所依赖的 jar 包被包含进来,可以通过类路径(CLASSPATH)来找到,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。
Java默认的线程上下文类加载器是系统类加载器(AppClassLoader)。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。
后续:
http://blog.csdn.net/zhoudaxia/article/details/35897057
相关推荐
2. 加载`vgg19_int8.pb`模型,这通常涉及到解析模型结构和权重。 3. 准备图像数据,进行预处理,如调整尺寸、归一化等,使其符合VGG19模型的输入要求。 4. 使用模型进行前向传播,得到每个图像的特征表示。 5. 应用...
S19文件不仅包含二进制数据,还包含了地址信息、数据长度以及校验和,这使得它在编程过程中更加方便,因为加载器可以自动解析这些信息并将数据正确地写入内存。 "bin2srec.exe"是实现这个转换的核心程序,它是一个...
1. **隔离驱动**: 首先,确保为每个数据库版本创建独立的类加载器。这样可以避免一个版本的驱动覆盖另一个版本的类,导致错误。通常,我们可以创建一个子类继承`java.lang.ClassLoader`,并重写`loadClass()`方法来...
Bootloader是一种在操作系统加载之前运行的软件,负责初始化硬件并加载操作系统到内存中。理解S19文件及其处理工具对于进行设备固件更新或故障排除至关重要。 “摩托罗拉S19文件分离器”是一款专门设计用来解析S19...
Oracle 19C驱动包是针对Oracle数据库19c版本的Java数据库连接器(JDBC)驱动程序。这个驱动包包含两个重要的文件:ojdbc10.jar和ojdbc8.jar,它们是Java开发者用来在Java应用程序中与Oracle 19c数据库进行交互的关键...
3. 初始化权重:可能从预训练的VGG19模型加载权重,以便于迁移学习或者直接进行图像分类任务。 4. 编译模型:设置损失函数(如交叉熵)、优化器(如Adam)和评估指标(如准确率)。 5. 训练模型:加载数据集,执行...
通常,我们会使用预训练的VGG19模型,通过`keras.applications.vgg19`模块导入,该模块提供了加载预训练权重的功能。在CPU上运行模型可以避免GPU资源限制,但可能速度较慢。 `layers_1.py`和`layers_2.py`可能包含...
- 这个jar文件同样提供了对Oracle 19c特性的访问,但它是为Java 8的类加载器和兼容性而优化的。 - 和ojdbc10.jar一样,ojdbc8.jar也需要正确地引入到项目环境,以便Java代码能够通过JDBC接口与Oracle数据库交互。 ...
"Java 中类加载过程全面解析" Java 中类加载过程是 Java 语言中一个非常重要的机制,它负责将类文件加载到内存中,以便 JVM 可以使用这些类。类加载过程是一个复杂的过程,涉及到多个步骤和机制。下面将对 Java 中...
例如,在TensorFlow中,可以使用`tf.keras.applications.VGG19`模块加载并使用预训练模型,同时指定`include_top=False`以不加载全连接层。接着,可以将模型的输出传递给自定义的网络结构,进行微调或特征提取。 ...
对于那些想要在LabVIEW中进行微控制器编程的开发者来说,这是一个非常有价值的资源,可以学习如何在实际项目中处理这类文件。通过分析和运行这个示例,可以提高对S19格式的理解,以及在LabVIEW中进行嵌入式系统编程...
随着嵌入式系统的快速发展,单片机作为其核心组件之一,在工业控制、汽车电子、消费类电子产品等多个领域得到了广泛应用。为了保护知识产权,避免源代码被非法获取,很多厂家会选择直接提供编译后的.s19格式文件进行...
4. **训练循环**:通过数据加载器分批进行训练,更新模型参数。 5. **验证与调优**:在验证集上评估模型性能,根据结果调整超参数。 **vgg19.pkl与ctu_params_vgg19.json** 在提供的压缩包中,"vgg19.pkl"很可能是...
- **/dev/sda1/boot**:大小为 500MB,用于存放引导加载器(Bootloader)及内核启动所需的文件。 - **/dev/sda2/**:大小为 40GB,作为根分区,存放系统文件。 - **/dev/sda3/home**:大小为 250GB,用于存放用户的...
- **配置连接**:在Java代码中,你需要通过`Class.forName()`方法加载对应的驱动类,然后使用`DriverManager.getConnection()`方法创建数据库连接。连接字符串通常包含数据库的URL、用户名和密码。 - **JDBC URL...
VGG19模型可以理解为一个图像特征提取器,它可以捕捉到图像中的各种层次特征,从低级纹理信息到高级语义内容。在风格迁移过程中,我们通常将VGG19的中间层视为风格特征的代表,而最后一层或倒数第二层的激活值则代表...
在Keras和TensorFlow.keras中加载VGG19的预训练权重非常简单。首先,导入相应的库,然后创建模型实例,并加载权重: ```python from tensorflow.keras.applications.vgg19 import VGG19 from tensorflow.keras....
VGG19模型的输入需要按特定的方式预处理,可以使用`VGG19`类的`preprocess_input`方法: ```python img_path = 'path_to_your_image.jpg' img = image.load_img(img_path, target_size=(224, 224)) x = image.img_...
为了提供更流畅的浏览体验,Firefox 19 对JavaScript引擎进行了优化,提升了页面加载速度。此外,内存占用的优化也是这一版本的重点,通过改进垃圾回收机制,减少了浏览器运行时的内存消耗,使长时间使用更加稳定。 ...
标题中的“免费谷歌瓦片下载器(0-19级)”指的是一个工具或软件,它允许用户免费下载谷歌地图的瓦片数据。在地图领域,瓦片是一种将大型地图分割成小块图像的技术,通常用于提高网页加载速度和优化地图服务。0-19级...