<commit_before>---
layout: post
title: "探究JVM类加载机制"
date: 2015-06-15
categories: Java
---

* content
{:toc}

## 1.类生命周期

JVM类生命周期分为5个阶段:

* 1.加载
* 2.连接
* 3.初始化
* 4.使用
* 5.卸载

### 1.1 加载

> ‘加载’是‘类加载’过程的第一个阶段,主要是将已编译好的class文件(以8字节为基础单位的二进制流,包含一个或多个类)加载到JVM内存区域中;这一阶段主要完成3件事情：
	
* 1.通过类的全限定名(带包路径的类名：cn.ants.loader.test.TestClass)来获取定义此类的二进制字节流;
* 2.将这个字节流所代表的静态存储结构(类中声明的静态变量,静态方法,常量,类自身结构)转换成方法区的运行时数据结构;
* 3.在内存中生成一个代表这个类的java.lang.Class对象,做为这个类的各种数据的访问入口(对于HotSpot虚拟机来讲,Class对象存放在方法区);

通过类的全限定名来获取定义此类的二进制字节流,这句话并没有规定二进制字节流需要从一个class文件中加载,在实际运用中有多种方式，但本质都是得到类编译后的字节码(即二进制字节流)将其加载到JVM内存区域中：

* 1.从压缩包中获取,普遍的就是引入jar包加载类,发布web项目打包成war包;
* 2.从网络中获取,典型的应用是Applet;
* 3.运行时计算生成,比如现在java产生了许多字节码生成,修改工具,比如asm,cglib技术动态生成字节码,或者修改字节码;
* 4.由其他文件生成,典型场景是JSP应用,即由JSP生成对应的Class类(我们将会探究JSP热部署的实现);
* 5.从数据库中读取

相对于类加载过程的其他阶段,非数组类的加载阶段(是加载阶段中获取类的二进制字节流的动作)是开发人员可控性最强的(可控性：开发人员可以选择二种方式的来完成这个操作):
	
* 1.通过使用系统提供的引导类加载器来完成;
* 2.通过自定义的类加载器来完成操作(重写loadClass方法);

对于数组类就不一样,因为数组类型使用JVM创建的(不通过类加载器),但是数组的元素类型最终还是需要靠类加载器去创建;

加载阶段与连接阶段的部分内容是交叉进行的,加载阶段还未完成,可能连接阶段就已经开始,但是两个阶段开始时间依然保持先后的顺序;	

### 1.2 连接

连接阶段分为3个子过程：

1.验证
						
* 验证过程是连接阶段的第一步,主要目的：为了确保class文件的字节流中包含的信息符合虚拟机的要求,不会危害虚拟机的安全;(这里就不一一贴出来了：详见深入理解Java虚拟机)主要验证class文件的规范;

2.准备

* 准备过程是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中分配;
		
	* 1.这里讲的类变量指的是static修饰的变量,不包括实例变量,实例变量跟随对象实例化分配在java堆中;
	* 2.这里说的初始值'通常情况'是数据类型的零值,例如：public static int value = 1;那变量value在准备阶段后的初始值是0而不是1;因为这时候并没有开始执行任何java方法,而把value赋值为1的putstatic指令是存放与构造器clinit方法中,所以赋值操作在初始化阶段中执行;(可以通过javap命令查看class文件的字节码执行指令);(但是也有特殊情况：如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段会被初始化ConstantValue的值,上述例子javac会为ConstantValue值,在准备阶段将ConstantValue值1赋给value);		

3.解析
		
* 解析过程是虚拟机将常量池中符号引用替换为直接引用的过程;(对于此过程实在是没有什么兴趣去探究由于这里涉及许多指令操作,若感兴趣请自行查阅书籍资料;)

### 1.3 初始化

类初始化阶段是类加载过程的最后一个阶段,到了这一步开始真正的执行类中定义的java代码(除了在加载阶段使用自定义类加载器加载类);

简单讲：连接阶段准备过程中已经为类变量进行一次系统要求的初始值的初始化,但是初始化阶段执行类构造器的过程(<clinit>());

* <clinit>()方法是编译器自动收集类中的所以类变量和静态块(static{ })中的语句合并产生的,编译器收集的顺序是由语句在源文件中的位置决定的,PS：静态语句块能对定义在之后的静态变量进行赋值操作,但是不能访问此静态变量;如下面的例子：
		
定义一个Test.java源文件：

	public class Test{
				
		static{
			value = 111;					
			System.out.println(value);
		}
				
		public static int value = 110;
	}
		
	编译报错：非法前向引用;
	使用javap -verbose Test 查看执行指令(字节码)
	Code:
      	stack=1, locals=0, args_size=0
         	0: bipush        111
         	2: putstatic     #2                  // Field value:I
        		5: bipush        110
         	7: putstatic     #2                  // Field value:I
        		10: return
	最后输出value值是110;
	
* <clinit>()方法与类的构造函数(实例构造器<init>()方法)不同,它不需要显示的调用父类构造器(<init>()则需要,类从Object类继承,在init()方法中显示调用java.lang.Object类构造器),虚拟机会保证在子类的<clinit>()方法的执行之前,父类的<clinit>()方法已经执行完毕.父类定义的静态块要优先于子类;
* <clinit>()对于类与接口不是必需的,若没有静态块和类变量赋值操作,编译器不会产生<clinit>()方法;
* 接口中不能有静态语句块,但是依然有变量赋值操作,所以一样会生成<clinit>()方法,但与类不同的是,执行接口的<clinit>方法不需要调用父类的<clinit>方法,当使用到父接口的变量时,才会触发初始化操作,接口实现类也一样;
* 虚拟机会保证<clinit>()方法在多线程环境下被正确的加锁,同步,如果多个线程同时去初始化一个类,那么只会有一个类执行<clinit>方法,其他线程都需要堵塞等待;
* 虚拟机规范严格规定有且只有5种情况必须对类进行“初始化”(加载,连接需在之前进行);<commit_msg>alter post jvm classloader<commit_after>---
layout: post
title: "探究JVM类加载机制"
date: 2015-06-15
categories: Java
---

* content
{:toc}

## 1.类生命周期

JVM类生命周期分为5个阶段:

* 1.加载
* 2.连接
* 3.初始化
* 4.使用
* 5.卸载

### 1.1 加载

> ‘加载’是‘类加载’过程的第一个阶段,主要是将已编译好的class文件(以8字节为基础单位的二进制流,包含一个或多个类)加载到JVM内存区域中;这一阶段主要完成3件事情：
	
* 1.通过类的全限定名(带包路径的类名：cn.ants.loader.test.TestClass)来获取定义此类的二进制字节流;
* 2.将这个字节流所代表的静态存储结构(类中声明的静态变量,静态方法,常量,类自身结构)转换成方法区的运行时数据结构;
* 3.在内存中生成一个代表这个类的java.lang.Class对象,做为这个类的各种数据的访问入口(对于HotSpot虚拟机来讲,Class对象存放在方法区);

通过类的全限定名来获取定义此类的二进制字节流,这句话并没有规定二进制字节流需要从一个class文件中加载,在实际运用中有多种方式，但本质都是得到类编译后的字节码(即二进制字节流)将其加载到JVM内存区域中：

* 1.从压缩包中获取,普遍的就是引入jar包加载类,发布web项目打包成war包;
* 2.从网络中获取,典型的应用是Applet;
* 3.运行时计算生成,比如现在java产生了许多字节码生成,修改工具,比如asm,cglib技术动态生成字节码,或者修改字节码;
* 4.由其他文件生成,典型场景是JSP应用,即由JSP生成对应的Class类(我们将会探究JSP热部署的实现);
* 5.从数据库中读取

相对于类加载过程的其他阶段,非数组类的加载阶段(是加载阶段中获取类的二进制字节流的动作)是开发人员可控性最强的(可控性：开发人员可以选择二种方式的来完成这个操作):
	
* 1.通过使用系统提供的引导类加载器来完成;
* 2.通过自定义的类加载器来完成操作(重写loadClass方法);

对于数组类就不一样,因为数组类型使用JVM创建的(不通过类加载器),但是数组的元素类型最终还是需要靠类加载器去创建;

加载阶段与连接阶段的部分内容是交叉进行的,加载阶段还未完成,可能连接阶段就已经开始,但是两个阶段开始时间依然保持先后的顺序;	

### 1.2 连接

连接阶段分为3个子过程：

1.验证
						
* 验证过程是连接阶段的第一步,主要目的：为了确保class文件的字节流中包含的信息符合虚拟机的要求,不会危害虚拟机的安全;(这里就不一一贴出来了：详见深入理解Java虚拟机)主要验证class文件的规范;

2.准备

* 准备过程是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中分配;
		
	* 1.这里讲的类变量指的是static修饰的变量,不包括实例变量,实例变量跟随对象实例化分配在java堆中;
	* 2.这里说的初始值'通常情况'是数据类型的零值,例如：public static int value = 1;那变量value在准备阶段后的初始值是0而不是1;因为这时候并没有开始执行任何java方法,而把value赋值为1的putstatic指令是存放与构造器clinit方法中,所以赋值操作在初始化阶段中执行;(可以通过javap命令查看class文件的字节码执行指令);(但是也有特殊情况：如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段会被初始化ConstantValue的值,上述例子javac会为ConstantValue值,在准备阶段将ConstantValue值1赋给value);		

3.解析
		
* 解析过程是虚拟机将常量池中符号引用替换为直接引用的过程;(对于此过程实在是没有什么兴趣去探究由于这里涉及许多指令操作,若感兴趣请自行查阅书籍资料;)

### 1.3 初始化

类初始化阶段是类加载过程的最后一个阶段,到了这一步开始真正的执行类中定义的java代码(除了在加载阶段使用自定义类加载器加载类);

简单讲：连接阶段准备过程中已经为类变量进行一次系统要求的初始值的初始化,但是初始化阶段执行类构造器的过程(clinit());

* clinit()方法是编译器自动收集类中的所以类变量和静态块(static{ })中的语句合并产生的,编译器收集的顺序是由语句在源文件中的位置决定的,PS：静态语句块能对定义在之后的静态变量进行赋值操作,但是不能访问此静态变量;如下面的例子：
		
定义一个Test.java源文件：

	public class Test{
				
		static{
			value = 111;					
			System.out.println(value);
		}
				
		public static int value = 110;
	}
		
	编译报错：非法前向引用;
	使用javap -verbose Test 查看执行指令(字节码)
	Code:
      	stack=1, locals=0, args_size=0
         	0: bipush        111
         	2: putstatic     #2                  // Field value:I
        		5: bipush        110
         	7: putstatic     #2                  // Field value:I
        		10: return
	最后输出value值是110;
	
* <%clinit%>方法与类的构造函数(实例构造器<%init%>方法)不同,它不需要显示的调用父类构造器(<%init%>则需要,类从Object类继承,在<%init%>方法中显示调用java.lang.Object类构造器),虚拟机会保证在子类的<%clinit%>方法的执行之前,父类的<%clinit%>方法已经执行完毕.父类定义的静态块要优先于子类;
* <%clinit%>对于类与接口不是必需的,若没有静态块和类变量赋值操作,编译器不会产生<%clinit%>方法;
* 接口中不能有静态语句块,但是依然有变量赋值操作,所以一样会生成<%clinit%>方法,但与类不同的是,执行接口的<%clinit%>方法不需要调用父类的<%clinit%>方法,当使用到父接口的变量时,才会触发初始化操作,接口实现类也一样;
* 虚拟机会保证<%clinit%>方法在多线程环境下被正确的加锁,同步,如果多个线程同时去初始化一个类,那么只会有一个类执行<%clinit%>方法,其他线程都需要堵塞等待;
* 虚拟机规范严格规定有且只有5种情况必须对类进行“初始化”(加载,连接需在之前进行);
{'fraction_non_alphanumeric': 0.11464755874020996, 'fraction_numerical': 0.02366272287952008, 'mean_word_length': 6.694871794871795, 'pattern_counts': {'":': 0, '<': 30, '<?xml version=': 0, '>': 32, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6977550', 'n_tokens_mistral': 4722, 'n_tokens_neox': 5320, 'n_words': 206}