说到JVM必须提及JDK与JRE。首先说一下三者之间的关系。
1. JDK、JRE、JVM的关系
JDK-JRE-JVM.PNG1.1 JDK
JDK is 是 Java Development Kit的缩写. JDK 是一套软件开发环境。它包含 JRE + development tools.
重要的开发工具:
- java: interpreter/loader 解释器
- javac: compiler 编译器
- jar: archiver 打包工具
- javadoc: documentation generator 文档生成器,从源码注释中提取文档。
1.2 JRE
JRE 是 Java Runtime Environment的缩写. 它也称为 Java RTE.
顾名思义 JRE 提供JAVA运行环境 . 包括JVM和JAVA核心类库和支持文件。
重要的核心类库和支持文件:
- jre\lib: 核心类库目录。该目录下重要的类库:
rt.jar: Bootstrap类库(构成Java平台核心API的运行时类)
charsets.jar: 字符转换类库 - jre\lib\ext: 扩展程序(插件等)的默认安装目录。如:localedata.jar 用于现场数据 java.text和java.util。
- jre\lib\security: 存放安全管理文件的目录。包括安全策略(java.policy)和安全性(java.security)文件。
- jre\lib\applet: 包含支持类的小程序jar类库。
- jre\lib\fonts: 平台使用的字体文件。
2. JVM简介
学习过程中看到很多种解释。下面这段较为准确,且通俗易懂。摘录下来直接用
JVM (Java Virtual Machine) is an abstract machine. It is called a virtual machine because it doesn't physically exist. It is a specification that provides a runtime environment in which Java byte code can be executed. It can also run those programs which are written in other languages and compiled to Java byte code.
The JVM performs the following main tasks:
- Loads code
- Verifies code
- Executes code
- Provides runtime environment
JVMs are available for many hardware and software platforms. JVM, JRE, and JDK are platform dependent because the configuration of each OS is different from each other. However, Java is platform independent.
3. JVM的构成
关于JVM的结构图,本想从网上Copy一张。
看了一些博客之后,发现:有些图片不知是什么年代的,早已过时;有些又混入了GC的概念:Young Generation、Old Generation等等。
于是用Visio DIY了一张,希望能表达得比较准确。
3.1 Runtime Data Area 运行时数据区
根据不同的分工,可划分成若干不同的内存区域。
3.1.1 Method Area 方法区
用于存储被虚拟机加载的类信息、常量、静态变量、JIT编译器(Just-In-Time Compiler)编译后的代码数据等。它有一个被人熟知的别名 Non-Heap(非堆)。
JDK 1.7前,经典JVM 用 Permanent Generation(永久代)来实现方法区。由于JVM启动时为Permanent Generation分配了固定的空间。Permanent Generation默认大小为物理内存的1/64。通过 启动参数 -XX:MaxPermSize 可以设定永久代的大小。
JDK 1.7及以后版本可动态分配方法区大小。理论上方法区最大为操作系统为该进程所分配的内存大小。方法区内存溢出风险大大降低了。
Method Area 是线程共享的内存区域。
3.1.2 Heap 堆
Heap为可能是Runtime Data Area中最为程序员说熟知的一块区域。它唯一的作用就是用于存放对象实例。几乎所有对象都在Heap区域分配内存。它是线程共享的内存区域。
GC便主要作用于Heap区域。从GC的角度堆可以分成:Young Generation、Old Generation。Young Generation又可细分为:Eden(伊甸) Pool、From Survivor、To Survivor等.... GC会在后续的文章中做简述,这里不做赘述。
GC角度看Heap(堆).png
3.1.3 VM Stack 虚拟机栈
Java 方法执行的内存模型。线程私有。程序员常说的“堆、栈”中的“栈”指的就是虚拟机栈。
每个方法在执行时都会创建一个Stack Frame(栈帧)用于存储局部变量表、方法出口地址等信息。方法执行的过程对应栈帧在VM Stack中入栈到出栈的过程。
3.1.4 Native Method Stack 本地方法栈
与VM Stack作用类似,都是方法执行的内存模型。最显著的区别在于:VM Stack用于执行Java 方法;Native Method Stack 用于执行Native方法。线程私有。
由于作用相似,部分JVM将VM Stack 与 Native Method Stack合二为一。包含程序员所熟知的Sun HotSpot JVM。
3.1.5 Program Counter Register 程序计数器
由于在大部分多线程场景下线程数量多于处理器的内核数。而一个处理器(内核)同一时间只会执行一个线程中的指令。Java 虚拟机需要为不同的线程轮流分配处理器的执行时间。因此,为了线程切换后能恢复到正确的执行位置。每个线程都需要一个独立(私有)的程序计数器。
程序计数器是一块比较小的内存空间。它可以看做是线程执行字节码位置的指示器。每个线程拥有一个程序计数器。换句话说,它是线程私有的。
3.2 Direct Memory 直接内存
它不是JVM Runtime Data Area的一部分。其大小不受JVM Runtime Data Area大小的限制。而是受物理机(或虚拟机)、操作系统所能分配内存大小的限制。之所以在乎这里提到,在一些特殊的场景下使用Direct Memory 会显著提高执行效率。这里不多说。
3.3 Class Loader 类加载器
用来加载Java类字节码(Java编译器编译生成的)到 JVM中的一种加载器。
JVM提供三个类加载器:Bootstrap ClassLoader、Extension ClassLoader、Application ClassLoader。
3.3.1 Bootstrap ClassLoader
和JVM一样,Bootstrap ClassLoader是用本地代码(一般是C语言)实现的,它负责加载核心JavaClass(即所有java.*开头的类)。
Extension ClassLoader、Application ClassLoader 也是由Bootstrap ClassLoader加载的。
3.3.2 Extension ClassLoader
负责加载扩展的Javaclass(例如所有javax.*开头的类和存放在JRE的ext目录下的类)。Java语言编写。
3.3.3 Application ClassLoader
负责加载应用程序自身的类。Java语言编写。
3.3.4 类加载基本流程
- JVM启动。BootstrapClassLoader 加载java核心类、ExtClassLoader、AppClassLoader也在此时被加载
- ExtClassLoader 加载扩展类库
- AppClassLoader 加载CLASSPATH目录下定义的类。
3.4 Execution Engine 执行引擎
JVM 实现的核心是Execution Engine。不同JDK(Open JDK、HotSpot JDK、JRockit JDK、IBM J9 JDK等等)执行效率的优劣主要取决于Execution engine的好坏。关于Execution Engine的描述,在网上看到一条比较贴切。这直接拿过来用
Execution Engine. The bytecode that is assigned to the runtime data areas in the JVM via class loader is executed by the execution engine. The execution engine reads the Java Bytecode in the unit of instruction. It is like a CPU executing the machine command one by one
4. 多说几句
个人感觉:JVM 与程序员是息息相关的。如果技术上想要进阶。JVM是必修的课程。
JVM相关的重要课题:GC、JVM调优。会在后续的文章中加以介绍。
欢迎大家一起探讨交流。如有能帮到大家的地方,我会感到非常荣幸。
如有错漏或者不准确的地方,也请大家指正。
附录:
- 常用JVM启动参数:
- -Xms 堆内存初始大小。如:
-Xms256m
- -Xmx 堆内存最大大小。如:
-Xmx512m
- -Xmn 新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%
- -Xss JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。
- -XX:NewRatio 新生代与老生代的比例,如
–XX:NewRatio=2
,则新生代占整个堆内存的1/3,老年代占2/3 - -XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10
- -XX:PermSize 永久代(方法区)的初始大小
- -XX:MaxPermSize 永久代(方法区)的最大值
- -XX:+PrintGCDetails 打印 GC 信息
- -XX:+HeapDumpOnOutOfMemoryError 让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析用
网友评论