你知道你的类是从什么地方加载来的吗?

 

 

你知道我们平常使用的JAVA类是怎么来的吗?

1. 类加载开篇

1.在java代码中,类的加载、连接与初始化过程都是在程序运行期间完成的;

2.提供了更大的灵活性,增加了更多的可能性;

3.类加载器是沙箱的第一道防线,保护代码不被恶意干扰,保护已验证的类库,代码放入有不同行为的保护域。

2. 类加载过程

1.加载:查找并加载类的二进制文件;

2.连接

验证 : 确保被加载类的准确性(运行时环境与编译时环境是否一致;是否符合规范的class文件)

准备:为类的静态变量分配内存,并将其初始化为默认值;

解析:把类中的符号引用转为直接引用;

3.初始化:为类的静态变量赋予正确的初始值;

3. 类的使用方式

我们对类的使用方式可以分为两种:主动使用 和 被动使用

所有的java虚拟机实现必须在每个类或接口被java程序 首次主动使用 时才初始化他们。

4. 类的加载

类的加载是指将 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class 对象 (虚拟机规范并未指出Class对象要放在哪里)用来封装类在方法区内的数据结构;

加载class文件的方式:

---从本地系统中直接加载

---通过网络下载.calss文件

---从zip,jar等归档文件中加载.class文件

---将java源文件动态编译为.class文件

类加载的最终产品是位于内存中的Class对象;

Class对象封装了类在方法区内的数据结构,并且向我们提供了访问方法区内数据结构的接口;

5. 类的初始化

主动使用(7种)

---创建类的实例

---访问某个类或接口的静态变量,或者对该静态变量赋值

---调用类的静态方法

---反射(如 Class.forName("com.test.ClassName"))

---初始化一个类的子类

---java虚拟机启动时被表明为启动类的类

---jdk1.7以后提供的动态语言支持,MethodHandle实例解析结果 REF_getstatic, REF_putstatic,REF_inoveStatic句柄对应的类没有初始化,则初始化。

6. 类加载器

虚拟机自带了加载器

---根类加载器(BootStrapClassLoader)c++实现,负责加载/lib下的类,我们常用的rt.jar就是根加载器所加载的。可以通过系统属性查看所加载的jar。

System.getProperty("sun.boot.class.path")
-----------------------------------------
C:\Program Files\Java\jdk1.8.0_162\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_162\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.8.0_162\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.8.0_162\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_162\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_162\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_162\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_162\jre\classes

---扩展类加载器(ExtensionClassLoader) Java实现,可以在java里获取,负责加载/lib/ext下的类。可以通过系统属性获取信息。


System.getProperty("java.ext.dirs")
-----------------------------------------
C:\Program Files\Java\jdk1.8.0_162\jre\lib\ext;
C:\windows\Sun\Java\lib\ext

---系统(应用)类加载器(AppClassLoader)java实现,可以加载classpath下的java类。可以通过系统属性获得。


System.getProperty("java.class.path")

用户自定义的类加载器

---java.lang.ClassLoader的子类

---用户可以指定类的加载方式

Spring中的类加载器

tomcat中的类加载器

获取ClassLoader的途径

---clazz.getClassLoader();

---Thread.currentThread.getContxtClassLoader();//线程上下文类加载器

---ClassLoader.getSystemClassLoader();

---DriverManager.getCallerClassLoader();

类加载器的命名空间

---每个类加载器都有自己的命名空间,命名空间由该类加载器及所有父加载器所加载的类组成。

---在同一个命名空间中,不会出现类的完整名字相同的两个类

---在不同的命名空间中,有可能出现类的完整名字相同的两个类。

7. 双亲委派以及破坏双亲委派

用户自定义

用户自定义

启动类加载器 bootstrap classloader

扩展类加载器 extension classloader

应用类加载器 APP classloader

自定义classloader

loader1

loader2



---根加载器/启动类加载器在 java程序中用 null表示。

双亲委派机制

在绝大多数场景下,类的加载都遵循双亲委派机制,当一个类将要被加载的时候,当前类的加载器总要委派父加载器去加载,父加载器加载不到了,才会由自己去加载。在ClassLoader中的loadClass方法中可以看到。


if (parent != null) {
   c = parent.loadClass(name, false);
} else {
   c = findBootstrapClassOrNull(name);
}

在一般的自定义类加载器中,jvm建议大家,去重写findClass方法,而非直接重写loadClass方法,等于我们的自定义类加载器在一般情况下也要遵守双亲委派机制。

破坏双亲委派

我们在自定义ClassLoader时,如果重写他的loadClass方法,就会对他原先的双亲委派机制进行破坏,我们可以通过自己扩展的方式去加载这个二进制流产生Class对象。

对于rt.jar已经加载过的类,他的实现又是第三方厂商实现的类,要怎么加载呢?这就是我们经常接触到的javaSPI机制(Service Provider Interface),Thread.currentThread().currentClassLoader()就是jdk给自己开的后门,对于这些个场景,会破坏自己的双亲委派机制,进行用线程上下文加载器进行加载。


例如java.sql.Driver,位于rt.jar下,由bootStrap加载器加载,就是一个典型的spi,会有不同的厂商去实现这个接口,而我们所引用的第三方实现包是位于classpath下的,当我们要加载时,使用双亲委派就不会起到作用了。所以他是通过线程上下文加载器去加载的。

ServiceClassLoader是现在对spi机制的一个通用的处理。他会找到所有classpath下"META-INF/services/"对将要加载的接口的全类路径名文件的查找,找到后将文件中的实现类通过线程上下文加载器全部加载。


// 规定前缀 service为要加载的class名字 loader为上下文加载器
private static final String PREFIX = "META-INF/services/";

String fullName = PREFIX + service.getName();

configs = loader.getResources(fullName);

c = Class.forName(cn, false, loader);
 

spi机制的扩展,springboot中的自动装配机制,我们会自定义starter也会通过扫描的方式,被springFactoryLoader加载到bean容器中。这种思想与jdk有异曲同工之妙。

8. 类加载器命名空间的应用

---类之间的可见性问题

由不同类加载器加载的同一个类所产生的对象,相互是不可见的,我们用下面的例子来印证一下:


ClassLoader loader1 = new MyClassLoader();
ClassLoader loader2 = new MyClassLoader();

Class clazz1 = loader1.loadClass("com.test.User");
Class clazz2 = loader2.loadClass("com.test.User");
User user1 = clazz1.newInstance();
Object user2 = clazz2.newInstance();

user1.setUser(user2);

由于user对象被不同的类加载器所加载,loader1和loader不在同一个命名空间,所以,两次加载出来的user对象之间是不可见的。所以这两个对象在发生强制类型转换的时候,会抛出异常。

---类与类之间的隔离性

我们所熟知的tomcat中,我们可以再tomcat中部署多个web应用,那么如果部署的多个应用中有类冲突,jar版本不一致等问题,tomcat又是怎么做到类与类的隔离的,这就是上述的类加载器的命名空间起到的作用。具体的tomcat类加载器实现在上下文的帮助下,也比较好理解,有兴趣的可以自行深入。

9. 先有类加载器还是先有加载类加载器的类

说到这里我们可能会有些疑惑了,既然我们的类是由类加载器所加载得来的,那么我们的类加载器又是被谁加载的呢。说到这里,上文中的boostrapClassLoader在我们java程序中为什么是null的原因就可以得到解释了,boostrapClassLoader是由c++编写,所以在程序启动中,会自动加载,而bootstrapClassloader又可以加载rt.jar。在rt.jar中会有一个比较关键的类会被他加载,如下是com.misc.Launcher类反编译的源码

 

public class Launcher {
 private static URLStreamHandlerFactory factory = new Factory((1)null);
 private static Launcher launcher = new Launcher();
 private static String bootClassPath = System.getProperty("sun.boot.class.path");
 private ClassLoader loader;
 private static URLStreamHandler fileHandler;
 
  public Launcher() {
  ExtClassLoader var1;
  try {
   var1 = ExtClassLoader.getExtClassLoader();
  } catch (IOException var10) {
   throw new InternalError("Could not create extension class loader", var10);
  }

  try {
   this.loader = AppClassLoader.getAppClassLoader(var1);
  } catch (IOException var9) {
   throw new InternalError("Could not create application class loader", var9);
  }

  Thread.currentThread().setContextClassLoader(this.loader);
  String var2 = System.getProperty("java.security.manager");
  if (var2 != null) {
   SecurityManager var3 = null;
   if (!"".equals(var2) && !"default".equals(var2)) {
    try {
     var3 = (SecurityManager) this.loader.loadClass(var2).newInstance();
    } catch (IllegalAccessException var5) {
     ;
    } catch (InstantiationException var6) {
     ;
    } catch (ClassNotFoundException var7) {
     ;
    } catch (ClassCastException var8) {
     ;
    }
   } else {
    var3 = new SecurityManager();
   }

   if (var3 == null) {
    throw new InternalError("Could not create SecurityManager: " + var2);
   }

   System.setSecurityManager(var3);
  }

 }
 }

 

launcher在初始化的时候,会加载ExtClassLoader和AppClassLoader,而且在加载AppClassLoader,会把上下文类加载器设置为AppClassLoader。所有先有类加载器和先有加载类加载器的问题在这里就得到了解释。

看到这里,一个类的前世今生想必大家心里都有一些深刻的过程了。

 

 

10. 一些关于类加载的参数

---想看到程序启动都加载了哪些类:-XX:+TraceClassLoading

---想看到程序执行会卸载哪些类 :-XX:+TraceClassUnloading

 

posted on 2019-10-23 10:20  闯入你笑颜  阅读(433)  评论(0编辑  收藏  举报

导航