我们可以把Java程序设计语言、Java虚拟机、Java类库这三部分统称为JDK(Java Development Kit),JDK是用于支持Java程序开发的最小环境。
JVM 是 JDK 的一部分,《Java 虚拟机规范》(The Java Virtual Machine Specification) 是平行于《Java 语言规范》(The Java Language Specification)的一套独立的规范,不同的公司对其有不同的实现 (类似于一个接口被不同的类实现)。
虚拟机始祖:Sun Classic/Exact VM- 世界上第一款商用Java虚拟机
- 在JDK 1.2之前是JDK中唯一的虚拟机
- JDK 1.4的时候,Classic VM才完全退出商用虚拟机的历史舞台被HotSpot取代
- 是Sun/OracleJDK和OpenJDK中的默认Java虚拟机,也是目前使用范围最广的Java虚拟机
- 如它名称中的HotSpot指的就是它的热点代码探测技术
- 为全世界使用最广泛的Java虚拟机
- 面对移动和嵌入式市场,Java ME中的Java虚拟机
- JRockit虚拟机曾经号称是“世界上速度最快的Java虚拟机”,是一款一款专门为服务器硬件和服务端应用场景高度优化的虚拟机。JRockit随着BEA被Oracle收购,现已不再继续发展
- IBM J9虚拟机的市场定位与HotSpot比较接近,它是一款在设计上全面考虑服务端、桌面应用,再到嵌入式的多用途虚拟机
- 与特定硬件平台绑定、软硬件配合工作的专有虚拟机
- Apache Harmony是一个Apache软件基金会旗下以Apache License协议开源的实际兼容于JDK 5和JDK 6的Java程序运行平台,它含有自己的虚拟机和Java类库API,用户可以在上面运行Eclipse、Tomcat、Maven等常用的Java程序。
- Dalvik虚拟机并不是一个Java虚拟机,它没有遵循《Java虚拟机规范》,不能直接执行Java的Class文件,使用寄存器架构而不是Java虚拟机中常见的栈架构。但是它与Java却又有着千丝万缕的联系,它执行的DEX(Dalvik Executable)文件可以通过Class文件转化而来,使用Java语法编写应用程序,可以直接使用绝大部分的Java API等。
根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域:
程序计数器- 是当前线程所执行的字节码的行号指示器
- 线程私有: 每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储
- 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)
- 没有OutOfMemory
- 线程私有
- 虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程
- 局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)
- 在《Java虚拟机规范》中,对这个内存区域规定了两类异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常
- 与虚拟机栈所发挥的作用相似,为虚拟机使用到的本地方法服务
- 是虚拟机所管理的内存中最大的一块
- 所有的对象实例以及数组都应当在堆上分配
- Java堆是被所有线程共享的一块内存区域
- 所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)Java堆内存是线程共享的!面试官:你确定吗?
- Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。
- 与Java堆一样,是各个线程共享的内存区域
- 它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
- 在JDK 8完全废弃了永久代的概念,改用元空间来代替。
- 运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
- 运行期间也可以将新的常量放入池:比如String类的intern()方法
- 如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。
- 并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域
- 在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
- 会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制,而导致动态扩展时出现OutOfMemoryError异常
- 当Java虚拟机遇到一条字节码new指令时,检查是否类已加载、解析、初始化,如果没有,则进行类加载
- 在类加载检查通过后,接下来虚拟机将为新生对象分配内存。
- 线程安全解决方案:1、CAS+失败重试保证更新操作的原子性 2、TLAB把内存分配的动作按照线程划分在不同的空间进行
- 内存分配完成之后,虚拟机必须将分配到的内存空间(但不包括对象头)都初始化为零值
- 接下来,Java虚拟机还要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码(实际上对象的哈希码会延后到真正调用Object::hashCode()方法时才计算)、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中。根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
- new指令之后会接着执行()方法,按照程序员的意愿对对象进行初始化,这样一个真正可用的对象才算完全被构造出来
- 在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
- 对象头包括:对象自身的运行时数据(Mark Word),所属类类指针,数组长度(如果是数组对象)
- Java程序会通过栈上的reference数据来操作堆上的具体对象。
- 对象访问方式也是由虚拟机实现而定的,主流的访问方式主要有使用句柄和直接指针两种
- 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
- 互相循环引用的对象无法被回收
- 通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的
- 可作为GC Roots的对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 在方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器
- 所有被同步锁(synchronized关键字)持有的对象
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
- 强引用(Strongly Re-ference):无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象
- 软引用(Soft Reference):只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常
- 弱引用(Weak Reference):当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象
Reference):只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常 - 弱引用(Weak Reference):当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象



