1. JVM从编译到执行
1.1 Java的执行过程
过程:Java 文件->编译器>字节码->JVM(识别class文件)->机器码。
(任何class文件都要经过类加载器,一般情况情况下走字节码解释器,有时会走JIT编译器)
1.2 JVM的作用
作用:从软件层面屏蔽不同操作系统在底层硬件和指令的不同。 (宏观方面)
JVM 是一个虚拟化的操作系统, 类似于 Linux 或者 Windows 的操作系统, 只是它架在操作系统上, 接收字节码也就是 class, 把字节码翻译成操作系统上的机器码且进行执行
1.3 JVM的两大特性
- 跨平台性
不同的jdk版本地址:https://www.oracle.com/java/t... - 语言无关性
2. JVM的知识体系
JVM 是一个虚拟化的操作系统, 所以除了要虚拟指令之外, 最重要的一个事情就是需要虚拟化内存
- 内存结构、
- 垃圾回收(回收内存)
- 类加载(加载到内存)
- 性能调优(内存优化)
- JVM 自身优化技术(内存优化)
- 执行引擎(与内存密不可分)
- 类文件结构(内存的设计)
- 监控工具等(监控内存)
- ......
2.1 JVM内存区域(重点)
2.1.1 运行时数据区域
定义:Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域
内存划分:堆、 程序计数器、 方法区、 虚拟机栈和本地方法栈等
按照与线程的关系:
私有线程(一个线程拥有单独的一份内存区域)
共享线程(被所有线程共享, 且只有一份)
不是运行时数据区:
直接内存:又称对外内存,会被频繁使用,没有被虚拟机化的操作系统上的其他内存,JVM 是借助一些工具使用
2.1.2 JAVA方法的运行与虚拟机栈
虚拟机栈
定义:线程运行 java 方法所需的数据, 指令、 返回地址。(一个线程是可以运行多个方法的)
示例:
package ex1; public class MethodAndStack { public static void main(String[] args) { A(); } public static void A(){ B(); } public static void B(){ C(); } public static void C(){ System.out.println("over"); } }
上述示例由于main方法调用A,A调用B,B调用C,这些方法的调用都运行在main方法所在的线程,同时在执行每个方法的时候都会打包成一个栈帧。比如main方法运行时,会打包一个栈帧送入到虚拟机栈。
C方法运行完了,C方法出栈,接着B运行完了,B出栈、接着A运行完了,A出栈,最后main方法运行完了,main 方法这个栈帧就出栈了
- 栈的数据结构:先进后出(FILO)的数据结构
虚拟机栈是基于线程的:线程私有,一个线程对应一个虚拟机栈
方法运行时的内存区域
虚拟机栈的大小:缺省为1M,可用参数 –Xss 调整大小,例如-Xss256k。参数官方文档(JDK1.8): https://docs.oracle.com/javas...
栈溢出问题:
虚拟机栈这个内存也不是无限大, 它有大小限制, 默认情况下是 1M。
如果不断的往虚拟机栈中入栈帧(调用方法), 但是就是不出栈的话, 那么这个虚拟机栈就会爆掉。示例1:
package ex1; public class MethodAndStack { public static void main(String[] args) { A(); } public static void A(){ A(); } }
无限递归后输出异常信息:Exception in thread "main" java.lang.StackOverflowError
栈帧(核心):每个 Java 方法被调用的时候, 都会创建一个栈帧, 并入栈。 一旦方法完成相应的调用, 则出栈。
栈帧大体包含的四个区域:
- 局部变量表:存放我们的局部变量的(方法中的变量)(8大基本类型+对象地址)
- 操作数栈(最核心的):存放 java 方法执行的操作数的, 它就是一个栈。执行引擎(类比CPU)的一个工作区域,对应操作系统的缓存, 操作数栈本质上是 JVM 执行引擎的一个工作区, 也就是方法在执行, 才会对操作数栈进行操作,如果代码不不执行,操作数栈其实就是空的
- 动态连接:Java 语言特性多态(此处略过)
- 返回地址:正常返回(调用程序计数器中的地址作为返回)、 异常的话(通过异常处理器表<非栈帧中的>来确定)
栈帧执行对内存区域的影响
在 JVM 中, 基于解释执行的这种方式是基于栈的引擎, 这个说的栈, 就是操作数栈。
总结:虚拟机栈就是用来存储线程运行方法中的数据的。
一个线程对应一个虚拟机栈
一个方法对应一个栈帧
程序计数器
较小的内存空间, 当前线程执行的字节码的行号指示器; 各线程之间独立存储, 互不影响。
由于 Java 是多线程语言, 当执行的线程数量超过 CPU 核数时, 线程之间会根据时间片轮询争夺 CPU 资源。 如果一个线程的时间片用完了, 或者是其它原因导致这个线程的 CPU 资源被提前抢夺, 那么这个退出的线程就需要单独的一个程序计数器, 来记录下一条运行的指令。
另外:程序计数器也是 JVM 中唯一不会 OOM(OutOfMemory)的内存区域
运行时数据区及 JVM 的整体内存结构
- 本地方法栈(含有native关键字)
方法区:方法区主要是用来存放已被虚拟机加载的类相关信息, 包括类信息、 静态变量、 常量、 运行时常量池、 字符串常量池等。方法区是 JVM 对内存的“逻辑划分”。
1)符号引用
2)常量池与运行时常量池
3) 元空间堆:堆是JVM 上最大的内存区域, 我们申请的几乎所有的对象, 都是在这里存储的。 我们常说的垃圾回收, 操作的对象就是堆。堆空间一般是程序启动时, 就申请了, 但是并不一定会全部使用。 堆一般设置成可伸缩的
堆大小参数:
-Xms: 堆的最小值;
-Xmx: 堆的最大值;
-Xmn: 新生代的大小;
-XX:NewSize; 新生代最小值;
-XX:MaxNewSize: 新生代最大值;
例如- Xmx256m- 直接内存(堆外内存)(文中有描述):直接内存主要是通过 DirectByteBuffer 申请的内存, 可以使用参数“MaxDirectMemorySize” 来限制它的大小
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。