Java教程

Java 虚拟机

本文主要是介绍Java 虚拟机,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

JVM-01

  • Java虚拟机家族
  • Java 内存区域(JVM 运行时数据区域)
  • HotSpot 对象创建
  • 对象的内存布局
  • 对象的访问定位
  • OutOfMemoryError 异常

Java虚拟机家族

  1. Sun Classic VM : JDK 1.0 ,Sun Classic VM 出现,但是这款虚拟机只能以纯解释器的方式来执行Java 代码,如果要使用即时编译器必须外挂,但是如果使用了外挂即时编译器的话,即时编译器会完全接管虚拟机的执行系统,解释器就不在工作了。(如果完全使用编译执行的话,编译器就必须对所有的 字节码进行编译,无论他们的执行频率是否都具有编译的价值,因此会影响程序的响应时间)
  2. Exact VM :为解决上述的问题,提升运行效率。JDK 1.2,Exact VM 出现,它的编译执行系统已经具有了现代高性能虚拟机的雏形,如热点探测,两级即时编译,编译器和解释器混合工作的模式。Exact VM 因使用准确式内存管理(exact memory management) 得名,虚拟机能够知道内存中某个位置处的数据具体是什么类型(例如,内存中有一个32bit 的整数123456,虚拟机能够区分它是一个指向地址“123456”的引用,还是一个数值为123456的整数),
    因此,Exact VM 可以抛弃之前 Sun Classic VM 所使用的基于句柄的对象查找方式。
  3. HotSpot VM :如它的名称所示,Hotspot 指的就是他的热点代码探测技术(通过执行计数器找出最具有编译价值的大码,然后通知 即时编辑器以方法为单位进行编译)。
    HotSpot 虚拟机中含有两个即时编译器,分别是
    1),客户端编译器(C1):编译耗时端,但是输出代码优化程度低。
    2),服务端编译器(C2):编译耗时长,但是输出代码优化质量也更高。
    他们会在分层编译机制下雨解释器互相配合共同构成HotSpot 的执行子系统。

Java 内存区域(JVM 运行时数据区域)

线程私有:

  1. 程序计数器 : 一块较小的内存区域,可以看作是当前线程所执行的字节码的行号指示器。它具有两个作用分别是
    1),字节码解释器通过改变这个计数器的值来选取下一天需要执行的字节码指令。
    2),为了在线程切换后能够恢复到正确的执行位置,(Java 虚拟机的多线程是通过线程轮流切换,分配处理器时间的方式来实现的,在任一时刻,一个处理器都只会执行一条线程中的指令)。
  2. Java 虚拟机栈 :描述的是Java 方法执行的线程内存模型,每个方法执行的时候,Java 虚拟机都会同步创建一个栈帧(Stack Frame),其中存储的数据有
    1),局部变量表 :定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量 (这些数据类型包括各种基本数据类型,对象引用(reference) 以及 return Address 类型)。
    这些数据类型在局部变量表中的寻存储空间以局部变量槽(slot)来表示,VM 会为局部变量表中的每一个slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值。
    **如果当前方法是由构造方法或者实例对象创建的,那么该对象引用 “this” 将会放在索引为0的slot处。
    2),操作数栈: 在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈或出栈、主要用于保存计算机过程的中间结果,同时作为计算过程中变量临时的存储空间。
    3),动态链接:指向运行时常量池的引用。动态链接的作用就是为了将这运行时常量池中的符号引用最终转换为调用方法的直接引用
    4),返回地址: 存放调用该方法的程序计数器的值。

扩展:class 字节码文件的常量池中保存着大量的符号引用(在类加载过程中 java 代码的各种变量,方法转化为符号引用保存其中),字节码中的方法调用指令就以指向常量池的引用作为参数。
a. 静态链接(早期绑定):当一个 字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知(非虚方法,静态方法、私有方法、final方法、实例构造器、父类方法(super调用)都是非虚方法),且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接
b. 动态链接(晚期绑定):如果被调用的方法 (虚方法)在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接,体现了多态性。

  1. 本地方法栈 : 类似Java虚拟机栈为执行Java方法(也即是字节码)服务,本地方法栈为执行本地(Native)方法服务。

线程共享:

  1. Java 堆 :所有的对象实例以及数组都在堆上分配内存。可以是物理上不连续的的内存空间,但在逻辑上是连续的。
  2. 方法区 : 用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等,方法区是一种规范,永久代和元空间都是其具体实现。

扩展
1.类型信息:
这个类型的完整有效名称(全名=包名.类名)
这个类型直接父类的完整有效名(对于interface或是java. lang.Object,都没有父类)
这个类型的修饰符(public, abstract, final的某个子集)
这个类型直接接口的一个有序列表
2.运行时常量池:存放编译时生成的各种字面量和符号引用
3.字面量:比较接近Java语言层次的常量概念,如文本字符串、被声明为final的常量值等。
4.符号引用则属于编译原理方面的概念,包括下面三类常量:
1)、类和接口的全限定名
2)、字段的名称和描述符
3)、方法的名称和描述符

HotSpot 对象创建

  1. 当JVM遇到一条字节码 new 指令时,首先会检查这个指令的参数是否能够在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载,如果没有则执行相应的类加载过程。
  2. 类加载检查通过后,JVM 开始为新生对象分配内存

扩展1
1.对象内存分配方法有两种:
1)指针碰撞:Java 堆中的内存是规整的。
2)空闲列表:Java 堆中的不是规整的,已使用的内存和空闲的内存相互交错在一起。

扩展2
在并发情况下,仅仅修改一个指针所指向的位置也并不是线程所安全的,可能出现正在给对象A分配内存时,指针还没来得及修改,对象B又同时使用了原来的指针分配内存的情况。
解决方案有两种:
1) 一种是对分配内存的动作进行同步处理,实际上虚拟机采用 CAS(Compare and Swap)+ 失败重试的方式 保证操纵的原子性。
2)把内存分配的动作按线程划分在不同的空间中进行,即每个线程在Java 堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB),那个线程分配内存,就在那个线程的 本地线程分配缓冲区中分配。

  1. 内存分配完之后,JVM 将分配到的内存空间(不包括对象头)初始化为零值。
  2. JVM 对对象头进行设置
  3. 最后new 指令之后会接着执行() 方法,按照程序员的意愿对对象进行初始化。

对象的内存布局

对象在堆内存中的存储布局可以划分为三个部分:

  1. 对象头(Header):主要存储两类信息:1),对象自身的运行时数据(哈希码,GC 分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。 2),类型指针,即对象指向它的类型元数据的指针,用来确实能够该对象是那个类的实例。
  2. 实例数据(Instance Data) : 对象真正存储的有效信息,即我们在程序代码中定义的各种类型字段。
  3. 对齐填充(Padding):占位符,HotSpot VM 要求对象的起始地址必须是8字节的整数倍。即对象的大小必须是8字节的整数倍。

对象的访问定位

  1. 句柄:好处:引用(reference)中存的是稳定的句柄地址,在对象移动时,只会改变句柄中的实例数据指针
  2. 直接地址:好处:访问速度快,节省了一次指针定位的时间(HotSpot VM 主要使用这种方法)。

OutOfMemoryError 异常

除程序计数器以外,虚拟机内存的其他几个运行时数据区域都有发生 OOM 异常的可能

.

这篇关于Java 虚拟机的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!