目录
1、什么是JVM?
Java Virtual Machine,Java虚拟机。Java程序的跨平台特性主要是指字节码文件可以在任何具有Java虚拟机的计算机或者电子设备上运行,Java虚拟机中的Java解释器负责将字节码文件解释成为特定的机器码进行运行。因此在运行时,Java源程序需要通过编译器编译成为.class文件。
2、JVM的内存结构?
粗略分来,JVM的内部体系结构分为三部分,分别是:类装载器(ClassLoader)子系统,运行时数据区,和执行引擎。
1)类装载器(ClassLoader)子系统
每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。JVM的两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。
2)运行时数据区
主要包括:方法区,堆,Java栈,PC寄存器,本地方法栈。
方法区:线程共享。存储类信息、常量、静态变量、即时编译器编译后的代码。
堆:线程共享。所有的对象实例以及数组都要在堆上分配。回收器主要管理的对象。
Java栈:线程私有。存储局部变量表、操作栈、动态链接、方法出口,对象指针。
PC寄存器:线程私有。是当前线程所执行的字节码的行号指示器。指向下一条要执行的指令。
本地方法栈:线程私有。为虚拟机使用到的Native 方法服务。如Java使用c或者c++编写的接口服务时,代码在此区运行。
3)执行引擎
主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行其中解释属于第一代JVM,即时编译JIT属于第二代JVM,自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式 。
自适应优化:开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。
3、类加载机制?
1)加载
类加载时机
- 使用new实例化对象时,读取和设置类的静态变量、静态非字面值常量(静态字面值常量除外)时,调用静态方法时。
- 对内进行反射调用时。
- 当初始化一个类时,如果父类没有进行初始化,需要先初始化父类。
- 启动程序所使用的main方法所在类
在加载阶段,虚拟机需要完成以下3件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2)链接
验证:确保被加载的类的正确性,确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。主要是文件格式验证、元数据验证、字节码验证、符号引用验证。 验证阶段是非常重要的,但不是必须的。可以采用-Xverify:none参数来关闭大部分的类验证措施。
准备:为类的静态变量分配内存,并将其赋默认值。对被final修饰的静态字面值常量赋初值。
解析:将常量池中的符号引用替换为直接引用(内存地址)的过程。
符号引用就是一组符号来描述目标,可以是任何字面量。属于编译原理方面的概念如:包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。如指向方法区某个类的一个指针。
3)初始化:为类的静态变量赋初值
赋初值两种方式:
- 定义静态变量时指定初始值。如 private static String x="123";
- 在静态代码块里为静态变量赋值。如 static{ x="123"; }
注意:只有对类的主动使用才会导致类的初始化。
4)clinit 与 init
在编译生成class文件时,编译器会产生两个方法加于class文件中,一个是类的初始化方法clinit, 另一个是实例的初始化方法init。
4.1clinit
clinit指的是类构造器,主要作用是在类加载过程中的初始化阶段进行执行,执行内容包括静态变量初始化和静态块的执行。
- 如果类中没有静态变量或静态代码块,那么clinit方法将不会被生成。
- 在执行clinit方法时,必须先执行父类的clinit方法。
- clinit方法只执行一次。
- static变量的赋值操作和静态代码块的合并顺序由源文件中出现的顺序决定。
4.2init
init指的是实例构造器,主要作用是在类实例化过程中执行,执行内容包括成员变量初始化和代码块的执行。
- 如果类中没有成员变量和代码块,那么clinit方法将不会被生成。
- 在执行init方法时,必须先执行父类的init方法。
- init方法每实例化一次就会执行一次。
- init方法先为实例变量分配内存空间,再执行赋默认值,然后根据源码中的顺序执行赋初值或代码块。