- loading 加载
- linking 连接
- verification 校验文件符合jvm规范
- preparetion 准备(静态成员变量赋默认值)
- resolution 解析 (将类,方法,属性等符号引用解析为直接引用)
- initializing 初始化(调用类的
,给静态成员变量赋值)---线程安全(可以实现单列模式)
- 启动类加载器
- 使用c/c++实现嵌套在jvm内部
- 加载java核心库
- 只加载包名为java,javax,sun开头的类
- 扩展类加载器
- java语言,sun.misc.Launcher.$ExtClassLoader实现
- 父类加载器为启动类加载器
- 加载java.ext.dirs/jre/lib/ext指定的目录
- 应用程序类加载器
- java语言,sun.misc.Launcher.$AppClassLoader实现
- 父类加载器为扩展类加载器
- 加载环境变量classpath或者系统属性java.class.path指定路径下的类
- 用户自定义类加载器的默认父加载器
双亲委派模型获取当前class的classloader xxx.class.getClassLoader()
从jdk1.2开始类的加载过程采用双亲委派机制,该机制规定了加载顺序是,引导类加载器先加载,如果加载不了,由扩展类加载器加载,若还加载不了,才会是系统类加载器或者在自定义类加载器进行加载。
Java代码验证:ClassLoader.loadClass方法
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)避免类的重复加载,确保一个类的全局唯一性
(2)保护程序安全,防止核心API被随意篡改
劣势顶层的ClassLoader无法访问底层的ClassLoader所加载的类,比如Tomcat中,类加载器采用先自己加载,如果加载失败,再委派给父类加载器加载,servlet规范推荐使用该做法。
自定义类加载器类加载范围:参考Launcher里面定义加载范围,通过获取系统属性确定加载范围。
- 继承classloader
- 重新模板方法findclass
- 调用defineClass
- 自定义类加载器加载自己加密过的class
- 防止反编译
public class TestClassLoader extends ClassLoader{
private int seed=11055544;
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
try {
FileInputStream fileInputStream = new FileInputStream(new File(name));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int i=0;
while ((i=fileInputStream.read())!=0){
byteArrayOutputStream.write(i^seed);//加密
}
byte[] bytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
fileInputStream.close();
return defineClass(name,bytes,0,bytes.length);
}catch (Exception e){
e.printStackTrace();
}
return super.findClass(name);
}
public static void main(String[] args) throws ClassNotFoundException {
TestClassLoader testClassLoader = new TestClassLoader();
Class> aClass = testClassLoader.loadClass("c://zczhang//app.java");
}
}
打破双亲委派模型
- jdk1.2之前,自定义的classloader必须重写loadclass()方法,所以可以打破;
- 自定义类加载器,重写loadClass方法;
- 使用线程上下文类加载器;
- 热部署,热加载(因为源码里面默认缓存里面找,如果有就不加载了)
- osgi tomact都自己的模块指定classloader
- 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没 有进行过初始化,则需要先触发其初始化。
- 使用java.lang.reflect包的方法对类进行发射调用的时候
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发 其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个 类),虚拟机会先初始化这个主类。
- 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
- 当使用 JDK1.7 的动态方法调用时,如果一个 MethodHandler 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个句柄没有初始化,则需要先触发类初始化。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)