注意:方法区只有HotSpot虚拟机有
类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。当中的类加载器只负责class文件的加载,至于它是否可以运行,则由Execution Engine(执行引擎)决定。加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
举例来说类加载子系统就像是一个中央快递站,当快递被打包好(编译后)发送过来的时候,去进行接收,首先收到快递看是什么类型的快递(顺丰,邮政等),不同的快递由不同的人员去接收(不同的类由不同的加载器去加载),接收完成后要进行验证,看是不是有什么损坏(链接阶段----验证),当一切无误后为该快递贴上取货码(链接阶段----准备:初始化一些信息比如类变量),再然后查看快递所要去往的地方,由快递员去派送(链接阶段----解析:将常量池内的符号引用转换为直接引用的过程),到达目的快递站后交由本地快递站进行处理(进入到初始化阶段),快递可以由快递站直接送往顾客家里(类的被动使用),也可以由顾客主动来领(类的主动使用例如:创建类的实例,调用类的静态方法等).
当然在类的加载阶段还有双亲委派机制,在后面会提到.
public class Loader {
public static void main(String[] args) {
System.out.println("谢谢ClassLoader加载我....");
}
}
对于上面的代码他的加载过程是什么样呢?
链接分为三个子阶段:验证 -> 准备 -> 解析
比如说如果你查看java编译后的字节码文件就会发现,它们的开头都是CAFE BABE(很多人称之为咖啡宝贝),如果出现不合法的字节码文件,那么将会验证不通过。
举例来说 查看编译后的文件
编译前
package com.Demo; public class ClassInitTest { public static int num = 3; public static void main(String[] args) { System.out.println(ClassInitTest.num); } }
编译后
public com.Demo.ClassInitTest(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 //可以看到在初始化阶段默认赋了初值为0 1: invokespecial #1 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/Demo/ClassInitTest;
static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=1, locals=0, args_size=0 0: iconst_3 //在初始化阶段才赋值为了3 1: putstatic #3 // Field num:I 4: return LineNumberTable: line 4: 0 }
通过反编译class文件可以查看到符号引用
#1 = Methodref #6.#23 // java/lang/Object."<init>":()V #2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream; #3 = Fieldref #5.#26 // com/Demo/ClassInitTest.num:I #4 = Methodref #27.#28 // java/io/PrintStream.println:(I)V #5 = Class #29 // com/Demo/ClassInitTest #6 = Class #30 // java/lang/Object #7 = Utf8 num #8 = Utf8 I #9 = Utf8 <init> #10 = Utf8 ()V #11 = Utf8 Code #12 = Utf8 LineNumberTable #13 = Utf8 LocalVariableTable #14 = Utf8 this #15 = Utf8 Lcom/Demo/ClassInitTest; #16 = Utf8 main #17 = Utf8 ([Ljava/lang/String;)V #18 = Utf8 args #19 = Utf8 [Ljava/lang/String;
类的初始化时机有:
除了以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化,即不会执行初始化阶段(不会调用 clinit() 方法和 init() 方法)
clinit()方法
<clinit>()
的过程<clinit>()
方法中的指令按语句在源文件中出现的顺序执行<clinit>()
不同于类的构造器。(关联:构造器是虚拟机视角下的<init>()
)<clinit>()
执行前,父类的<clinit>()
已经执行完毕<clinit>()
方法在多线程下被同步加锁JVM严格来讲支持两种类型的类加载器 。分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)
从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
可以看到所有ClassLoader下的所有派生类都是属于自定义类加载器,包括扩展类加载器(Extension ClassLoader)以及系统类加载器(Application ClassLoader)
在程序中我们最常见的类加载器只有3个分别是ExtClassLoader,AppClassLoader,用户自定义加载器
启动类加载器(引导类加载器,Bootstrap ClassLoader)
扩展类加载器(Extension ClassLoader)
应用程序类加载器(也称为系统类加载器,AppClassLoader)
public class CustomClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] result = getClassFromCustomPath(name); if (result == null) { throw new FileNotFoundException(); } else { //defineClass和findClass搭配使用 return defineClass(name, result, 0, result.length); } } catch (FileNotFoundException e) { e.printStackTrace(); } throw new ClassNotFoundException(name); } //自定义流的获取方式 private byte[] getClassFromCustomPath(String name) { //从自定义路径中加载指定类:细节略 //如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。 return null; } public static void main(String[] args) { CustomClassLoader customClassLoader = new CustomClassLoader(); try { Class<?> clazz = Class.forName("One", true, customClassLoader); Object obj = clazz.newInstance(); System.out.println(obj.getClass().getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } }
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式
例如:
我们自己建立一个 java.lang.String 类,写上 static 代码块
package java.lang; public class String { static{ System.out.println("自定义的String类的静态代码块"); } }
在另外的程序中加载 String 类
public class StringTest { public static void main(String[] args) { java.lang.String str = new java.lang.String(); System.out.println("hello String"); } }
输出结果:
hello String
并没有打印自定义的String 类中的语句,所以系统加载的还是JDK 自带的 String 类.
把刚刚的类改一下
package java.lang; public class String { static{ System.out.println("自定义的String类的静态代码块"); } public static void main(String[] args) { System.out.println("hello String"); } }
由于双亲委派机制会一直找父类加载器,所以最后找到了Bootstrap ClassLoader(引导类加载器),Bootstrap ClassLoader找到的是 JDK 自带的 String 类,在那个String类中并没有相应的 main() 方法,所以就报了上面的错误。
如果要判断两个class对象是否相同,在JVM中表示两个class对象是否为同一个类存在两个必要条件:
对类加载器的引用
原文:https://www.cnblogs.com/niulongwei/p/14829915.html