常量池主要存放两大类常量:字面量与符号引用。字面量是java意义中的常量,诸如final,或文本字符串。而符号引用则是编译原理中的概念,主要包括:
- 类和接口的全限定名(com/enjoy/learn/core/oop/method/TestClass(理解为加上包名的完整名字))
- 字段的名称和描述符
描述符的含义:描述字段的数据类型,比如char就是C - 方法的名称和描述符
描述符的含义:描述方法的参数列表与返回值,比如void inc() -> ()V
类加载机制
类加载的流程
类加载机制是把描述类的数据从class文件加载至内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型。
特性:类型的加载、连接和初始化过程都是在程序运行期完成的,从而通过牺牲一些性能开销来换取Java程序的高度灵活性。
0.类加载的时机
类从加载进内存开始,到卸载出内存为止,共经历七个生命周期:加载、验证、准备、解析、初始化、使用、卸载。
其中加载、验证、准备、初始化的顺序是确定的,而解析则不确定,可以再初始化后进行解析。
以下五种情况对类进行初始化:
(1)遇到new、getstatic、setstatic、invokestatic;当new对象,或读取设置调用一个静态字段,以及调用一个类的静态方法时;
(2)使用反射时,若类没有进行初始化,则会进行初始化;
(3)当初始化一个类时,如果其父类没有初始化,则会先对其父类进行初始化;
(4)当虚拟机启动时,用户需要指定一个主类(包含main方法的),该类要先进行初始化。
1.加载
加载包括三个步骤:
- 首先通过类对应的全限定名找到二进制字节流;
- 将字节流代表的静态结构转换为方法区的运行时数据结构;
- 在内存中生成一个代表该类的class对象,作为方法区中这个类的各种数据的访问入口。
2.验证
验证是为了保证class文件中的二进制字节流符合jvm的规范。
- 文件格式验证:字节流是否符合class文件的规范
- 元数据验证:判断字节流中的类是否满足规范,比如是否具有父类,其父类是否继承了不允许继承的类(final),是对字节流中的数据类型进行校验。
- 字节码验证:对类的方法体进行校验分析。
4.符号引用验证: 字节流中的符号引用是否正确;字段或方法是否引用了当前类不能使用的方法或类。
3.准备
为类对象(静态变量)分配内存并初始化。初始化是赋零值,只有在flinit之后才会有具体的值。
4.解析
解析阶段是虚拟机将常量池内的符号引用转换成直接引用。
符号引用:符号引用以一组符号来描述所引用的目标,只要使用时能无歧义地定位到目标即可,引用的目标不一定加载到内存中。
直接引用:直接引用是指直接指向对象的指针,或指向一个句柄,引用的目标一定加载到内存中。
虚拟机并未规定解析的时间:需要来判断到底是在类被加载器加载时就对常量池中的符号引用进行解析, 还是等到一个符号引用将要被使用前才去解析它
5.初始化
初始化才真正开始执行类中定义的Java程序代码( 或者说是字节码) 。
在准备时,已经为对象赋了一次初始值,而初始化中将类对象按照程序要求的进行赋值。初始化阶段是执行flinit方法。
类加载器
类加载器的种类
java中将通过类的全限定名来获取二进制字节流拿到jvm外部执行。
类的加载十分重要,对于任意一个类, 都需要由加载它的类加载器和这个类本身一同确立其在Java虚
拟机中的唯一性, 每一个类加载器, 都拥有一个独立的类名称空间。通俗来说,比较两个类是否“相等”, 只有在这两个类是由同一个类加载器加载的前提下才有意义,比如equals方法。
从虚拟机的角度一共有两种虚拟机:启动类加载器,由C++编写,是虚拟机的一部分,不可以被java代码使用;其他类加载器:由java编写,独立于虚拟机外,都继承自抽象类java.lang.ClassLoader。
具体为三种类加载器:
-
启动类加载器:
- 由C++编写,是虚拟机自身的一部分。
- 该加载器负责加载
<JAVA_HOME>/lib
下以及或被-Xbootclasspath
参数所指定路径中的、且可被虚拟机识别的类库, - 启动类加载器不可以直接被java代码所引用。
-
拓展类加载器:
- 该加载器加载
<JAVA HOME>/lib/ext
或者被**java.ext.dirs
系统变量**所指定的路径中的所有类库。
- 该加载器加载
-
应用程序类加载器:
- 该加载器加载用户路径classpath下的所有类,该加载器在java中;
- 如果应用程序中没有自定义过自己的类加载器, 一般情况下这个就是程序中默认的类加载器。
双亲委派机制
顶层为启动类加载器、扩展类加载器、应用程序类加载器、自定义的类加载器。
除了顶层启动类加载器外,都应当有自己的父类加载器,这里的父与子的关系不是继承而是组合关系来复用父加载器的代码。
工作流程:当类加载器收到一个加载类的通知后,不会自己加载类,而是上交给父类加载器处理,当父类加载器不能加载时,所查找的区域没有该类,则会交由子类加载器加载。
好处:以java.lang.Object为例,该类应该在启动类加载器加载,这样Object类在各个类加载器中都是同一个类。如果没有双亲委派机制的话,就会在子类加载器加载,假如应用程序的classpath中也有一个java.lang.Object就会导致,Object类被多次加载。