JVM专栏-JVM概述和类加载子系统
前言:在面试中,我们常被问及
JVM调优经验
、JVM内存区域知识
以及常用的JVM调优命令
。对于资深开发者而言,对JVM的不熟悉可能会影响高薪工作的获取。此外,JVM知识对于排查生产环境中的死锁
、内存溢出
、内存泄漏
等问题至关重要。本系列旨在从基础到深入,逐步加深对JVM的理解。相信坚持和收获总是成正比的,只愿今天的我比昨天的我更加努力一点,坚持的更久一点。
本篇是JVM专栏的第一篇,主要讲解以下内容:
- JVM的概念
- JAVA技术体系
- JVM架构
- 类加载子系统
一 JVM基本常识
1.1 什么是JVM?
我们可以从广义和狭义两个角度来定义JVM,广义上JVM指的是一种规范
,狭义上指的是JDK中的JVM虚拟机
,JVM的实现是由各个厂商来做的,比如现在流传最广泛的是Hotspot,其他公司也都有各自的实现,比如BEA公司的JRocket、IBM的J9 JVM、Zing JVM、taobao JVM。从广义上讲Java
、Kotlin
、Clojure
、JRuby
、Groovy
等运行于Java虚拟机上的编程语言及其相关的程序都属于Java技术体系中的一员。
1.2 Java技术体系
JAVA技术体系主要包括如下四个方面。
-
JAVA程序设计语言
-
JAVA类库API
-
第三方类库(比如Google、Apache)
-
Java虚拟机:各种硬件平台上的Java虚拟机实现
其实JVM相当于一个宿主,而我们的Java程序就是寄生在宿主体内的程序。
1.3 JVM架构
如上图JVM整体架构包括三大部分:
- 类加载子系统:负责加载我们的class文件
- 运行时数据区:负责存放各种对象和数据
- 执行引擎:按照逻辑执行字节码
对于我们开发来说,我们应该要知道类加载子系统是如何将class文件加载到内存生成一个字节码Class对象的,以及程序运行期间,运行时数据区的分布,这对于进行程序调优来说都是非常关键的。
二.类加载子系统
2.1 什么时候会加载类
大家一块思考一下,我们平时在开发工具中开发的java文件,是如何部署道服务器上运行的,还有这些类难道都是一次性加载的吗?当时不是,JVM加载类是以按需加载的方式进行的,一般来说类加载的时机如下:
- new关键字创建对象时。
- 程序第一次使用类的静态成员时。
- 使用反射方式访问类的成员时。
- 执行main函数所在的类时。
public class Student extends SupperStudent{private static int age ;public static void method(){}
}
//Student.age 访问静态成员
//Student.method();访问静态成员
//new Student();new对象
//Class c = Class.forname("com.lx.Student");
2.2 类是如何运行的
我们先定义一个Math类,解释一下Math类是如何运行的。
public class Math {public static final int initData = 10;private int compute(){int a = 1;int b = 2;int c = (a + b) *10;return c;}public static void main(String[] args) {Math math = new Math();math.compute();}
}
当我们在windows上执行main方法的时候,它需要经过以下过程
1.首先会通过java.exe调用底层的jvm.dll创建Java虚拟机
2.创建一个引导类加载器实例(C++实现的,也就是bootstapClassLoader)
3.加载JVM启动器实例sun.misc.Launcher类
4.加载类加载器实例
5.类加载器调用loadClass方法加载我们的类
6.JVM找到Main函数入口进行调用
2.3 类的生命周期
类的生命周期包括加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
-
加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存(方法区)中生成一个代表这个类的java.lang.Class对象,作为方法区访问这个类的各种数据的入口
-
验证:校验字节码文件的正确性
-
准备:给类的静态变量分配内存,并赋予默认值
-
解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所在内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。
-
初始化:对类的静态变量初始化为指定的值,执行静态代码块
什么是
静态链接
和动态链接
?当一个字节码文件被装载进 JVM 内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接
如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接
当类加载完毕后,会在方法区生成一个访问该类成员变量的Class对象
class对象的作用:类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。(这里记住类信息存储在方法区,类实例存储在堆中)
当然方法区也会存在其类成员数据,主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。
注意:主类在运行过程中如果使用到其它类,会逐步加载这些类。jar包或war包里的类不是一次性全部加载的,是使用到时才加载。
我们可以做一个小测试:
/*** 测试动态加载*/
public class TestDynamicLoad {static {System.out.println("*************load TestDynamicLoad************");}public static void main(String[] args) {A a = new A();System.out.println("*************load test************");B b = null; //B不会加载,除非这里执行 new B()}
}class A{static {System.out.println("*************load A************");}public A(){System.out.println("*************initial A************");}
}class B{static {System.out.println("*************load B************");}public B(){System.out.println("*************initial B************");}
}
运行结果:
*************load TestDynamicLoad************
*************load A************
*************initial A************
*************load test************
因为对象b为null,所以不会调用B的静态代码块和构造方法。
当我们把B b = null 改为 B b = new B()时,运行结果:
*****load TestDynamicLoad****
*****load A****
*****initial A****
*****load test****
*****load B****
*****initial B****
因为程序运行期间B被使用到,所以类加载器会加载B,并调用静态代码块和构造方法。
2.4 类的加载途径
一般情况下,我们可以从以下几种方式进行字节码的加载
- Jar/War包
- JSP生成的class
- 数据库中的二进制流
- 网络中的二进制流
- 动态代理产生的二进制流
本章节我们了解了
JVM的概念
、JAVA体系
、JVM架构图
和类加载子系统
。下一章节将深入讲解类加载器的类型、源码和类加载机制