类的生命周期
首先我们先看类的生命周期
类的加载过程包含了加载、验证、准备、解析、初始这五个阶段,其中除了解析阶段其他四个阶段的发生顺序都是确定的,因为解析阶段在某些情况下会在初始阶段之后开始,同时这些阶段都是按顺序开始的不是按顺序进行或结束,因为这些阶段通常都是互相交叉的混合进行。以下为类的生命周期
加载->验证->准备->解析->初始化->使用->卸载 (验证->准备->解析这三个可概括为连接阶段)
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
连接阶段:
首先进行验证,确保当前Class文件的字节流信息是否符合当前虚拟机的要求分为:文件格式验证、元数据验证、字节码验证、 符号引用验证。然后进行准备阶段开始正式为类的变量(仅包括类变量static,不包含实例变量,实例变量会在对象实例化时一起分配到Java堆中)分配内存并设置变量初始值(零值非代码中的数值)的阶段,这些内存在方法区中分配。开始解析,将常量池中的常量引用替换为直接引用。
初始化为类的静态变量赋予正确的初始值。
初始化有以下步骤:如果这个类还没有被加载和连接则先进行加载和连接;如果该类 的父类还没有被初始化,则先初始化其父;如果该类中有初始化语句则先依次执行类中的初始化语句。
由此也可得出类的初始话时机,创建类的实例时(new);访问某个类或接口的静态变量或对该静态变量赋值时;调用类的静态方法时;反射(Class.forname("com.XXX.XXX"));初始化某个类的子类时;Java虚拟机启动时被标记为启动类时
使用:类访问方法区内的数据结构的接口,对象时Heap区(堆区)的数据。
最后卸载阶段:执行了System.exit()方法时;程序正常结束时;程序执行过程中遇到了异常或错误导致异常终止;由于操作系统出翔错误而导致java虚拟机进程终止;
类加载器、jvm加载机制:
再java程序员的眼中类加载器主要分为三种;
启动类加载器: Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
扩展类加载器: Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader
实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
应用程序类加载器
: Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader
来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
类的加载:
-
命令行启动应用时候由JVM初始化加载
-
通过Class.forName()方法动态加载
-
通过ClassLoader.loadClass()方法动态加载
Class.forName()和ClassLoader.loadClass()区别?
-
Class.forName(): 将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
-
ClassLoader.loadClass(): 只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
-
Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。
JVM类加载机制:
-
全盘负责
,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入 -
父类委托
,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类 -
缓存机制
,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效 -
双亲委派机制
, 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
双亲委派机制过程?
-
当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
-
当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
-
如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
-
若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
双亲委派优势
-
系统类防止内存中出现多份同样的字节码
-
保证Java程序安全稳定运行
-
JVM内存结构: