1、类的生命周期
image.png加载
- 找到类文件(通过类的全限定名来获取定义此类的二进制字节流,class文件)
- 放入方法区(将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构)
- 开个入口(生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口)
就是通过类加载器把类读入内存。需要注意:第三步虽然生成了对象,但并不在堆里,而是在方法区里。
连接
1>校验
检查Class文件的字节流中包含的信息是否符合当前虚拟机的规范要求,并且不会危害虚拟机自身的安全(文件格式验证、元数据验证、字节码验证、符号引用验证)
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
2>准备
将为静态变量和静态常量分配内存,并赋值。
需要注意的是,静态变量只会给默认值。
比如下面这个:public static int value = 123;
此时赋给value的值是0,不是123。
静态常量(static final修饰的)则会直接赋值。
比如下面这个:public static final int value = 123;
此时赋给value的值是123。
3>解析
就是jvm将常量池的符号引用替换为直接引用
假设有一个People类,包含了一个Car类的run()方法:
class People{
......
public void gotoWork(){
car.run(); //这段代码在People类中的二进制表示为符号引用
}
......
}
在解析阶段之前,People类并不知道car.run()这个方法内存的什么地方,于是只能用一个字符串来表示这个方法。该字符串包含了足够的信息,比如类的信息,方法名,方法参数等,以供实际使用时可以找到相应的位置。这个字符串就被称为符号引用。
在解析阶段,jvm根据字符串的内容找到内存区域中相应的地址,然后把符号引用替换成直接指向目标的指针、句柄、偏移量等,这之后就可以直接使用了。这些直接指向目标的指针、句柄、偏移量就被成为直接引用。
初始化
主要工作是为静态变量赋程序设定的初值, 初始化类的局部变量,同时执行静态初始化块
上面的静态变量:public static int value = 123;
经过这一步,value的值终于是123了。
image.png
2、类的加载机制
加载阶段需要"通过一个类的全限定名来获取描述此类的二进制字节流",这件事情就是类加载器在做
jvm自带三种类加载器:
1.启动类加载器
2.扩展类加载器
3.应用程序类加载器
他们的继承关系如下图:
image.png
双亲委派
双亲委派机制工作过程:
- 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了
- 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader
- 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回
对于任意一个类,被同一个类加载器加载后都是唯一的。但如果被不同加载器加载后,就不是唯一的了。
即使是源于同一个Class文件、被同一个JVM加载,只要加载类的加载器不同,那么类就不同。
双亲委派的优点:
- 避免重复加载。防止内存中出现多份同样的字节码,保证了类在JVM中的唯一性
- 为了安全
打破双亲委派:
"双亲委派"机制只是Java推荐的机制,并不是强制的机制
比如JDBC就打破了双亲委派机制。它通过Thread.currentThread().getContextClassLoader()得到线程上下文加载器来加载Driver实现类(JDBC只提供了接口,并没有提供实现),从而打破了双亲委派机制
不遵循双亲委托模型的情况: 重写loadClass()方法、SPI、OSGI
简单总结:
- 什么是类的加载?
JVM把通过类名获得类的二进制流之后,把类放入方法区,并创建入口对象的过程被称为类的加载。经过加载,类就被放到内存里了。 - 哪些情况会触发类的初始化?
1>假如这个类是入口类,他会被初始化。
2>使用new创建对象,或者调用类的静态变量,类会被初始化。不过静态常量不算。
3>通过反射获取类,类会被初始化
4>如果子类被初始化,他的父类也会被初始化。
5>使用jdk1.7的动态语言支持时,调用到静态句柄,也会被初始化。 - JVM加载一个类的过程
"加载、连接、初始化、使用、卸载"五个阶段,连接又可以分为"校验、准备、解析"三个过程 - 什么时候会为变量分配内存?
在准备阶段为静态变量分配内存,只会给默认值;在初始化阶段才会真正的赋值 - JVM的类加载机制是什么?
双亲委派机制,类加载器会先让自己的父类来加载,父类无法加载的话,才会自己来加载。 - Class.forName()和ClassLoader.loadClass()区别?
Class.forName():将类.class文件加载到jvm中,还会对类进行解释,执行类中的static块;
Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象;
ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance()才会去执行static块;
参考文章:
https://www.cnblogs.com/ityouknow/p/5603287.html
https://www.jianshu.com/p/db2eee89d5c9
网友评论