Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范。
它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。
JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。
两个子系统为:Class loader(类装载)、Execution engine(执行引擎);
两个组件为:Runtime data area(运行时数据区)、Native Interface(本地接口)。
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。
这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。
不同虚拟机的运行时数据区可能略微有所不同,但都会遵从 Java 虚拟机规范, Java 虚拟机规范规定的区域分为以下 5 个部分:
作用 :
当我们调用 Java 命令运行某个 Java 程序时,该命令将会启动一条 Java 虚拟机进程,
首先通过编译器把 Java 代码转换成字节码,在通过类加载器(ClassLoader)再把字节码加载到内存中的方法区,执行引擎将字节码翻译成底层系统指令,再交由 CPU 去执行,而交由 CPU 去执行这个过程中需要调用本地接口(Native Interface)来实现。
类加载分为三步:加载、连接、初始化
加载:是将class文件读入内存并为之创建一个Class对象,存放在JVM的方法区。任何类被使用时系统都会建立一个Class对象。
连接:
(1)检查:检查载入的class文件数据的正确性
(2)准备:给类的静态变量分配存储空间
(3)解析:将符号引用转成直接引用
初始化:
1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
2)如果类中存在初始化语句,就依次执行这些初始化语句。
使用new关键字 | 调用了构造函数 |
---|---|
使用Class的newInstance方法 | 调用了构造函数 |
使用Constructor类的newInstance方法 | 调用了构造函数 |
使用clone方法 | 没有调用构造函数 |
使用反序列化 | 没有调用构造函数 |
虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载。
类加载通过后,接下来分配内存。然后内存空间初始化操作,接着是做一些必要的对象设置,最后执行<init>方法。
为对象分配内存
类加载完成后,接着会在Java堆中划分一块内存分配给对象。内存的分配会根据Java堆是否规整,
有两种方式:
对象的创建在虚拟机中是一个非常频繁的行为,哪怕只是修改一个指针所指向的位置,在并发情况下也是不安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案:
Java
程序需要通过JVM
栈上的引用访问堆中的具体对象。对象的访问方式取决于JVM
虚拟机的实现。
目前主流的访问方式有句柄和直接指针两种方式。
指针: 指向对象,代表一个对象在内存中的起始地址。
句柄: 可以理解为指向指针的指针,维护着对象的指针。句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址),再由对象的指针指向对象的真实内存地址。
如果使用直接指针访问,引用 中存储的直接就是对象地址,那么Java
堆对象内部的布局中就必须考虑如何放置访问类型数据的相关信息。
优势:速度更快,节省了一次指针定位的时间开销。由于对象的访问在Java
中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。HotSpot 中采用的就是这种方式。
Java
堆中划分出一块内存来作为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息,具体构造如下图所示:
优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要修改。
浅拷贝:增加一个指针指向一个已存在的内存地址
深拷贝:增加一个指针并申请一块新的内存,使这个增加的指针指向这个新的内存,
在Java中对于基本数据类型就是深拷贝,对于引用类型区分深拷贝还是浅拷贝
如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。
反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。
Java 堆 和 Java 虚拟机栈 有四个方面的区别:
物理地址的区别:
堆的物理地址分配对对象是不连续的,因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
内存大小的区别:
堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。
栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。
存放内容不同:
堆存放的是对象的实例和数组。因此Java堆更关注的是数据的存储
栈存放:局部变量,操作数栈,返回结果。Java虚拟机栈更关注的是程序方法的执行。
程序的可见度的区别
堆对于整个应用程序都是共享、可见的。
栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
队列和栈都是被用来预存储数据的。
HotSpot VM是绝对的主流。大家用它的时候很可能就没想过还有别的选择,或者是为了迁就依赖了Oracle/Sun JDK某些具体实现的烂代码而选择用HotSpot VM省点心。
Oracle / Sun JDK、OpenJDK的各种变种,用的都是相同核心的HotSpot VM。 从Java SE 7开始,HotSpot VM就是Java规范的“参考实现”。把它叫做“标准JVM”完全不为过。
当大家说起“Java性能如何如何”、“Java有多少种GC”、“JVM如何调优”云云,经常默认说的就是特指HotSpot VM。可见其“主流性”。
JDK8的HotSpot VM已经是以前的HotSpot VM与JRockit VM的合并版,也就是传说中的“HotRockit”,只是产品里名字还是叫HotSpot VM。
这个合并并不是要把JRockit的部分代码插进HotSpot里,而是把前者一些有价值的功能在后者里重新实现一遍。移除PermGen、Java Flight Recorder、jcmd等都属于合并项目的一部分。
不过要留意的是,这里我说的HotSpot VM特指“正常配置”版,而不包括“Zero / Shark”版。Wikipedia那个页面上把后者称为“Zero Port”。用这个版本的人应该相当少,很多时候它的release版都build不成功…
J9是IBM开发的一个高度模块化的JVM。
在许多平台上,IBM J9 VM都只能跟IBM产品一起使用。这不是技术限制,而是许可证限制。 例如说在Windows上IBM JDK不是免费公开的,而是要跟IBM其它产品一起捆绑发布的;使用IBM Rational、IBM WebSphere的话都有机会用到J9 VM(也可以自己选择配置使用别的Java SE JVM)。 根据许可证,这种捆绑在产品里的J9 VM不应该用于运行别的Java程序…大家有没有自己“偷偷的”拿来跑别的程序IBM也没力气管(咳咳
而在一些IBM的硬件平台上,很少客户是只买硬件不买配套软件的,IBM给一整套解决方案,里面可能就包括了IBM JDK。这样自然而然就用上了J9 VM。
所以J9 VM得算在主流里,虽然很少是大家主动选择的首选。
J9 VM的性能水平大致跟HotSpot VM是一个档次的。有时HotSpot快些,有时J9 快些。 不过J9 VM有一些HotSpot VM在JDK8还不支持的功能,最显著的一个就是J9支持AOT编译和更强大的class data sharing。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。