在学习JVM之前,先了解Java程序执行过程和Java为什么跨平台,有助于我们对JVM的理解。
Java的执行过程:
1. 编译通过Java命令,调用JDK编译器将*.java源文件编译成*.class字节码文件。
2. 执行通过java命令,调用JVM虚拟机,执行*.class字节码文件。
Java程序的跨平台原因:
1. 通用字节码文件JDK编译器在不同平台编译相同源文件的字节码文件都是相同的。
2. 不同版本JVM虚拟机不同平台执行字节码文件会用到不同的JVM虚拟机,将字节码翻译成当前平台可以执行的机器码指令。
JDK、JRE、JVM:
JDK:Java开发工具包,提供javac编译器、jheap、jconsole等监控工具。
JRE:Java运行环境,提供Class Library 核心类库+JVM。
JVM:Java虚拟机,用于运行Java应用程序。
JVM的组成结构:
JVM由JVM解释器、JIT解释编译器、类加载器、运行时数据区、垃圾回收器、本地方法库等部分组成。
1. JVM的执行方式
JVM以解释+编译混合模式,执行字节码文件。
解释执行为主,执行过程中,JVM将每个字节码文件中的每个指令,通过解释器转换成当前平台可以识别的机器码,然后交给CPU执行。
为了提高执行效率,JVM还会在运行期间,JVM通过热点代码的统计分析,识别高频的方法调用,循环体、公共模块等,当超过阈值时,JVM会基于JIT即时编译器(just-in-time compiler )将热点代码转换成机器码,直接交给CPU 执行,提高执行效率。
2. JVM类加载机制
程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。
2.1 类加载过程包含加载、验证、准备、解析和初始化。
1. 加载
这里的「加载」是「类加载」过程的一个阶段,「类加载」描述的是整个过程,「加载」仅表示「类加载」的第一阶段。
在加载阶段,JVM主要完成以下3件事:
1.通过类的完全限定名称获取定义该类的*.class 字节码文件的二进制字节流。
2.将该字节流表示的静态存储结构转换为Metaspace元空间区的运行时存储结构。
3.在内存中生成一个代表该类的Class对象,作为元空间区中该类各种数据的访问入口。
2. 验证
验证是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。如果验证到输入的字节流不符合 Class 文件格式的约束,虚拟机就会抛出一个 java.lang.VerifyError 异常或其子类异常。
验证阶段大致完成 4 个阶段的检验动作:
文件格式验证
元数据验证
字节码验证
符号引用验证
3. 准备
准备阶段是正式为类变量(static 修饰的变量)分配内存并设置类变量初始值的极端,这些变量所使用的内存都将在方法区中进行分配。注意此时进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。
4. 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
符号引用:只包含语义信息,不涉及具体实现,以一组符号来描述引用目标,是字面量;符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
直接引用:与具体实现息息相关,是直接指向目标的指针;直接引用是可以直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。
5. 初始化
初始化阶段,才真正开始执行类中定义的 Java 程序代码(或者说是字节码)
在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源。
也就是我们通常理解的赋初始值以及执行静态代码块。
2.2 类加载器
在类加载过程的加载阶段,通过类的完全限定名,获取描述类的二进制流的实现类,被称为"类加载器"。
从JVM虚拟机的角度来讲,只存在以下两种不同的类加载器:
1.启动类加载器(Bootstrap ClassLoader ),使用C++实现,是虚拟机的一部分;
2.其它类的加载器,使用Java实现,独立于虚拟机,继承自抽象类java.lang.classLoader。
从Java开发人员的角度可以分为:
1.启动类加载器(Bootstrap ClassLoader):负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。
2.扩展类加载器(Extension ClassLoader):负责加载 <JAVA_HOME>\lib\ext 目录中的,或者被 java.ext.dirs 系统变量所指定的路径中的所有类库。
3.应用程序类加载器(Application ClassLoader):也称为系统类加载器,负责加载用户类路径(ClassPath)上所指定的类库。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
3. 运行时数据区
JVM虚拟机在执行Java程序的过程中会把它管理的内存划分成若干个不同的数据区域。由方法区、堆区、虚拟机栈、本地方法栈、程序计数器务部分。
JDK 1.8之前分为:线程共享(Heap堆区、Method Area方法区),线程私有(虚拟机栈、本地方法栈、程序计数器)
JDK 1.8之后分为:线程共享(Heap 堆区、MetaSpace 元空间),线程私有(虚拟机栈、本地方法栈、程序计数器)
4. JVM垃圾收集器
分类:
1.按照执行原理分类:
单线程收集器:
Serial垃圾收集器
Serial Old垃圾收集器
多线程收集器:
ParNew垃圾收集器
Parallel Scavenge垃圾收集器
Parallerl Old垃圾收集器
并发收集器:
CMS收集器
G1收集器
2.按照作用区域分类:
新生代:
Serial垃圾收集器
Parallel Scavenge垃圾收集器
ParNew垃圾收集器
老年代:
Serial Old垃圾收集器
Parallerl Old垃圾收集器
CMS收集器
整个Java堆:
G1收集器
具体介绍:
1.Serial垃圾收集器
定义:用于新生代,是单线程收集器,使用复制算法
缺点:单线程完成垃圾回收,在进行垃圾回收时,必须暂停其他所有工作线程,直到垃圾收集结束
2.Serial Old垃圾收集器
定义:用于老年代,是单线程收集器,使用标记整理算法
用途:jdk5.0之前版本与新生代Parallerl Scavenge收集器搭配使用
在老年代中作为CMS收集器的后备垃圾收集方案
3.ParNew垃圾收集器
定义:多线程收集器,使用复制算法,是JVM在Server模式下新生代的默认垃圾回收器
缺点:和Serial一样,多线程进行垃圾回收时也要暂停其他所有工作线程
4.Parallerl Scavenge收集器
定义:多线程垃圾收集器,使用复制算法,用于新生代中
优点:使得程序达到一个可控的吞吐量,高吞吐量能够最高效率利用CPU时间;采用自适应调节策略
5.Parallel Old收集器
定义:用于老年代,使用标记整理算法,是多线程垃圾收集器
用处:能够在老年代提供吞吐量优先的垃圾收集器
6.CMS收集器
定义:CMS(Concurrent mark sweep),用于老年代的多线程垃圾收集器,使用标记清除算法,属于并发执行的垃圾收集器
作用:获取最短垃圾回收停顿时间,从而提高用户体验
过程:
初始标记:标记GC Roots能直接管理的对象,需要暂停所有工作线程
并发标记:进行GC Roots跟踪,和用户线程一起工作,无需暂停工作线程
重新标记:修正在并发标记期间,因用户程序工作导致标记变动的标记记录
并发清除:清楚GC Roots不可达对象,无需暂停工作线程
7.G1收集器
定义:G1(Garbage First)使用标记整理算法
优点:不会产生内存碎片
可以精确控制停顿时间,实现低停顿的垃圾回收
将整个Java堆内存划分为几个固定区域,优先回收垃圾最多的区域