当程序主动使用某个类的时候,如果该类还没有加载到内存中,则通过以下三个步骤对类进行初始化;
- 类的加载:将类的class文件读入内存,并建一个java.lang.Class对象到方法区中,此过程由类加载器完成
- 类的链接:将类的二进制数据合并到jre中
- 类的初始化:JVM负责对类进行初始化
加载完之后还有两步-----使用,卸载,就是完整的类的生命周期
1、类的加载
将类的文件信息加载到内存中,作为程序方法入口,通俗的来说就是:
“类的加载”就是:将类的class文件读入内存,并且为之创建一个java.lang.Class对象到方法区中
类的加载过程由类加载器完成
具体的加载分三步:
1、通过类的全限命名获取类的二进制字节流
2、将字节流的静态储存结构转化为方法区的运行时结构
3、在内存中生成class对象,作为方法去入口
如果被加载的是一个数组类型,数组类型是一个比较特殊的类型,它不通过类加载器加载,而是通过虚拟机去完成,但是他的引用却要靠加载器加载,加载器会加载完数组的数据类型后将该数组绑定到相应的加载器上,然后与该类加载器一起绑定标识唯一性。
2、类的链接
其中,类的链接可以分为以下几点:
1、验证:确保加载的类符合JVM虚拟机的规范,不会危害虚拟机的自身安全;
2、准备:正是为类变量(static)分配内存并设置类变量默认初始默认值的阶段,这些内存都将在方法区中进行分配,后续初始化时会实际赋值(实例变量会被分配到堆中)
3、解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
什么是将符号引用替换为直接引用?首先我们了解两个概念,如下:
● 符号引用:比如一个类中引用了其它类,但是JVM不知道实际引用的其它类地址在哪,所以就会用符号引用来代表,等到解析的时候,再根据唯一符号引用去找其它类的地址。不止是其它类的代表,符号引用也可以代表方法,字段等。需要注意的是,符号引用与虚拟机布局无关,引用的目标不一定已经加载到内存中。
● 直接引用:直接引用与虚拟机布局有关,如果使用直接引用,那么引用的目标一定已经加载到内存中。
符号引用要转换成直接引用才有效,这也说明直接引用的效率要比符号引用高。那为什么要用符号引用呢?这是因为类加载之前,javac会将源代码编译成.class文件,这个时候javac是不知道被编译的类中所引用的类、方法或者变量他们的引用地址在哪里,所以只能用符号引用来表示,当然,符号引用是要遵循java虚拟机规范的。
3、类的初始化
1、执行类构造器()方法的过程,类构造器()方法是由编译器自动收集所有类变量的赋值动作和静态代码块中的语句合并产生的;
2、当初始化一个类的时候,会判断该父类有没有初始化,如果没有,则先触发父类的初始化,具体的类的初始化流程;
3、jvm会保证一个类的()方法在多线程环境下的被正确枷锁和同步
准备阶段中已经对类变量(static)进行内存分配,初始化是对类变量和静态代码块进行赋值和执行;
4、总结
java类的加载分为三个步骤:
- 类的加载:读取变异之后的.class到内存中,并且创建一个Class对象
- 类的链接:
-
- 验证:验证class字节码,是否符合jvm开发规范
- 准备:将类中的静态变量赋予默认值
- 解析:将类中的符号引用转化为直接饮用
- 类的初始化
-
- 子类初始化
- 父类初始化
5、扩展 - 类的加载机制
前面提到一些概念,其中有一些具体的操作没有说清楚,比如:
● 类加载的时候,如果类中还有别的类,那么是否会更换类加载器去加载?--全盘负责
● 类的链接的时候,会首先进行验证,那么具体的验证方式是什么?--双亲委派机制
● 当类已经被加载之后,还需要被二次加载吗?--缓存机制
01、全盘负责
所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显式的使用另外一个类加载器来载入
02、双亲委派机制
所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载
为什么会出现双亲委派机制?
防止内存中出现多份同样的字节码,这样类的唯一性将无法保证。
若是存在一个类需要被加载到内存中,用户类使用自己的加载器去加载,就会发生每个用户都加载了一份该类的字节码,这就违反了类的唯一性。
即:双亲委派机制,就是为了防止你自定义的类和系统默认类冲突,如果冲突,优先按照系统默认类加载!!!
03、缓存机制
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因