JVM由类加载器、运行时数据区、执行引擎、本地方法接口、垃圾回收系统五部分组成。
一、类加载器
用来将二进制.class文件加载到内存中,经过加载、链接(校验、准备、解析)、初始化,并在内存中创建一个class类对象,保存着该类在内存中的数据结构。(注意这个class类对象和堆内存中new出来的实例不是一个东西,这个class类对象只有一个,jvm并没有规定这个class类对象在内存中的位置,hotspot虚拟机讲这个class类对象放在方法区,作为程序访问方法区这个类的各种数据的访问入口)
1.1 加载
将符合jvm规范的二进制字节流加载到内存;用于后面的连接和初始化。
加载主要完成三件事:
1)通过全限定名来获取定义此类的二进制字节流
2)建这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
3)在雷村中生成一个java.lang.Class对象,作为程序访问方法区这个类的各种数据的访问入口
二进制字节流的来源:
1)本地的.class文件
2)通过网络获取的.class文件
3)在运行期间有jvm生成的字节码(动态代理)
4)从zip包中读取,比如jar,zip,war等
5)从数据库中读取
6)有其他文件生成,比如有jsp文件生成class类。
1.2 连接
1.2.1 校验
1)文件格式校验
只有通过这个阶段的验证,字节流才会进入方法区中进行存储,后面的所有验证阶段都是对基于方法区的存储结构进行的,不会再直接操作字节流
2)元数据校验
3)字节码校验
4)符号引用校验
1.2.2 准备
准备阶段是为这个类的类变量分配内存设置初始值,如果是int型,赋初始值为0;如果是引用类型,赋为null。而赋真实值(程序员设置的值)是在初始化阶段。
例外:
1)如果被final修饰的变量,在准备阶段会赋真实值,比如static final int a = 123;这时,在准备阶段a会被赋值为123
2)如果被final修饰,但是在编译阶段不确定,只有在运行阶段确定,还是会被赋值为0(如果是int型),如 static final int a = (int)Math.random();
1.2.3 解析
这一阶段试讲常量池诶的符号引用替换为直接引用。主要针对:类或者接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符
1.3 初始化
初始化阶段是将准备阶段分配的类变量赋值真实值,类的初始化阶段会执行类构造器()方法。
1)()方法是由编译器自动收集类中的所有类变量的赋值动作和**静态语句块(static{}**块)中的语句合并产生的,编译器手机的顺序由语句在源文件中出现的顺序决定。静态语句块只能访问到定义在静态语句块之前的变量
2)虚拟机会保证子类的方法执行之前,父类的方法已经执行完毕。因此在虚拟机中第一个被执行的()方法类肯定是java.lang.Object
3)对于接口,执行()方法时,不需要先执行父类的()方法,只有真正调用父类的变量才会去执行父类的
4)对于一个类的()方法,虚拟机会保证其线程安全,并且只执行一次,如果有多个线程视图初始化这个类,只有一个线程会执行方法。比如单例,就是使用了jvm的这个特性。
看几个例子:
1、子类调用父类的类变量,父类会初始化,子类不会
2、子类调用子类的类变量,先执行父类的,再执行子类的
3、准备阶段先于初始化阶段,b在准备阶段被赋值为0,static块在初始化阶段先于构造方法执行。
4、()方法由静态变量和静态块组成,顺序由源码决定,静态变量p在static{}块之前,所以先执行new Parent()方法
1.4 主动引用与被动引用
只有主动引用会触发被的初始化,被动引用不会;
主动引用的场景5条:
1)遇到new 、getstatic、putstatic或者invokestaic这四条字节码指令时,如果类没有被初始化过,则需要先触发初始化。4条指令对应的场景是:new关键字实例化对象、读取或者设置一个类的静态变量、调用静态方法的时候。(final关键之修饰的除外)
2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有被初始化,首先触发初始化。
3)当初始化一个类,发现其父类没有初始化,则先初始化其父类
4)当虚拟机启动时,用户需要指定一个执行的主类,虚拟机先初始化这个主类
5)使用JDK1.7,如果java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个句柄对应的类没有初始化,则先触发初始化。
final修饰的变量,如果在编译阶段确定,就会被编译器在编译阶段放入常量池,调用该变量不会触发该类的初始化
变量被final修饰,但是编译器不确定其值,只有在运行期才能确定,这种情况下,调用该变量还是会初始化该类
当一个接口初始化时,并不要求其负借口完成了初始化;
只有在正真使用到父接口的时候(如,引用接口中定义的常量),才会初始化
static{}静态块只会在类舒适化时执行一次。{}代码块,每次new的时候都会执行。
1.5 JVM命令
JVM命令格式有两种,
一种是赋值型的,比如将堆内存赋值为20M,形如
-XX:MaxPermSize=128M;
一种是布尔值开关型,形如
-XX:+TraceClassLoading
其中**-XX:是固定写法,赋值型就是XX=数值**;开关型就是加号或减号在加上开关命令,加号表示开启,减少表示关闭。比如+TraceClassLoading就表示开启类加载追踪。
常用内存分配:
-Xms 设置堆内存的最小值
-Xmx 设置堆内存的最大值
-Xss 设置栈内存的大小
-XX:MinHeapFreeRatio 设置堆内存的最小空闲比例
-XX:MaxHeapFreeRatio 这是堆内存的最大空闲比例
-XX:NewSize 设置新生代的最小内存值
-XX:NewMaxSize 设置新生代的最大内存值
-XX:SurviorRatio 设置Eden区和Survior区的比例
-XX:MaxPermSize 设置持久代的最大值
-XX:PermSize 设置持久代的最小值
-XX:TargetSurvivorRatio 设置survivor区的可使用率