前言
引用百度文库上对java虚拟机的定义.
虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行
重要组成部分
类加载子系统
负责从文件系统或者网络中加载class信息,加载的信息存放在一块称之为方法区的内存空间
方法区
存放类信息、常量信息、常量池信息、包括字符串常量和数字常量等
java堆
在虚拟机启动的时候创建,此内粗区域的唯一目的就是存放对象实例,是所有线程共享的,同时,这块内存区域也是垃圾收集器的主要区域,也被成为GC堆
Java虚拟机栈
每个java虚拟机线程都有一个私有的栈,其生命周期与线程相同,它描述了Java的方法执行模型,每个方法执行时都会创建一个栈帧。会抛出StackOverFlowError和OOM
本地方法栈
与Java虚拟机栈类似,最大的不同就是它是用于本地方法调用,同常用c语言编写
直接内存
NIO库允许java使用直接内存,通常速度由于java堆
程序计数器
程序计数器可以理解为当前线程执行的字节码的行号指示器,字节码解释器就是通哟改变这个值来获取需要执行的下一条需要执行的字节码指令。对于多线程来说,每条线程都有自己的程序计数器,这样各线程之间的计数器互不影响,这类内存区域也叫作“私有内存”(可以看到其实并不是私有的),之所以这么设计,是因为在多线程的情况下,完全可能出现线程中断的情况,那么当被中断的线程需要回复执行的时候,怎么知道上次该线程执行到哪里了呢?这就需要程序计数器发挥作用了,由于每个线程都有自己的程序计数器,这样当CPU重新调度该线程的时候,从其计数器中取出下一条的字节码执行指令,于是就可以继续执行了
执行引擎
负责执行虚拟机的字节码,先进行编译成机器码后执行
对象创建
事实上,堆解决的是数据存储的问题,即数据怎么做,放在哪里,而栈解决的是程序的运行问题,即程序如何执行或者说如何处理数据,而方法区则是辅助堆栈的永久区,解决堆栈信息的产生,比如我们现在要创建一个student类实例
其中栈中存放的是对象的引用
对象创建详细过程
首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有就执行下一步
检查通过后,虚拟机会为新生的对象分配内存,主要由两种内存分配策略,一种是指针碰撞,一种是空闲列表。所谓指针碰撞就是把Java堆中的内存一分为二,一边是所有用过的内存(这部分内存不能被分配了),一边是空闲的内存,是可以被分配的,这样的话,在可用于不可用的内存之间会有一个分割点指示器,那么为对象分配内存实际上就是从这个分界点指示器往空闲内存的一边拨动一段空间就可以了。而空闲列表则没有这个假设,已使用的内存与空闲内存可能是交叉在一起的,那么使用指针碰撞的方式分配内存就会产生问题,但是虚拟机维护着一张列表,这张列表记录了哪些区域的内存是可用的,那么在分配内存的时候就从选择可以容纳对象要求大小的内存区域分配给这个对象
虚拟机将分配到的内存空间都初始化为零(不包括对象头),这里的初始化不同于我们在Java中利用构造函数进行初始化的过程,这里的初始化时保证Java的一些原生数据类型在不重新赋值的时候就可以直接使用,程序在使用这些对象的时候可以直接使用零值。
接下来,虚拟机要对对象一些必要的设置,进行这些设置的目的是可以知道这些对象是哪个类的实例、对象的哈希码、对象的GC分代年龄等信息,这些信息都存储在对象头中。
上面这些工作完成之后,从虚拟机的角度看,一个对象已经构造完成,但是从开发人员的角度看,还需要进行new对象之后初始化,接着执行init方法,到这里一个对象才算真正创建完毕
内存布局
主要包括三部分的信息:对象头、实例数据和对齐填充
对象头又包括两部分信息,第一部分用于存储对象自身的运行时数据,比如哈希码、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等;第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据部分是对象真真好存储的有效信息,也是程序代码中所定义的各种类型的字段内容
第三部分不是必然存在的,只是起到占位符的作用,因为HotspotVM规定对象的起始地址必须是8字节的整数倍。所以很有可能以上两部分的大小不够8字节的整数倍,那么这个字段就可以发挥作用了。
对象的访问
主要是通过Java栈中的reference数据,通过这个reference数据只是一个指向对象的引用,那么对象的访问方式就可以不同。目前主流的对象访问方式主要由句柄和直接指针两种。通过句柄访问的话,会在Java堆中划分出一块句柄池,句柄池中句柄存放了对象的实例数据和类型指针,而reference数据则存放了句柄的地址引用。使用直接指针访问对象,那么reference数据存放的就是对象的地址
使用句柄访问的最大好处是reference中存储的稳定的句柄地址,当对象的地址发生了改变可以不用去关心。而直接指针的最大好处是速度更快,在于节省了一次指针定位的时间
OOM异常分类
Java堆溢出
虚拟机栈和本地方法栈溢出
方法区和运行常量池溢出
本机直接内存溢出