JMM—— 类加载

类加载的时机:
在类加载时,如果类没有初始化,需要先触发其初始化。
下面四种情况要初始化类:
(1)创建类的实例时(new 的方式)。访问某个类或者接口的静态变量,或者对该静态变量赋值,调用类的的静态方法。(类。xxx调用)
(2)使用反射的方式时
(3)java虚拟机启动时被标记为启动类的类,直接使用java.exe命令来运行某个主类(包含main方法这个类)
(4)初始化摸个类的子类,其父类也会被初始化。
(类只被初始化一次,如果要进行初始化,这个类一定被要加载)

类的生命周期:
类加载的全过程:加载、验证、准备、解析、初始化
类的生命周期:7阶段
JMM—— 类加载_第1张图片
(了解)
加载
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证:
这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求, 并且不会危害虚拟机自身的安全
准备
正式为类变量分配内存并且设置类变量初始值的阶段,这些内存都将在方法区中进行配置。
假设一个类变量的定义为:
public static int value = 123;
则整个变量在准备阶段的值依然为0,只有在初始化阶段执行之后他的值才会变为123。

解析
虚拟机将常量池中的符号引用替换为直接引用的过程。
符号引用: 符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中了。
直接引用: 直接引用是个虚拟机实现的内存布局相关的,如果有了直接引用,那么引用的目标一定在内存中存在。

初始化
在准备阶段,变量已经被赋过一次系统要求的初始值了,而在初始化阶段,则是要根据程序猿指定的主观计划去初始化类变量和其他资源。
JMM—— 类加载_第2张图片

<clinit>()方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。
接口中定义的变量使用时,接口才会初始化:接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生<clinit>()方法。但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问:

JMM—— 类加载_第3张图片
反射可以获取泛型参数或参数类型(虽然在类加载时泛型被擦除为object,但是任然有记录)

类加载:
类加载器可以分为:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器,他们的关系一般如下:
JMM—— 类加载_第4张图片
类加载机制————双亲委派机制
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

应用程序类加载器(Application ClassLoader)

这个类加载器由sun.misc.Launcher $App-ClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器

类加载应用的面试问题:
(1)能否自定义类加载器加载自定义的java.lang.Object类.
(2)jdk如何保证一定是调用jdk提供的jjava.lang.Object类.

结论:遵循双亲委派机制的类加载,不会加载到自定义的java.lang.Object.如果是不遵循双亲委派机制的加载,类加载会效验,不允许java.开头的类进行加载.

你可能感兴趣的:(JMM—— 类加载)