类加载过程
加载
1.通过类名来获取定义此类的二进制字节流(这个可以通过自定义类加载器,来指定加载类的来源,字节码有可能放在数据库、甚至云端。字节码通过网络传输为了安全性对字节进行加密,自定义类加载器可以读取、解密和验证)
2.将字节流所代表的静态存储结构转换为方法区的运行时数据结构
3.在java堆中生成一个代表这个类的class对象,做为方法去这些数据的访问入口。
验证
确保class文件的字节流中包含的信息符合虚拟机的要求,并且不会危害虚拟机自身安全。
1.文件格式验证
2.元数据验证 final
3.字节码验证
4.符号引用验证
准备
正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配
解析
虚拟机常量池的符号引用替换为直接引用的过程。
初始化
下面几个情况会触发初始化
1.new、getstatic、putstatic或invokestatic指令
2.使用java.lang.reflect包的方法对类进行反射调用的时候
3.初始化子类如果父类还未初始化
4.jvm启动时用户指定的入口类(定义main方法的类)
类加载模式
类加载分为Bootstrap类加载器、Extend类加载器、App类加载器、自定义类加载器。 双亲委派模式指看当前类加载有没有加载类,如果加载直接返回,如果没有委托父类加载加载类。简单的说查找类是否加载时自下而上的,加载类的顺序是自顶向下的。
ClassLoader.java 类加载源码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
双亲委派模型的好处:
1.主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String
2.同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不同的 ClassLoader加载就是不同的两个类。
自定义类加载使用场景
1.加密:java代码可以轻易被反编译,如果你需要把自己的代码进行加密以防止反编译,可以先将编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,这时就需要自定义ClassLoader在加载类的时候先解密类,然后再加载。
2.从非标准的来源加载代码:如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,从指定的来源加载类。
3.以上两种情况在实际中的综合运用:比如你的应用需要通过网络来传输 Java 类的字节码,为了安全性,这些字节码经过了加密处理。这个时候你就需要自定义类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出在Java虚拟机中运行的类。
如何自己实现一个自定义类加载器
自定义类加载需要实现ClassLoader类,并且覆盖findClass方法,这个方法里需要实现根据类名获得二进制字节流,然后调用父类的defineClass方法将其转化为一个java.lang.Class
MyClassLoader.java源码
public class MyClassLoader extends ClassLoader
{
public MyClassLoader()
{
}
public MyClassLoader(ClassLoader parent)
{
super(parent);
}
protected Class<?> findClass(String name) throws ClassNotFoundException
{
File file = new File("D:/People.class");
try{
byte[] bytes = getClassBytes(file);
//defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
return c;
}
catch (Exception e)
{
e.printStackTrace();
}
return super.findClass(name);
}
private byte[] getClassBytes(File file) throws Exception
{
// 这里要读入.class的字节,因此要使用字节流
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel wbc = Channels.newChannel(baos);
ByteBuffer by = ByteBuffer.allocate(1024);
while (true){
int i = fc.read(by);
if (i == 0 || i == -1)
break;
by.flip();
wbc.write(by);
by.clear();
}
fis.close();
return baos.toByteArray();
}
}