自己整理了些关于JVM(Java Virtual Machine)的相关概念。
主要围绕着以下几个问题来写:
- jvm是什么
- 类的加载过程
- jvm的内存结构
- 双亲委派模型
- GC算法,如何调优
- FULL GC的条件
jvm是什么?
JVM是虚拟机的英文简称。它是java运行环境的一部分,是一个虚构出来的计算机,它是通过在实际的计算机上仿真模拟各种计算机功能来实现的。主要用来运行Java的类文件。
经常听说hotspot,她和jvm的区别在于 hotspot是jvm的一种实现,是sun jdk和open jdk中自带的虚拟机,同时也是目前使用范围最广的虚拟机。
命令行中输入 java -version 看到了hotspot
类的加载过程
1.加载
加载时jvm做了这三件事:
1)通过一个类的全限定名来获取该类的二进制字节流
2)将这个字节流的静态存储结构转化为方法区运行时数据结构
3)在内存堆中生成一个代表该类的java.lang.Class对象,作为该类数据的访问入口
2.验证
验证、准备、解析这三步可以看做是一个连接的过程,将类的字节码连接到JVM的运行状态之中
验证是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会威胁到jvm的安全
验证主要包括以下几个方面的验证:
1)文件格式的验证,验证字节流是否符合Class文件的规范,是否能被当前版本的虚拟机处理
2)元数据验证,对字节码描述的信息进行语义分析,确保符合java语言规范,元数据验证验证的是子类继承的父类是否是final类;如果这个类的父类是抽象类,是否实现了起父类或接口中要求实现的 所有方法;子父类中的字段、方法是否产生冲突等,这个过程把类、字段和方法看做组成类的一个个元数据,然后根据JVM规范,对这些元数据之间的关系进行验证。所以,元数据验证阶段并未深 入到方法体内。
3)字节码验证 既然元数据验证并未深入到方法体内部,那么到了字节码验证过程,这一步就不可避免了。字节码主要是对方法体内部的代码的前后逻辑、关系的校验,通过数据流和控制流分析,确 定语义是合法的,符合逻辑的。
4)符号引用验证 这个校验在解析阶段发生,符号引用验证做的工作主要是验证字段、类方法以及接口方法的访问权限、根据类的全限定名是否能定位到该类等。具体过程会在接下来的解析阶段进行 分析。
3.准备 将类的静态变量放进方法区分配内存,初始化为系统的初始值。对于final static修饰的变量,
直接赋值为用户的定义值。如下面的例子:这里在准备阶段过后的初始值为0,而不是7
public static int a=7
4.解析
解析是将常量池内的符号引用转为直接引用(如物理内存地址指针)
5.初始化
到了初始化阶段,jvm才真正开始执行类中定义的java代码
初始化阶段是执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。 虚拟机会保证一个类的
<clinit>() 方法在多线程环境中被正确加锁和同步。
何时初始化:
1>在类没有进行过初始化的前提下,当执行new
、getStatic
、setStatic
、invokeStatic
字节码指令时,类会立即初始化。对应的java操作就是new
一个对象、读取/写入一个类变量(非final
类型)或者执行静态方法。
2>在类没有进行过初始化的前提下,当一个类的子类被初始化之前,该父类会立即初始化。
3>在类没有进行过初始化的前提下,当包含main
方法时,该类会第一个初始化。
4>在类没有进行过初始化的前提下,当使用java.lang.reflect
包的方法对类进行反射调用时,该类会立即初始化。
5>在类没有进行过初始化的前提下,当使用JDK1.5
支持时,如果一个java.langl.incoke.MethodHandle
实例最后的解析结果REF_getStatic
、REF_putStatic
、REF_invokeStatic
的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。第五个没看懂 囧
注:
通过子类引用父类的类变量不会触发子类的初始化操作
通过定义对象数组的方式是不能触发对象初始化的
引用类的final类型的类变量无法触发类的初始化操作
并发初始化情况下的运行机制又如何?
JVM虚拟机规定了几条标准:
- 先父类后子类,(源码中)先出现先执行
- 向前引用:一个类变量在定义前可以赋值,但是不能访问。
- 非必须:如果一个类或接口没有类变量的赋值动作和
static
代码块,那就不生成<clinit>
方法. - 执行接口的
<clinit>
方法不需要先执行父接口的<clinit>
方法。只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>
方法。 - 同步性:
<clinit>
方法的执行具有同步性,并且只执行一次。但当一个线程执行该类的<clinit>
方法时,其他的初始化线程需阻塞等待。
jvm的内存结构
更详细的解释于https://blog.csdn.net/rongtaoup/article/details/89142396
程序计数器:指向当前线程正在执行的字节码指令的地址、行号
虚拟机栈: 存储当前线程运行方法所需要的数据、指令、返回地址。first in last out
本地方法栈: Java代码中用native修饰的,和操作系统交互的,底层用c/c++写的
方法区: 被虚拟机加载的类信息、常量、静态常量等。
堆: 主要存放new出来的对象
脑瓜疼,未完待续
部分引用于:https://www.cnblogs.com/coder-lichao/p/10919908.html
https://segmentfault.com/a/1190000012527652
侵删