1.Java 读取配置文件
2.描述一下JVM加载class文件 的原理机制?
3.Java中类的加载顺序详细分析(ClassLoader)
4.面试官问:Java中Class.forName和ClassLoader到底有啥区别?
5.java中classloader的defineclass()方法是如何实现的?
6.java ContextClassLoader (线程上下文类加载器)
Java 读取配置文件
Java中,我们可以利用`java.lang.Class`和`java.lang.ClassLoader`来读取配置文件,两种方式都提供了`getResource()`和`getResourceAsStream()`方法。下面分别介绍这两种方法的使用:
首先,通过`java.lang.Class`获取文件路径:
1. `xxx.class.getResource("").getPath`: 这会返回编译后的xxx.class文件的路径。
2. `xxx.class.getResource("fileName").getPath`: 如果"fileName"不是移动医生app源码以"/"开头,它会作为相对路径被解析;如果以"/"开头,就视为绝对路径。
其次,使用`java.lang.ClassLoader`的方式:
1. `xxx.class.getClassLoader().getResource("").getPath`: 这个方法获取的是src资源文件编译后的classes路径。
2. `xxx.class.getClassLoader().getResource("fileName").getPath`: 用于获取classes路径下指定文件的路径,"fileName"同样不能以"/"开头。
下面是相应的代码示例:
使用java.lang.Class:获取xxx.class文件路径: xxx.class.getResource("").getPath
根据相对或绝对路径读取文件: xxx.class.getResource("fileName").getPath
使用java.lang.ClassLoader:获取classes路径: xxx.class.getClassLoader().getResource("").getPath
获取classes目录下的文件路径: xxx.class.getClassLoader().getResource("fileName").getPath
请注意,使用`ClassLoader`时,文件名应避免以"/"开头。
描述一下JVM加载class文件 的原理机制?
Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是keras损失函数源码反射,就需要显式的加载所需要的类。
类装载方式,有两种
1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
2.显式装载, 通过class.forname()等方法,显式加载需要的类
隐式加载与显式加载的区别:两者本质是一样?
Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。
Java的类加载器有三个,对应Java的三种类:(java中的类大致分为三种: 1.系统类 2.扩展类 3.由程序员自定义的类 )
Bootstrap Loader // 负责加载系统类 (指的是内置类,像是String,对应于C#中的System类和C/C++标准库中的类)
|
- - ExtClassLoader // 负责加载扩展类(就是继承类和实现类)
|
- - AppClassLoader // 负责加载应用类(程序员自定义的类)
三个加载器各自完成自己的工作,但它们是如何协调工作呢?哪一个类该由哪个类加载器完成呢?为了解决这个问题,Java采用了委托模型机制。
委托模型机制的工作原理很简单:当类加载器需要加载类的时候,先请示其Parent(即上一层加载器)在其搜索路径载入,如果找不到,才在自己的通达信 源码搜索路径搜索该类。这样的顺序其实就是加载器层次上自顶而下的搜索,因为加载器必须保证基础类的加载。之所以是这种机制,还有一个安全上的考虑:如果某人将一个恶意的基础类加载到jvm,委托模型机制会搜索其父类加载器,显然是不可能找到的,自然就不会将该类加载进来。
我们可以通过这样的代码来获取类加载器:
ClassLoader loader = ClassName.class.getClassLoader();
ClassLoader ParentLoader = loader.getParent();
注意一个很重要的问题,就是Java在逻辑上并不存在BootstrapKLoader的实体!因为它是用C++编写的,所以打印其内容将会得到null。
前面是对类加载器的简单介绍,它的原理机制非常简单,就是下面几个步骤:
1.装载:查找和导入class文件;
2.连接:
(1)检查:检查载入的class文件数据的正确性;
(2)准备:为类的静态变量分配存储空间;
(3)解析:将符号引用转换成直接引用(这一步是可选的)
3.初始化:初始化静态变量,静态代码块。
这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类的构造器也会引发该动作。
Java中类的加载顺序详细分析(ClassLoader)
Java类的加载顺序,主要由类加载过程、链接阶段、初始化阶段以及加载器层级决定。类加载过程分为加载、极简汇率 源码链接和初始化三个阶段,其中加载阶段通过类的全限定名获取二进制字节流,将其转化为方法区的运行时数据结构,并在Java堆中生成Class对象作为入口。链接阶段包含验证、准备和解析三个步骤,分别确保类的正确性、为静态变量分配内存并初始化为默认值,以及将符号引用转换为直接引用。初始化阶段在类被创建实例、访问静态变量或方法、调用静态方法、反射加载类或类子类初始化时触发,分为静态变量和静态初始化块、变量和初始化块,最后执行构造器。
类的加载最终产品为堆区中的Class对象,提供方法访问方法区内的数据。加载类的方式包括从本地系统、网络、归档文件、数据库中或动态编译源文件。类加载由ClassLoader及其子类执行,源码加了授权ip类的层次关系和加载顺序由图示表示。BootstrapClassLoader负责核心类加载,ExtensionClassLoader加载扩展功能类,AppClassLoader处理类path指定的类,CustomClassLoader是根据应用需求自定义的加载器。
类加载器顺序遵循自底向上检查已加载类,保证唯一性,自顶向下尝试加载类。BootstrapLoader为最顶层,无父加载器。类的继承关系决定了加载顺序,如C继承B,B继承A,C依赖D,则创建C时自动加载B和D,B加载A。所有变量初始化完毕后执行构造方法,静态成员的初始化优先于普通成员。类对象创建和静态块访问触发类加载。
类构造方法顺序通过代码示例展示,强调静态成员类优先加载,成员初始化后执行构造方法,静态成员初始化与静态块执行发生在类加载阶段。总结类加载顺序的关键点,包含基类优先加载、静态成员优先初始化、构造方法在成员初始化后执行、静态成员初始化与静态块执行同步于类加载。
参考博文:blog.csdn.net/eff/ar...
补充类构造方法顺序示例:
执行结果:
因此,结论是:
参考博文:cnblogs.com/xing/...
面试官问:Java中Class.forName和ClassLoader到底有啥区别?
面试官提问:在Java中Class.forName()与ClassLoader在加载类时有何不同?本文将详细解析。
Class.forName()与ClassLoader均能加载类至Java虚拟机中,其工作原理遵循双亲委派模型,最终调用启动类加载器实现“通过类的全限定名获取描述此类的二进制字节流”的功能。
Class.forName()调用实际亦通过ClassLoader完成,方法原型为Class.forName(String className);在源码中,对参数initialize的默认设置为true,这意示着加载类后将执行类中的静态代码块与静态变量赋值等初始化操作。
此外,Class.forName(String name, boolean initialize, ClassLoader loader)方法允许手动选择在加载类时是否进行初始化。
以含有静态代码块、静态变量、赋值给静态变量的静态方法的类为例,使用Class.forName()与ClassLoader加载类的结果存在显著差异。Class.forName加载类时执行了类的初始化,而ClassLoader的loadClass操作仅加载类至虚拟机中,并未执行初始化。
具体应用场景包括Spring框架中的IOC实现与JDBC中加载数据库连接驱动。在Spring框架中,使用ClassLoader加载类以实现依赖注入,而JDBC规范要求Driver类向DriverManager注册,此过程通常在类加载时执行。
以MySQL驱动为例,其注册操作写在静态代码块中,解释了为何在编写JDBC代码时使用Class.forName()的原因。
总结:面试中常遇到有关Java类加载机制的问题,本文通过解析Class.forName()与ClassLoader的区别,为理解类加载机制提供了清晰视角。在实际开发中,理解并恰当使用类加载机制有助于构建高效、灵活的系统。
java中classloader的defineclass()方法是如何实现的?
在Java中,ClassLoader的defineClass()方法用于将字节码定义为类。此方法允许外部提供类的字节码,以便在Java虚拟机(JVM)中加载并执行它们。此功能尤其适用于从网络或其他外部源加载类,而不必依赖于预先存在的类文件。
该方法通过JNI(Java Native Interface)实现,使用C/C++语言编写。它通过将字节码转换为类,使得类可以在JVM环境中运行。要深入了解其内部实现,可以查阅JVM的源代码,但这里不再赘述。
值得注意的是,对于同一类名,可以多次提供不同的字节码。这使得实现类似反射的功能成为可能。与Java内置反射机制相比,这种方法在某些情况下能提供倍的调用效率。不过,这种高效实现主要针对动态语言功能的需求,而反射的使用场景和效率可能在其他情况下有所不同。
此外,如果类名未知,defineClass()方法允许传入null。这意呀着JVM在加载所有字节码后设置类名,这可能会增加额外的步骤和资源消耗。然而,考虑到可能出现字节码中不包含类名的情况,提供这种实现方式是合理的,但在这种情况下必须明确指定类名。
java ContextClassLoader (线程上下文类加载器)
每个ClassLoader只能加载自己所绑定目录下的资源。
加载资源时有四种选择:系统类加载器、当前ClassLoader、线程上下文类加载器和自定义类加载器。
Bootstrap ClassLoader、ExtClassLoader、AppClassLoader是真实存在的类,遵循“双亲委托”机制。而ContextClassLoader概念存在,只是一个成员变量,通过设置和获取方法操作。
线程上下文类加载器产生原因是解决Java服务提供者接口(SPI)中引导类加载器无法找到SPI实现类的问题。双亲委派模型无法解决此问题。
ContextClassLoader用于破坏类加载委托机制,让程序可以逆向使用类加载器。SPI的接口由引导类加载器加载,实现类由系统类加载器加载,ContextClassLoader帮助逆向访问实现类。
线程上下文类加载器自JDK 1.2引入,通过Thread的getContextClassLoader()和setContextClassLoader()方法获取和设置。
默认情况下,线程继承父线程的Context ClassLoader。如果没有设置,所有线程将使用System ClassLoader作为上下文类加载器。
在线程中运行的代码可以使用此类加载器加载类和资源。经典结构为获取-使用-还原。
在SPI实现中,JNDI、JDBC通过使用线程的Context ClassLoader获取当前线程的ClassLoader(即appClassLoader),加载所需实现类。
Tomcat则使用共享ClassLoader和webappClassLoader,前者用于加载容器类,后者用于加载webapp类,避免webapp之间的相互访问。
Java Servlet规范推荐此代理模式,确保Web应用类优先于容器提供的类。Java核心库类不在查找范围,以保证类型安全。