一、类加载器
类加载机制:双亲委派机制,自下向上检查类是否已加载,自上向下加载类。
类的加载时机:在使用的时候就会加载。
常用的类加载器:
BootStrapClassLoader:启动类加载器,加载路径为/jre/lib包下如:rt.jar
ExtClassLoader:扩展类加载器,加载路径/jre/lib/ext包下jar
AppClassLoader:应用程序类加载器,加载路径为classpath,如我们自定义的类和引入的第三方jar。
加载顺序:BootStrapClassLoader> ExtClassLoader> AppClassLoader
public static void main(String[] args) {
//这三行代码在Launcher类中均有体现。
//获取bootstrap classloader的加载路径
System.out.println(System.getProperty("sun.boot.class.path"));
//获取ext classloader的加载路径
System.out.println(System.getProperty("java.ext.dirs"));
//获取app classloader的加载路径
System.out.println(System.getProperty("java.class.path"));
}
类加载器语义上的父子关系
ClassLoader中的parent属性:用于指定类加载器的父类加载器是谁,如图,在创建AppClassLoader时指定了ExtClassLoader为它的parent(父类加载器),创建ExClassLoader时没有指定父类加载器(null),则表示他的父类加载器是BootstrapClassLoader。这里说的父类并非继承关系,AppClassLoader和ExtClassLoader均继承了URLClassLoader。
双亲委派机制
findClass:当前类加载器 实际执行加载二进制流的具体行为方法。
可以从下而上加载类文件吗?不可以,一般来说rt.jar中某些文件会在初始化时就开始加载,如果从下而上加载,那么无法校验这些class是否已加载过,倒是jvm中可能存在同一包下一个类有两个类文件,导致jvm的不安全不稳定。
自定义类加载器
//自定义类加载器,加载非classpath下的类文件
class MyClassLoader extends ClassLoader {
private String FILE_LOCATION = "D://";
public MyClassLoader() {
//父加载器默认为AppClassLoader
}
@Override
protected Class<?> findClass(String name) {
try {
//name com.payment.service.impl.JDKProxy
InputStream inputStream = new FileInputStream(FILE_LOCATION
.concat(name.replaceAll("\\.", "/"))
.concat(".class"));
ByteArrayOutputStream byteArrayInputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int count;
while ((count = inputStream.read(bytes)) != -1) {
byteArrayInputStream.write(bytes, 0, count);
}
byte[] data = byteArrayInputStream.toByteArray();
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
try {
MyClassLoader classLoader = new MyClassLoader();
Class<?> aClass = classLoader.findClass("com.payment.service.impl.JDKProxy");
Object jdkProxy = aClass.newInstance();
Method method = aClass.getMethod("print");
method.invoke(jdkProxy);
//JDKProxy jdkProxy =(JDKProxy)aClass.newInstance();
//该操作JDKProxy会被加载两次,一次是MyClassLoader,一次是AppClassLoader,
//类型转换时会抛异常ClassCastException
} catch (Exception e) {
e.printStackTrace();
}
}
}
二、单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
懒汉式:类加载时便创建对象,如果创建对象会消耗大量资源,则应该延时加载该对象。
class Singleton {
//Singleton类加载时便创建Singleton对象,因为类只会加载一次,因此Singleton对象只会创建一次
private static final Singleton instance = new Singleton();
//构造方法私有,防止new对象
private Singleton() {
}
//提供全局唯一的访问点
public static Singleton getInstance() {
return instance;
}
}
懒汉式:延时加载对象,当真正使用对象时再创建。
class Singleton {
//volatile 防止指令重排,先完成初始化再赋值。
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
//第一次检测
if (instance == null) {
//加锁
synchronized (Singleton.class) {
//第二次检测
if (instance == null) {
//指令重排出现位置,如果赋值操作在Singleton实例化完成前,那么另一个线程
//得到的可能是一个未初始化完成的对象。
instance = new Singleton();
}
}
}
return instance;
}
}
内部类:类加载过程由类加载器加锁,从而保证线程安全。
class Singleton {
private Singleton() {
}
//虽然是static修饰,Singleton类加载的时候不会加载SingletonHolder类,
//只有使用到SingletonHolder类时才会加载。
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
//触发SingletonHolder类加载
return SingletonHolder.instance;
}
}