什么是java虚拟机
java虚拟机是运行程序(java虚拟机可执行其他语言程序)的抽象化计算机,通过在实际的计算机上模拟计算机功能,隐藏底层技术的复杂性以及物理机与操作系统的差异性,实现了程序的无关性(平台和语言无关性)。java虚拟机有自己完善的硬件架构(如处理器、堆栈、寄存器)和相应的指令集。
jvm的内存结构
jvm在执行程序的过程中,所管理的内存包括5个运行时数据区域:方法区、堆(线程共享)、虚拟机栈、本地方法栈、程序计数器(线程隔离)。
| 定义 | 生命周期 | 异常 | |
|---|---|---|---|
| 程序计数器 | 当前线程所执行的行号指示器,记录当前程序执行位置 | 与线程相同 | 无内存溢出,占用较小的内存空间 |
| 虚拟机栈 | 描述java方法执行的线程内部模型:每个方法被执行的时候会同步创建一个栈帧,用于存储局操动方(局部变量、操作数栈、动态链接、方法出口),方法的执行对应这栈帧在虚拟机中入栈和出栈的过程 | 与线程相同 | 当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度时,抛出 StackOverFlowError 异常;当线程请求栈时内存用完了,无法再动态扩展时,抛出 OutOfMemoryError 异常 |
| 本地方法栈(C栈) | 与Java虚拟机栈实现的功能类似,描述本地方法运行过程的内存模型,为Native方法服务 | 与线程相同 | StackOverFlowError 和 OutOfMemoryError 异常 |
| 堆 | 存放对象实例及数组的内存空间,是垃圾收集器管理的内存区域,故称GC堆 | 与虚拟机相同 | OutOfMemoryError异常 |
| 方法区 | 在Java虚拟机规范中定义方法区是堆的一个逻辑部分,用于存放:已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码 | 与虚拟机相同周期 | OutOfMemoryError异常 |
Java对象的定位方式
Java 程序通过栈上的 reference 数据(指向对象的引用)来操作堆上的具体对象,但具体方式由虚拟机实现而定,主流的方式有句柄和直接指针两种。
句柄:堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息。好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。
直接指针(HotSpot主要采用):reference 中存储的直接就是对象的地址。好处就是访问对象速度快,节省了一次指针定位的时间开销。
堆栈的区别
堆是物理地址上不连续的内存空间,存储对象的实例和数组,线程共享。
栈是物理地址上连续的内存空间,存储局操动方(局部变量、操作数栈、动态链接、方法出口),线程私有。
什么情况下会发生栈溢出
1、线程请求的栈深度大于虚拟机允许的最大深度,会抛出StackOverFlowError异常。如方法递归没终止条件。
2、新建线程的时候没有足够的内存去创建对应的虚拟机栈,虚拟机会抛出OutOfMemoryError异常。比如线程启动过多就会出现这种情况。
类文件结构
Class文件采用一种类C语言结构体的伪结构来存储数据,这种结构只有无符号数和表两种数据类型,且Class文件是一组以8字节为基础单位的二进制流。 Class 文件结构如下:
ClassFile {
u4 magic; //魔数,类文件的标志,值为OXCAFEBABE
u2 minor_version;//次版本号
u2 major_version;//主版本号
u2 constant_pool_count;//常量池中常量的数量
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags;//访问标记,标识类或接口层次的访问信息
u2 this_class;//类索引(确定类的继承关系,用于确定类的全限定名)
u2 super_class;//父类索引(确定类的继承关系,用于确定父类的全限定名)
u2 interfaces_count;//类实现的接口数量
u2 interfaces[interfaces_count];//接口索引集合(确定类的继承关系)
u2 fields_count;//字段表中字段的数量
field_info fields[fields_count];//字段表集合,描述接口或类中声明的变量,方法表结构中包含属性表集合(attributes)
u2 methods_count;//方法数量
method_info methods[methods_count];//方法表集合
u2 attributes_count;//属性表中属性的数量
attribute_info attributes[attributes_count];//属性表集合(字段表、方法表都包含自己的属性表集合,以描述某些场景的专有信息,如Code属性保存方法体内代码对应的字节码指令)
}
什么是类加载?类加载的过程?
类的加载是指把描述类的class文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
类加载过程包括加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)五个阶段,类的生命周期包括:加载、验证、准备、解析、初始化、使用、卸载。
加载阶段工作:
1、通过类的全限定名获取定义此类的二进制字节流。
2、将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
3、在堆中生成一个代表该类的Class对象,作为程序访问方法区中的类型数据的外部接口。
验证阶段工作:
确保Class文件的字节流中包含的信息符合虚拟机规范,保证这些信息被当做代码运行后不会危害虚拟机自身的安全。 主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证。
准备阶段工作:
为静态变量(类变量)分配内存并设置初始值(随着Class对象分配在堆中)。
解析阶段工作:
将常量池内的符号引用替换为直接引用的过程。符号引用用于描述所引用的目标,直接引用直接指向目标的地址。
初始化阶段工作:
执行类构造器<clinit>()方法,去初始化类变量和其他资源。
什么是类加载器,类加载器有哪些?
通过一个类的全限定名获取该类的二进制字节流的代码叫做类加载器,即类加载器实现类的加载动作。主要有四种类加载器:
启动类加载器:用来加载 Java 核心类库,无法被 Java 程序直接引用,是虚拟机的一部分。
扩展类加载器:它用来加载 Java 的扩展类库。
系统类加载器:负责加载用户路径上的所有类库。可通过ClassLoader.getSystemClassLoader()获取它。
自定义类加载器:通过继承java.lang.ClassLoader类的方式实现。
类的实例化顺序?(待定)
父类中的static代码块,当前类的static代码块 父类的普通代码块 父类的构造函数 当前类普通代码块 当前类的构造函数
如何判断一个对象是否存活?
对象死亡:对象不再被任何途径使用。判断对象是否存活有两种方法:引用计数法和可达性分析。
什么是指针碰撞?什么是空闲列表?什么是TLAB?
一般情况下,对象实例在堆中分配内存(发生逃逸分析等除外)。创建对象时,在经过类加载检查后,虚拟机开始为新生对象分配内存。如果Java堆是规整的,使用过的的内存和空闲内存中间有一个指针作为分界点的指示器,内存分配时把指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式就是“”。如果Java堆不是规整的,已被使用的内存和空闲内存相互交错,虚拟机就必须维护一个列表,记录空闲内存块,内存分配时从列表找到一块内存分配给对象实例,并更新列表上的记录,这种分配方式称为“”。
为了解决内存分配时的线程安全问题,可以让每个线程在Java堆中预先分配一小块内存进行内存分配,称为(TLAB,Thread Local Allocation Buffer)。虚拟机通过-XX:UseTLAB设定它的。