欢迎访问我的个人博客休息的风
JVM运行时数据区分为堆,栈,方法区(元空间),我将从一个class文件加载到内存,再分配对象,再使用这个对象这样一个过程进行分析。总体情况如下图所示:
- 方法区
方法区是各个线程共享的内存区域,用于存储被虚拟机加载的类信息,常量,静态常量,即时编译器编译后的代码,运行时常量池。在HotSpot上也被称为“永久代”。
这一区域主要的知识点是类加载的过程,这一过程分为五个阶段,分别是加载,验证,准备,解析,初始化
- 加载
虚拟机需要完成以下三件事:
1、通过一个类的全限定名来获取定义此类的二进制字节流
2、将这个字节流所代表的静态数据结构转换为方法区的运行时数据结构
3、在内存中生成一个代表这个类的java.lang.Class对象,做为方法区这个类的各种数据结构的访问入口。
简单来说,就是先通过全类名把文件加载到二进制字节流,然后转换为方法区的数据结构,再生成class对象,做为访问入口。
类加载器
对于1中的这一操作,需要用到类加载器模块。这个类加载器在用于判断两个类是否相等时用到。也就是只有再同一个类加载器加载的前提下,再去比较是否相等才有意义。这里的相等包括:equals、isAssignableForm、instanceof等
双亲委派模型
java虚拟机通过双亲委派模型来实现类加载器。所谓的双亲委派模型如下图:
从上到下分别为:
启动类加载器,加载<JAVA_HOME>/lib目录下(或通过-Xbootclasspath参数指定的目录下),能被java虚拟机识别的
文件名(如:rt.jar)类库加载虚拟机内存中。开发者不能直接使用
扩展类加载器,加载<JAVA_HOME>/lib/ext目录下(或java.ext.dirs系统变量所指定的目录下),所有的类库,参被开发者使用
应用程序类加载器,加载类路径下(ClassPath)的所有类库,开发者可以直接使用
自定义类加载器,自己定义的类加载器。
双新委派模型的加载查找过程也是自顶向下的,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。这些父加载器的组织方法一般不通过继承,而是通过组合来复用。
- 加载
虚拟机需要完成以下三件事:
1、通过一个类的全限定名来获取定义此类的二进制字节流
2、将这个字节流所代表的静态数据结构转换为方法区的运行时数据结构
3、在内存中生成一个代表这个类的java.lang.Class对象,做为方法区这个类的各种数据结构的访问入口。
简单来说,就是先通过全类名把文件加载到二进制字节流,然后转换为方法区的数据结构,再生成class对象,做为访问入口。
类加载器
对于1中的这一操作,需要用到类加载器模块。这个类加载器在用于判断两个类是否相等时用到。也就是只有再同一个类加载器加载的前提下,再去比较是否相等才有意义。这里的相等包括:equals、isAssignableForm、instanceof等
双亲委派模型
java虚拟机通过双亲委派模型来实现类加载器。所谓的双亲委派模型如下图:
从上到下分别为:
启动类加载器,加载<JAVA_HOME>/lib目录下(或通过-Xbootclasspath参数指定的目录下),能被java虚拟机识别的
文件名(如:rt.jar)类库加载虚拟机内存中。开发者不能直接使用
扩展类加载器,加载<JAVA_HOME>/lib/ext目录下(或java.ext.dirs系统变量所指定的目录下),所有的类库,参被开发者使用
应用程序类加载器,加载类路径下(ClassPath)的所有类库,开发者可以直接使用
自定义类加载器,自己定义的类加载器。
双新委派模型的加载查找过程也是自顶向下的,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。这些父加载器的组织方法一般不通过继承,而是通过组合来复用。
- 验证
- 准备
- 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
- 初始化
● 初始化阶段是执行类构造器<clinit>()方法的过程
● 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他 线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。(单例模式静态内部类的实现方式就是根据这一原理)。
这里需要注意,只有以下五种情况下才会触发初始化操作:
1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初 始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字 实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常 量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化, 则需要先触发其初始化。
3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父 类的初始化。
- 4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个 类),虚拟机会先初始化这个主类。
- 堆区
- TLAB分配
java对象分配过程:
1. 编译器通过逃逸分析,确定对象是在栈上分配还是在堆上分配。如果是在堆上分配,则进入选项2.
2. 如果tlab_top + size <= tlab_end,则在在TLAB上直接分配对象并增加tlab_top 的值,
3. 重新申请一个TLAB,并再次尝试存放当前对象。如果放不下,则4.
4. 在Eden区加锁(这个区是多线程共享的),如果eden_top + size <= eden_end则将对象存放在Eden区,
5. 执行一次Young GC(minor collection)。
6. 经过Young GC之后,如果Eden区任然不足以存放当前对象,则直接分配到老年代。
- 可达性分析(什么时候可以回收?)
走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
- 安全点(什么时候回收?)
- 安全区域(什么时候回收?)
- 垃圾回收算法(怎么回收?)
- 垃圾回收器(怎么回收?)
- 虚拟机栈
栈帧包括:局部变量表,操作数栈,动态链接(每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用),方法出口方法区等
局部变量表:八种基本数据类型,dubbo、long占两个局部变量空间(slot),reference对象引用,returnAddress;所需空间编译期就确定,运行期不再改变
- 静态分派(重载)
- 动态分派(重写)
1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。
2)如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校
验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回
java.lang.IllegalAccessError异常。
3)否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。
4)如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。