1、启动类加载器:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分。2、所有其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且 全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。
1、启动类加载器:Bootstrap ClassLoader,跟上面相同,它负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。主要加载JVM自身工作所需的类,完全由JVM自己控制。没有父加载器,也没有子加载器,只是一个类加载工具。根类加载器从System.getProperty(“sun.boot.class.path”)所指定的目录中加载类库。2、扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由System.getProperty(“java.ext.dirs”)指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。3、应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(System.getProperty(“java.class.path”),就是我们常用的classpath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
1.在遇到 new、putstatic、getstatic、invokestatic 字节码指令时,如果类尚未初始化,则需要先触发初始化。2.对类进行反射调用时,如果类还没有初始化,则需要先触发初始化。3.初始化一个类时,如果其父类还没有初始化,则需要先初始化父类。4.虚拟机启动时,用于需要指定一个包含 main() 方法的主类,虚拟机会先初始化这个主类。5.当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类还没初始化,则需要先触发初始化。
1.通过子类引用父类的静态字段,不会导致子类初始化。对于静态字段,只有直接定义这个字段的类才会被初始化。2.通过数组定义来引用类,不会触发此类的初始化。3.常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。编译通过之后,常量存储到 NotInitialization 类的常量池中,NotInitialization 的 Class 文件中并没有 ConstClass 类的符号引用入口,这两个类在编译成 Class 之后就没有任何联系了。
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
使用双亲委派模型来组织类加载器之间的关系,有一个很明显的好处,就是Java类随着它的类加载器(说白了,就是它所在的目录)一起具备了一种带有优先级的层次关系,这对于保证Java程序的稳定运作很重要。例如,类java.lang.Object类存放在JDK\jre\lib下的rt.jar之中,因此无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载,这边保证了Object类在程序中的各种类加载器中都是同一个类。
定义类加载器:若有一个类加载器能成功加载Sample类,那么这个类加载器被称为定义类加载器。初始类加载器:所有能成功返回Class对象的引用的类加载器(包括定义类加载器,即包括定义类加载器和它下面的所有子加载器)都被称为初始类加载器。
每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的全限定名相同的两个类。在不同的命名空间中,有可能会出现类的全限定名相同的两个类。
由同一类加载器加载的属于相同包的类组成了运行时包。只有属于同一运行时包的类才能互相访问包可见(即默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。
①隐式加载:通过JVM自动加载需要的类到内存中②显式加载:通过写代码去调用ClassLoader类来加载 1)forName() 2)loadClass() 3)findClass()
①加载1、通过一个类的全限定名来获取其定义的二进制字节流。2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
1、文件格式的验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,该验证的主要目的是保证输入的字节流能正确地解析并存储于方法区之内。 经过该阶段的验证后,字节流才会进入内存的方法区中进行存储,后面的三个验证都是基于方法区的存储结构进行的。2、元数据验证:对类的元数据信息进行语义校验(其实就是对类中的各数据类型进行语法校验),保证不存在不符合Java语法规范的元数据信息。3、字节码验证:该阶段验证的主要工作是进行数据流和控制流分析,对类的方法体进行校验分析,以保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。4、符号引用验证:这是最后一个阶段的验证,它发生在虚拟机将符号引用转化为直接引用的时候(解析阶段中发生该转化,后面会有讲解),主要是对类自身以外的信息(常量池中的各种符号引用)进行匹配性的校验。
1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。3、如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。我们可以理解为static final常量在编译期就将其结果放入了调用它的类的常量池中。
jvm虚拟机参数:都以-XX:开始-XX:+ 表示开启option选项 ,-XX:- 表示关闭option选项,-XX:=,表示将option值设置为value例如:-XX:+TraceClassLoading有时候我们需要监控系统中哪些类被加载进来,什么样的类加载的比较频繁,什么样的类加载的比较少,可以使用这个参数来配置打印出程序执行过程中类的加载信息。
加载器和Class对象:在类加载器的内部实现中,用一个Java集合来存放所加载类的引用。另一方面,一个Class对象总是会引用它的类加载器。调用Class对象的getClassLoader()方法,就能获得它的类加载器。由此可见,Class实例和加载它的加载器之间为双向关联关系。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69984138/viewspace-2731586/ ,如需转载,请注明出处,否则将追究法律责任。