> Java 中的类加载器
Java自带加载器:Bootstrap ClassLoader,Extention ClassLoader,system class loader;自定义Java 中的类加载器 ClassLoader一般覆盖findClass()方法。
-- Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
1.引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
2.扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
3.系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
-- Java ClassLoader,Java语言系统自带有三个类加载器+自定义ClassLoader
1.Bootstrap ClassLoader 是Java类加载层次中最顶层的加载类,主要加载JDK核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。我们可以打开我的电脑,在上面的目录下查看,看看这些jar包是不是存在于这个目录。属于启动类加载器。
2.Extention ClassLoader 扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。
3.Appclass Loader称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。
4.自定义ClassLoader一般覆盖findClass()方法。
注意: 除了Java默认提供的三个ClassLoader之外,用户还可以根据需要定义自已的ClassLoader,而这些自定义的ClassLoader都必须继承自java.lang.ClassLoader类,也包括Java提供的另外二个ClassLoader(Extension ClassLoader和App ClassLoader)在内,但是Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。 当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。(责任链模式)
深入分析Java ClassLoader原理- http://blog.youkuaiyun.com/xyang81/article/details/7292380
Java加载Class文件的原理机制-http://blog.youkuaiyun.com/a1259109679/article/details/48085473
JavaJVM加载class文件的原理机制(提高篇)-http://www.cnblogs.com/Qian123/p/5707562.html
深入探讨 Java 类加载器-- https://www.ibm.com/developerworks/cn/java/j-lo-classloader/
ClassLoader 只管加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由Execution Engine 负责的。
Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。
- 类装载方式,有两种:
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,委托模型机制会搜索其父类加载器,显然是不可能找到的,自然就不会将该类加载进来。
- jvm原理机制
1.装载:查找和导入class文件;
2.连接:
(1) 检查:检查载入的class文件数据的正确性;
(2) 准备:为类的静态变量分配存储空间;
(3) 解析:将符号引用转换成直接引用(这一步是可选的)
3.初始化:初始化静态变量,静态代码块。
这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类的构造器也会引发该动作。
> Android ClassLoader类加载器
Android中类加载器有BootClassLoader,URLClassLoader,(PathClassLoader,DexClassLoader),BaseDexClassLoader等都最终继承自java.lang.ClassLoader(基类)。
和java虚拟机中不同的是BootClassLoader是ClassLoader内部类,由java代码实现而不是c++实现, BootClassLoader是Android平台上所有ClassLoader的最终parent,这个内部类是包内可见,所以我们没法使用。
PathClassLoader和DexClassLoader都继承自BaseDexClassLoader,其中的主要逻辑都是在BaseDexClassLoader完成的。
DexClassLoader 和 PathClassLoader, BaseDexClassLoader(基于类装载器设计“插件”框架)。在Android中,ClassLoader是一个抽象类,实际开发过程中,我们一般是使用其具体的子类DexClassLoader、PathClassLoader这些类加载器来加载类的,它们的不同之处是:
1.DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;
2.PathClassLoader可以加载/data/app目录下的apk,这也意味着,它只能加载已经安装的apk,只能加载dex文件
3.URLClassLoader :可以加载java中的jar,但是由于dalvik不能直接识别jar,所以此方法在android中无法使用,尽管还有这个类。
PathClassLoader classLoader = (PathClassLoader) getApplicationContext().getClassLoader();
Log.d("mytest", "classLoader : " + classLoader + "\n" +
"parent : " + classLoader.getParent() + "\n" +
"grandParent : " + classLoader.getParent().getParent() + "\n" +
"system classloader : " + ClassLoader.getSystemClassLoader() + "\n" +
"system parent : " + ClassLoader.getSystemClassLoader().getParent());
结果:classLoader : dalvik.system.PathClassLoader[dexPath=/data/app/com.gavin.demo2application-1.apk,libraryPath=/data/app-lib/com.gavin.demo2application-1]
parent : java.lang.BootClassLoader@41099128
grandParent : null
system classloader : dalvik.system.PathClassLoader[dexPath=.,libraryPath=null]
system parent : java.lang.BootClassLoader@41099128
-- Android中类加载器有BootClassLoader,URLClassLoader,
其实在BaseDexClassLoader里对".jar",".zip",".apk",".dex"后缀的文件最后都会生成一个对应的dex文件,所以最终处理的还是dex文件,而URLClassLoader并没有做类似的处理。
Android Runtime(缩写为ART),在Android 5.0及后续Android版本中作为正式的运行时库取代了以往的Dalvik虚拟机。ART能够把应用程序的字节码转换为机器码,是Android所使用的一种新的虚拟机。
-- Dalvik与ART虚拟机的不同:
Dalvik采用的是JIT技术,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率;
ART采用Ahead-of-time(AOT)技术,应用在第一次安装的时候,字节码就会预先编译成机器码,这个过程叫做预编译。ART同时也改善了性能、垃圾回收(Garbage Collection)、应用程序除错以及性能分析。但是请注意,运行时内存占用空间较少同样意味着编译二进制需要更高的存储。
-- Android 热补丁动态修复框架小结- http://blog.youkuaiyun.com/lmj623565791/article/details/49883661/
热修复入门:Android 中的 ClassLoader。ClassLoader 是个抽象类,其具体实现的子类有 BaseDexClassLoader 和SecureClassLoader。
SecureClassLoader 的子类是 URLClassLoader,其只能用来加载 jar 文件,这在 Android 的 Dalvik/ART 上没法使用的。
BaseDexClassLoader 的子类是 PathClassLoader和 DexClassLoader。
-- 类加载器的代理模式
public void testClassIdentity() {
String classDataRootPath = "C:\\workspace\\Classloader\\classData";
FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
String className = "com.example.Sample";
try {
Class<?> class1 = fscl1.loadClass(className);
Object obj1 = class1.newInstance();
Class<?> class2 = fscl2.loadClass(className);
Object obj2 = class2.newInstance();
Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
setSampleMethod.invoke(obj1, obj2);
} catch (Exception e) {
e.printStackTrace();
}
}
-- 2种加载类的方法:Class.forName。
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()用来加载 Apache Derby 数据库的驱动。
该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader)和 Class.forName(String className)。
-- 自定义类加载器, 文件系统类加载器
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);
}
}
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;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
}
OSGi 中的每个模块(bundle)都包含 Java 包和类。模块可以声明它所依赖的需要导入(import)的其它模块的 Java 包和类(通过 Import-Package),也可以声明导出(export)自己的包和类,供其它模块使用(通过 Export-Package)。也就是说需要能够隐藏和共享一个模块中的某些 Java 包和类。这是通过 OSGi 特有的类加载器机制来实现的。
遇到 ClassNotFoundException和 NoClassDefFoundError等异常的时候,应该检查抛出异常的类的类加载器和当前线程的上下文类加载器,从中可以发现问题的所在。在开发自己的类加载器的时候,需要注意与已有的类加载器组织结构的协调。
-- Java字节码存放在哪 如何加载?
JVM把文件读入内存,然后用ClassLoader来加载吗,或者应该ClassLoader本身就负责从文件加载到内存
public class PathClassLoader extends ClassLoader {
private File dir;
public PathClassLoader(String path) {
dir = new File(path);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if(dir != null) {
File clazzFile = new File(dir, name + ".class");
if(clazzFile.exists()) {
FileInputStream input = null;
try {
input = new FileInputStream(clazzFile);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while((len = input.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return defineClass(name, baos.toByteArray(), 0, baos.size());
} catch(Exception e) {
throw new ClassNotFoundException(name, e);
} finally {
if(input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return super.findClass(name);
}
}
Java虚拟机上Java代码是如何编译成字节码并执行的,了解JVM内部架构和在字节码执行期间不同内存区域之间的差异.
Java虚拟机是基于栈的架构。当一个方法包括初始化main方法执行,在栈上就会创建一个栈帧(frame),栈帧中存放着方法中的局部变量。局部变量数组(local veriable array)包含在方法执行期间用到的所有变量包括一个引用变量this,所有的方法参数和在方法体内定义的变量。对于类方法(比如:static方法)方法参数从0开始,然而,对于实例方法,第0个slot用来存放this。
内存从申请分配、到使用、再到最后的释放。
Java 的几乎所有内存对象都是在堆内存上分配(基本数据类型除外),然后由 GC ( garbage collection)负责自动回收不再使用的内存。 GC 在什么时候回收内存对象,什么样的内存对象会被 GC 认为是“不再使用”的?