- JVM实际上现在已经成为了跨语言平台,只要按字节码生成规则,都可以在JVM上运行
- JVM规范中最具含金量的虚拟机实现是HotSpot也是JDK1.3至今的默认虚拟机,其他的还有JRockit、IBM J9等
- JDK9之后 G1垃圾回收器完全替代了CMS
- 编译成规范的字节码的好处是:一次编译,到处运行
- 传统意义上的虚拟机如VMware,它是在硬件层面模拟了一个操作系统
- 而JVM是在操作系统之上,解释运行字节码文件的一个细粒度的虚拟机。它没有和硬件直接交互
- 他们的层次是: 硬件——操作系统——JVM——字节码
- 对于Java程序员来说,在JVM自动内存管理机制的帮助下,不需要为每一个new出来的对象配对delete/free去释放内存,不容易出现出现内存泄漏和内存溢出问题。
但是一旦出现了这方面的问题,不了解JVM是怎么使用内存的话就很难排查错误。 - HotSpot是JVM概念的一个实现,最初由Sun开发,现在由Oracle拥有。 还有其他JVM规范的实现,如JRockit , IBM J9等等。
JVM运行时会把它所管理的内存划分为若干个不同的数据区,有的区域随JVM启动而一直存在,有的区域依赖用户线程的启动和结束。它包括了以下几种运行时数据区。
(按相关性和逻辑顺序依次介绍)
首先:JVM的多线程是靠线程轮流切换、分配处理器执行时间来实现的,那么在切换的过程中,如何确定当前线程执行到哪里了呢?
- 线程私有:每个线程都有自己独立的程序计数器,独立存储,互不影响
- 程序计数器占用的内存空间很小
- 字节码解释器就是通过改变程序计数器的值,来选取下一条需要执行的字节码指令
- 分支、循环、跳转、异常处理、线程恢复等功能都依赖于程序计数器
- 线程私有,其生命周期和线程相同
- 每一个方法被执行的时候,JVM都会同步创建一个栈帧
- 栈帧用于存储:局部变量、操作数栈、动态连接、方法出口等信息
- 每一个方法从调用到结束,对应一个栈帧的入栈出栈
虚拟机栈一般都用来指局部变量表,一般包括:
- 8种基本数据类型(其中long和double占用两个局部变量槽Slot)
- 对象引用,如People p = new People()种的p;int[] arr = new int[]{}中的arr
- 因此虚拟机栈的大小是可以完全确定的,如果线程请求的虚拟机栈深度>JVM允许的最大深度,则会报错StackOverflowError即俗称的爆栈,在递归调用的时候较为容易出现爆栈的情况
- 线程私有
- 本地:native:即指编写JDK所用到底层C++库
- 它和虚拟机栈大同小异,区别就是:虚拟机栈调用的是Java方法的字节码,本地方法栈调用的是C++的库
- JVM的HotSpot实现将它们二者合二为一了
一般Java程序员在没有了解JVM的情况下,喜欢把JVM粗略的分为堆和栈,这种分法来自于C++,但JVM规范中的划分更加复杂
- 多线程共享
- 堆是JVM管理内存中最大的一块,几乎所有的对象实例都存放于此
- 对于大的对象(典型如数组)一般都是使用连续的内存空间
- Java堆是可扩展的,一般通过设置-Xmx和-Xms,超过最大限度后报错OutOfMemoryError,即俗称的OOM
- Java堆也叫GC堆,因为其是垃圾收集器管理内存的区域
在JDK8以前,很多程序员喜欢使用HotSpot来开发Java程序,HotSpot将分代设计拓展至了方法区,因此很多人将方法区和永久代混为一谈。此外永久代也出现了很多严重的内存泄露BUG,因此目前被摒弃
对于JRockit、IBM J9虚拟机来说,永久代被完全摒弃,取而代之的是元空间,同理方法区是HotSpot独有的
- 多线程共享
- 准确来说,永久代实现了方法区,在JDK8之后替换为了元空间实现方法区。二者效果相近。
- 对于以前永久代中的静态变量、字符串常量池,目前是放在了堆Heap中
- 元空间使用本地内存也就意味着只要本地内存足够,就不会出现OOM的错误。
- Java1.8的运行时数据区域如图所示。方法区已经不见了踪影,多出来的是叫做元数据区的区域。
Class常量池(又称常量池) :主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References)。
字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等;
符号引用则属于编译原理方面的概念,包括了三种类型的常量:类和接口的全限定名、字段名称和描述符、方法名称和描述符。
2.7*直接内存这个直接内存并不是JVM运行时数据区的一部分,它是JDK4引入的NIO的一种缓冲优化策略:
可以将Native库直接分配到堆外内存,然后通过Java堆中的一个缓冲对象来引用,避免了反复在Java堆和Native堆的复制数据。



