1、JVM启动时,会申请内存空间,按功能划分,如下图
2、Java的类加载过程
一个Java文件从编码完成到最终执行,一般主要包括两个过程:编译和运行 。
编译,即把我们写好的java文件,通过javac命令编译成字节码,也就是我们常说的.class文件。
运行,则是把编译生成的.class文件交给Java虚拟机(JVM)执行。
而我们所说的类加载过程(类的加载)即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。
也就是说JVM不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类时才会加载,且只加载一次。
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括如下七个阶段:
3、 加载(查找并加载类的二进制数据)
这个阶段通常也被称作“装载”,在这个阶段中,类加载器通过“类全名”来获取定义此类的二进制字节流到JVM内部,然后将字节流中的的静态存储结构转换为方法区的运行时数据结构,最后在java堆中生成一个代表这个类的java.lang.Class对象,也就是在堆内存中开辟空间。
可以理解为JVM只规定了“通过一个类的全限定名来获取定义此类的二进制字节流”,但是并没有说从哪里加载,我们可以通过.class文件中加载,也可以通过网络加载任何地方的字节码。
类加载器(用来加载类),从Java虚拟机的角度看,只有两种不同的类加载器:
启动类加载器(Bootstrap ClassLoader):用C++实现,是虚拟机自身的一部分;
所有其他的类加载器:用Java语言实现,独立于虚拟机外部,都继承自抽象类java.lang.ClassLoader;
类加载器(用来加载类),从Java开发人员看,类加载器可分为3种:
1.启动类加载器(Bootstrap ClassLoader):负责加载<\JAVA——HOME>\lib目录中的并且可以被虚拟机识别的;
2.扩展类加载器(Extension ClassLoader):负责加载<\JAVA_HOME>\lib\ext目录中的所有类库,开发者可以直接使用扩展类加载器;
3.应用程序类加载器(Application ClassLoader):它是ClassLoader中的getSystemClassLoader()方法的返回值,所以也称它为系统类加载器。他负责加载用户类路径(ClassPath)上所指定的类库
二进制字节流:字节码来源,一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译生成的字节流。
4、验证(确保被加载类的正确性)
这一步主要目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。
验证阶段主要包含如下四个检验过程:
(1)文件格式验证:验证class文件格式规范。
(2)元数据验证:这个阶段是对字节码描述的信息进行语义分析,以保证描述的信息符合java语言规范要求。
(3)字节验证码:进行数据流和控制流分析,这个阶段对类的方法体进行校验分析,这个阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。
(4)符号引用验证:符号引用中通过字符串描述的全限定名是否能找到对应的类、符号引用类中的类,字段和方法的访问性(private、protected、public、default)是否可被当前类访问。
5、准备(为类的静态变量分配内存,并将其初始化为默认值)
这一步中,主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值。
初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值,例如public static int value = 10,这一步设置后 value 的值被设置为0。
6、解析(把类中的符号引用转换为直接引用)
符号引用:符号引用是一组符号来描述所引用的目标对象,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标对象并不一定已经加载到内存中。
直接引用:直接引用可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。
7、初始化(为类的静态变量赋予正确的初始值)
在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,例如public static int value = 10,这一步初始化后,value 的值就会被设置为10.
从另一个角度来讲:初始化阶段是执行类构造器<clinit>()方法的过程。在以下四种情况下初始化过程会被触发执行:
(1)遇到new、getstatic、putstatic或invokestatic这四个字节码指令时,如果类没有进行过初始化,则需先触发其初始化。
(2)使用java.lang.reflect包的方法对类进行反射调用的时候。
(3)当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先触发其父类的初始化。
(4)jvm启动时,用户指定一个执行的主类(包含main方法的那个类),虚拟机会先初始化这个类。