导图社区 Java虚拟机
关于Java虚拟机的思维导图,包含虚拟机类加载(Class Loader);运行时数据区(Runtime Data Area);执行引擎(Execution Engine);本地库接口(Native Interface)等。
编辑于2021-12-02 16:59:02JVM
虚拟机类加载(Class Loader)
什么是类加载机制?
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
Java可以动态拓展的语言特性依赖于运行期加载和动态连接这两个特点
Java语言里,类型的加载、连接和初始化过程都是在程序运行期间完成的,为Java应用程序提供了高度的灵活性。
类的生命周期
加载
类加载器
通过一个类的全限定名来获取描述此类的二进制字节流
类加载器和这个类本身一同确立在java虚拟机中的唯一性,每个类加载器都拥有一个独立的类名称空间。
Java中类加载器种类
启动类加载器(Bootstrap ClassLoader)
拓展类加载器(Extension ClassLoader)
应用程序类加载器(Application ClassLoader)
Java推荐使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是可以有效的维护Java基础类在开发环境中肯定是同一个类。
双亲委派类加载器加载类过程
如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器去完成,每个层次的加载器都是如此,因此所有加载请求最终都会上送到顶层的启动类加载器中,只有父类加载器反馈自己无法完成这个加载请求(它的搜索范围里没有找到所需要的类)时,子加载器才会尝试自己去加载、
连接(验证、准备、解析)
初始化
什么时候初始化?
有且只有一个类进行主动引用时才会触发类的初始化
1、使用new关键字实例化对象的时候、读取或设置一个类的静态字段(final修饰的常量除外,在编译期把结果存储在常量池的静态字段里)、调用一个类的静态方法的时候;
2、使用java.lang.reflect包的方法对类进行反射调用时候,如果类没有进行过初始化,则需要先触发其初始化;
3、当初始化一个类时,发现该类的父类还没有进行初始化,则需要先触发其父类的初始化;
4、当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类;
5、使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandler实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先触发其初始化;
接口初始化和类初始化的区别?
一个接口在初始化时,并不要求父接口全部都完成了初始化,只有在真正使用的父接口的时候(如接口中定义的常量),才会初始化;
使用
卸载
运行时数据区(Runtime Data Area)
程序计数器
当前线程执行字节码的行号指示器
每个线程都具有一个独立的程序计数器
Java虚拟机支持的多线程是通过线程轮流切换并分配执行时间的方式实现的,也就是说在任何一个特定时刻,一个处理器都只会执行一条线程中的指令。所以,为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器。
执行java方法,程序计数器记录的是正在执行的字节码指令的地址
执行native方法,计数器值为空(Undefined)
jvm中所有内存区域中唯一一个没有规定任何OOM的区域
虚拟机栈(VM Stack)
线程私有,生命周期和线程相同
描述java方法执行的内存模型
局部变量表
存放编译期可知的基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress类型
操作数栈
动态链接
方法出口
方法执行时候会创建一个栈帧(Stack Frame),用来存储局部变量表、操作数栈、动态链接、方法出口等信息
每个方法从调用到执行完成对应着一个栈帧从入栈到出栈的过程
异常
StackOverflowError异常
线程请求的栈深度大于虚拟机允许的深度,抛出异常
OutOfMemoryError异常
虚拟机栈如果可以动态拓展,当无法申请足够内存时,抛出异常
本地方法栈(Native Method Stack)
为虚拟机执行native方法服务(和虚拟机栈作用相同)
堆(Heap)
描述
在虚拟机启动时候创建,所有线程共享的一块内存区域
所有对象实例以及数组都在堆上分配
唯一目的就是存放对象实例,几乎所有对象实例都在堆上分配内存,随着技术发展所有对象在堆上分配这句话已经慢慢不准确了(JIT编译器)
垃圾收集器管理的主要区域,也被称为GC堆
异常
OutOfMemoryError
堆中不能完成实例分配,并且堆无法拓展时,抛出异常
新生代(Young Generation)
Eden Space
Survivor Space
From Survivor
To Survivor
老年代(Tenured Generation)
方法区(Method Area)
描述
存储已经被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据
非堆(Non-Heap),作为堆的逻辑部分
所有线程共享的内存区域
异常
OutOfMemeoryError
当方法区无法满足内存分配的需求时,出现异常
永久代(Permanent Generation)
在后续的HotSpot版本虚拟机中被代替掉(java8取消了永久代,代用了Metaspace)
运行时常量池(Runtime Constant Pool)
存放编译器生成的各种字面量、符号引用
运行期间也可以将新的常量存放在常量池中(String.intern())
执行引擎(Execution Engine)
将输入的字节码指令在加载或执行时翻译成宿主主机本地CPU的指令集(HotSpot中执行引擎实现的一种解释-编译的层次结构)
解释执行
解释执行字节码,并已方法为单位收集“热点”代码,将热点代码执行C0编译
C0编译
将收集的“热点代码”编译成本地代码,并进行一些简单的优化。继续手机运行时信息,将一些频繁执行的本地代码进行C1编译
C1编译
将C0阶段的本地代码,进行一些比较激进的优化。如果某些优化导致本地代码执行失败,此时JVM会退化到解释执行字节码阶段。
本地库接口(Native Interface)
垃圾收集器与内存分配策略
垃圾收集算法
标记-清除
复制
新生代采用复制算法收集内存
标记-整理
老年代和永久代采用标记-整理算法
分代收集
垃圾收集器整体上采用分代收集算法,将内存分为:新生代、老年代、永久代(非堆)
自动内存管理
1、对象优先在Eden区分配
当Eden区没有足够空间进行分配时,虚拟机发生一次Minor GC(新生代GC),在发生Minor GC时,会将当前Eden区中对象复制到Survivor空间,Suvivor空间不足时,则通过担保机制提前转移到老年代去,当老年代内存不够时,则会出发老年代GC(Full GCMajor GC)
2、大对象直接进入老年代
虚拟机提供了-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配
3、长期存活的对象将进入老年代
每次Minor GC,Survivor空间中对象的分代年龄(对象头age属性)会递增,当年龄大于MaxTenuringThreshold设置值时,长期存活的对象将会进入老年代
4、动态对象年龄判定
在Survivor空间中相同年龄所有对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄对象就可以直接进入老年代,无需达到MaxTenuringThreshold中要求的年龄
垃圾收集策略
新生代GC(Minor GC/Young GC)
指发生在新生代的垃圾收集动作;java大多数对象存在朝生夕灭的特性,Minor GC非常频繁,一般回收速度也比较快
老年代GC(Full GC/Major GC)
指发生在老年代的GC,出现Major GC至少伴随至少一次Minor GC(但非绝对,在Parallel Scavenge收集器的收集策略中就有直接进行Major GC策略的选择过程)。
Major GC的速度一般会比Minor GC慢10倍以上