java1.7和java8的jvm存在差异,本文先按照《深入理解java虚拟机》的讲解内容总结,并将java8的改变作为附录放在文末
图:java虚拟机运行时数据区
概念与作用:
一块较小的内存空间,可以看作时当前线程执行字节码的行号指示器,字节码解释器通过控制改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖程序计数器完成。
线程私有的
为什么要设计成线程私有的:
java多线程的实现方式:
java多线程是通过线程轮流切换,分配处理器执行时间的方式实现的,在任何一个确定的时刻,一个处理器(多核处理器来说是一个内核)都只会执行一个线程中的指令。
为了线程切换之后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储
线程正在执行一个java方法,程序计数器记录的是正在执行的虚拟机字节码指令地址,如果是native方法,这个计数器的值为空
1.为什么:
程序计数器存放的是Java字节码的地址,而native方法的方法体是非Java的,所以程序计数器的值才未定义。
2.native方法执行结束怎么确保下一次的执行位置
这是因为每个Java线程(这个说话优点绝对,至少HotSpot是这样的)都直接映射到一个OS(操作空间)线程上执行。所以native方法就在本地线程上执行,无需理会JVM规范中的程序计数器的概念。native方法执行后会退出(栈帧pop),方法退出返回到被调用的地方继续执行程序。
唯一在Java
虚拟机规范中没有规定任何OutOfMemoryError
情况区域。
联想斗破苍穹炼丹理解java虚拟机栈
1.药方:编写的java代码
2.药鼎:操作数栈
3.储物戒指:局部变量表
i++ 反编译的文件 iloal_1(从局部变量表中的1位置加载值到操作数栈栈顶),iinc1 1(将局部变量表中的数值加1)
这也是就为什么i++先使用后自加的愿意,改变了局部变量中的值,但是操作数栈中的值时改变之前的值
++i 反编译的文件是iinc1 1 (将局部变量表中的数值加1)后iloal_1(从局部变量表中的1位置加载值到操作数栈栈顶)
这就是为什么++i是先自己后使用的原因,先改变局部变量表中的值,后将自加的值加入到操作数栈
这也是i++ ++i线程不安全的原因,想象i++只做完第一步,加入到了操作数栈,后被其他线程抢占,这个时候还没有写回
概念和作用:描述了java方法执行的线程内存模型,每个方法被执行的时候,java虚拟机都会同步的创建一个栈帧用于存储局部变量表,操作数栈,动态连接,方法出口等信息。 每一个方法执行完毕的过程都对应一个栈帧从虚拟机栈中入栈到出栈的过程。
局部变量表(局部变量数组或本地变量表):
存放编译器可知的java虚拟机基本数据类型(boolean,byte,char,short,int,float,long,double),对象引用(reference类型,并不等同对象本身,可能是指向对象起始位置的引用指针,也可能是指向一个代表对象的句柄,或者其他与次对象相关的位置)和retrunAddress(指向一条字节码指令的地址)
1.由于局部变量是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题.
2.局部变量表中变量只在当前方法调用中有效,在方法执行中,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程.当方法随着栈帧的销毁,局部变量表也随之销毁.
操作数栈:主要用于保存计算过程中的中间结果,同时作为计算过程中变量临时的存储空间。
return address:方法返回
正常完成出口是指方法正常完成并退出,没有抛出任何异常(包括Java虚拟机异常以及执行时通过throw语句显示抛出的异常)。如果当前方法正常完成,则根据当前方法返回的字节码指令,这时有可能会有返回值传递给方法调用者(调用它的方法),或者无返回值。具体是否有返回值以及返回值的数据类型将根据该方法返回的字节码指令确定。
异常完成出口是指方法执行过程中遇到异常,并且这个异常在方法体内部没有得到处理,导致方法退出。
1.无论方法采用何种方式退出,在方法退出后都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在当前栈帧中保存一些信息,用来帮他恢复它的上层方法执行状态。
2.方法退出过程实际上就等同于把当前栈帧出栈,因此退出可以执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压如调用者的操作数栈中,调整PC计数器的值以指向方法调用指令后的下一条指令。
动态连接
每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。在Java源文件被编译到字节码文件时,所有的变量和方法引用都作为符号引用(Symbilic Reference)保存在class文件的常量池里。
比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
异常:线程请求的栈深度大于虚拟机允许的深度:StackOverflowError,如果java虚拟机栈容量可以动态扩展,扩展无法申请到空间是抛出OutOfMermoryError(hotspot不允许动态扩展)
概念和作用:
可以使用-Xmx和-Xms来设定java堆的大小,如果超过可以使用的大小抛出OutOfMermoryError
新生代,老年代,永久代,Eden等名词这些区域划分知识一部分垃圾收集器的设计风格,并不意味着有具体的的内存布局
并不属于Java虚拟机中定义的内存区域
NIO基于通道,与缓冲区的io方式,可以使用navtive 函数直接分配堆外内存,通过存储与java堆里面DirectByteBuffer对象作为这块内存的引用进行操作,避免java堆和native堆中来回复制数据
各个内存之和大于物理内存的时候抛出OutOfMermoryError
当虚拟机执行到new指令时,收件检查这个指令的参数是否在常量池中定位到一个类的符号引用
检查这个符号引用代表的类是否已被加载、解析和初始化过,如果没有先执行类加载过程
类加载检查通过后,虚拟机为新生对象分配内存,对象所需的内存在类加载之后便可以完全确定(分配内存=把虚拟机的一块确定大小的内存从java堆中划分出来)
1.假如java堆中内存绝对规整,空闲和使用过的内存分别放在两边,中间放着一个指针作为分解分配一段和对象大小相同的内存————指针碰撞
2.如果内存并不规整,那么虚拟机需要维护一个列表,记录那些内存块可以用,找到可用的内存分配给对象,更新列表————空闲列表
3.如果虚拟机java堆的收集器具备空间压缩整理能力使用指针碰撞,基于清除算法的收集器,其堆使用空闲列表(Serial,parnew具备压缩处理能力,CMS基于清除算法)
内存分配完成,虚拟机给分配到的内存空间(不包括对象头)初始化为零值
进行必要的设置:该对象是哪个类的实例,如何找到类的信息,对象的hashcode(在调用hashcode的时候才计算),对象的GC分代年龄存放于对象头中(指此一个新对象已经产生,java程序的构造函数才刚刚开始)
接下来按照构造函数的内容进行对象属性的赋值,执行剩余逻辑,一个真正可以用的对象创建出来
对象位于堆,对象的引用位于虚拟机栈
指在给对象A分配内存时,指针还没来得及修改,这是给B分配内存指针还位于原来的位置
对象在内存中的布局主要分为三部分:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)
Mark word:用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳(如果对象是一个数组对象,对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小,根据对象的类型和数组的大小计算对象的大小)等等。
mark word是一个动态的结构,便于极小的空间存储大量的数据(其中使用2个比特记录锁状态,比如01指未锁定)
Klass Pointer:类型指针,是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
如果对象有属性字段,则这里会有数据信息。如果对象无属性字段,则这里就不会有数据。根据字段类型的不同占不同的字节,例如boolean类型占1个字节,int类型占4个字节等等,无论是从父类继承下来的字段还是子类自己定义的字段,都必需记录下来。
这部分的存储顺序受到虚拟机分配策略和java代码中定义的字段顺序有关,默认的分配顺序是long/double,int,short,char,byte/boolean,oop(对象指针,比如string类型,存储指向对象的地址),大小相同的分配在一起,如果虚拟机设置允许(默认便允许)子类窄变量允许插入到父类变量的间隙中(压缩空间)
Hotspot虚拟机自动内存管理系统要求对象的起始地址必须是8字节的,如果对象实例数据部分不是8字节的整数倍使用对齐填充
字段内存对齐的其中一个原因,是让字段只出现在同一CPU的缓存行中。如果字段不是对齐的,那么就有可能出现跨缓存行的字段。也就是说,该字段的读取可能需要替换两个缓存行,而该字段的存储也会同时污染两个缓存行。这两种情况对程序的执行效率而言都是不利的。其实对其填充的最终目的是为了计算机高效寻址
java程序定位堆中的对象主要使用栈中的reference数据,访问到堆中对象的具体方式有主要有两种:句柄和直接指针(hotspot采用此)
java堆中分配一块作为句柄池,存储对象的对象的句柄,包含实例数据和类型数据的具体地址信息
优点:对象被移动(垃圾收集器常移动对象,保证内存的规整)的时候,只需要改变句柄中的实例的数据,栈中的reference不需要改变
手机扫一扫
移动阅读更方便
你可能感兴趣的文章