面试题总结
其它面试题(springboot、mybatis、并发、java中高级面试总结等)
public byte[] loadClassData(String name) {
System.out.println(“abc”);
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
this.classLoaderName = name.replace(“.”, “/”);
String fileExtension = “.class”;
is = new FileInputStream(path.concat(classLoaderName).concat(fileExtension));
byteArrayOutputStream = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
byteArrayOutputStream.write(ch);
}
data = byteArrayOutputStream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
assert is != null;
is.close();
assert byteArrayOutputStream != null;
byteArrayOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return data;
}
@Override
protected Class<?> findClass(String name) {
byte[] data = loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
}
复制代码
测试一下,我删除了target目录下的MyTest.class,然后将MyTest.class移动到/Users/zhangxiaobin/Desktop这个目录下,findClass时系统类加载器没有加载到MyTest.class,自定义类加载器就能够加载到这个类了
public static void main(String[] args) throws Exception {
// 定义第一个类加载器
MyClassLoader myClassLoader = new MyClassLoader(“myClassLoader”, “/Users/zhangxiaobin/Desktop/”);
Class<?> clazz = myClassLoader.loadClass(“com.example.jvm.MyTest”);
Object object = clazz.newInstance();
System.out.println(object);
System.out.println(clazz.getClassLoader());
System.out.println(clazz.getClassLoader().getParent());
System.out.println(clazz.hashCode());
// 定义第二个类加载器
MyClassLoader myClassLoader2 = new MyClassLoader(“myClassLoader”, “/Users/zhangxiaobin/Desktop/”);
Class<?> clazz2 = myClassLoader2.loadClass(“com.example.jvm.MyTest”);
Object object2 = clazz2.newInstance();
System.out.println(object2);
System.out.println(clazz.getClassLoader());
System.out.println(clazz.getClassLoader().getParent());
System.out.println(clazz2.hashCode());
}
复制代码
测试一下,发现自定义了两个类加载器,同一个类被加载了两次,这是因为类加载器有一个命名空间的问题,每个类加载器都有自己的命名空间,在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类,在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
将target目录下的MyTest.class弄回来,会发现该类是使用了系统类加载器来加载的
类加载器命名空间的一些问题
-
同一个命名空间内的类是相互可见的
-
子加载器的命名空间包含所有父加载器的命名空间,因此由子加载器加载的类能看到父加载器加载的类,父加载器加载的类不能看到子加载器加载的类
-
如果两个加载器之间没有直接或间接的父子关系,那么他们各自加载的类相互不可见
SPI打破双亲委派机制
在类加载器命名空间的限制下,双亲委派机制在某些场景下无法满足我们的需求,比如SPI机制
Java核心类库定义了接口,并未给出实现,这些接口的实现来自不同的jar包(厂商),比如JDBC,Java核心类库定义了Connection等接口,不同的厂商有不同的实现,MySQL、Oracle等等,这些实现是通过jar包的方式加载的,jar包位于ClassPath下。Java核心类库是由启动类加载器加载的,ClassPath下的jar包是由系统类加载器加载的,按照命名空间的规则,他们是不可见的
办法总比问题多,jdk在双亲委派机制的基础上,新增了线程上下文类加载器,通过给当前线程设置线程上下文类加载器的方式来实现对于接口实现类的加载
这个线程上下文类加载器一般是系统类加载器
// 具体设置的代码在Launcher这个类中
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
// 获取扩展类加载器
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
“Could not create extension class loader”, e);
}
// Now create the class loader to use to launch the application
try {
// 获取系统类加载器
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
“Could not create application class loader”, e);
}
// 将线程上下文类加载器设置为系统类加载器,此加载器是可以替换的
Thread.currentThread().setContextClassLoader(loader);
…
}
复制代码
SPI机制是通过ServiceLoader这个类进行实现类的加载
public static ServiceLoader load(Class service) {
// 可以看到,获取了线程上下文类加载器来加载类
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
复制代码
示意图如下
链接
链接分为三步,分别是验证、准备、解析
验证:确保Class文件中包含的信息符合Java虚拟机的规范
最后总结
ActiveMQ+Kafka+RabbitMQ学习笔记PDF
关于分布式,限流+缓存+缓存,这三大技术(包含:ZooKeeper+Nginx+MongoDB+memcached+Redis+ActiveMQ+Kafka+RabbitMQ)等等。这些相关的面试也好,还有手写以及学习的笔记PDF,都是啃透分布式技术必不可少的宝藏。以上的每一个专题每一个小分类都有相关的介绍,并且小编也已经将其整理成PDF啦
mcached+Redis+ActiveMQ+Kafka+RabbitMQ)等等。这些相关的面试也好,还有手写以及学习的笔记PDF,都是啃透分布式技术必不可少的宝藏。以上的每一个专题每一个小分类都有相关的介绍,并且小编也已经将其整理成PDF啦