类加载机制

最近阅读《深入理解JVM》,整理出以下资料,与大家分享。由于写作水平和阅历有限,本中存在不妥、不全之处,还请大家多多留言。

目录

类加载机制:

类的生命周期

类的加载时机 

初始化时机

类主动引用

被动引用

 接口初始化

类加载过程

 加载

验证

准备

解析

初始化

类加载器

类与类加载器

双亲委派模型

破坏双亲委派模型


类加载机制:

JVM将Class文件加载到内存,并对其进行校验、转换解析和初始化,最终形成被JVM直接使用的Java类型。

类的生命周期

类的生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、和卸载(Unloading)7阶段。从加载至JVM内存开始,到卸载出内存。

类的加载时机 

  • 初始化时机

  • 类主动引用

  1. 遇到new、getstatic、putstatic、或invokestatic这4条字节码指令时。

  2. 遇到java.lang.reflect包的方法对类进行反射调。

  3. 初始化子类时,则先初始化父类。

  4. 虚拟机(JVM)启动时,用户指定程序的主类。

  5. 使用JDK1.7时,如果一个java.lang.invoke.MethodHandler实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且句柄对应的类没有进行初始化,则先触发其初始化。

  • 被动引用

1、通过子类引用父类的静态字段,不会导致子类初始化

代码
/**
 * 被动使用字段演示1
 * 通过子类引用父类的静态字段,不会导致子类初始化
 * @author Bad Written
 *
 */
public class SuperClass {

	static{
		System.out.println("SuperClass init!");
	}
	public static int value=888;
}


public class SubClass extends SuperClass {

	static{
		System.out.println("SubClass init!");
	}
}

public class NotInitialization {

	public static void main(String[] args) {
		System.out.println(SuperClass.value);
	}

}

运行结果:
SuperClass init!
888

2、通过数组定义来引用类,不会触发此类初始化。创建动作有字节码指令newarray触发。

代码
/**
 * 被动使用字段演示2
 * 通过数组定义来引用类,不会触发此类初始化
 * @author Bad Written
 *
 */
public class SuperClass {

	static{
		System.out.println("SuperClass init!");
	}
	public static int value=888;
}


public class SubClass extends SuperClass {

	static{
		System.out.println("SubClass init!");
	}
}

public class NotInitialization {

	public static void main(String[] args) {
		SuperClass[] sup=new SuperClass[10];
	}

}

运行结果:

 3、常量在编译阶段会存入常量池中,并没有直接引用到定义常量的类,因此不会触发常量的类初始化。

代码:
/**
 * 被动使用类字段演示3
 * 常量在编译阶段会存入常量池中,并没有直接引用到定义常量的类,因此不会触发常量的类初始化。		
 * @author Bad Written
 *
 */
public class ConstClass {

	static {
		System.out.println("ConstClass init!");
	}
	
	public static final String HELLO="hello word";
	
}

public class NotInitialization {

	public static void main(String[] args) {
		
		System.out.println(ConstClass.HELLO);
	}

}

运行结果:
hello word
  •  接口初始化

在初始化时,区别在于类主动引用第3种:在子接口初始化时,只有在用到父接口,才会初始化。

类加载过程

  •  加载

  1. 通过某个类的全限定名来获取定义此类的二进制字节流。
  2. 将这个字节流的静态存储结构转换为方法区运行时数据结构。
  3. 在内存中生成此类的java.lang.Class对象,作为方法区此类的访问入口。
  • 验证

目的:确保Class文件不危害JVM自身的安全。

验证阶段非常重要、但不是一定必要的阶段,可以对反复使用和验证过正确的,可考虑使用 -Xverify:none参数来关闭大部分类验证,以缩短虚拟机类加载时间。

大致分为4个阶段:

  1. 文件格式验证:验证字节流是否符合Class文件格式规范。(此阶段基于二进制字节流进行,后面3个阶段基于方法区的存储结构)

  2. 元数据验证:字节码描述信息进行验证。

  3. 字节码验证:验证程序语义是否合法、符合逻辑。

  4. 符号引用验证:对类自身以外的信息进行匹配校验。

  • 准备

为类变量分配内存并设置类变量初始值。

  • 解析

将常量池内的符号引用替换为直接引用的过程。

  1. 符号引用(Symbolic  References):用一组符号来描述所引用的目标,符号可以是任何形式的字面量。
  2. 直接引用(Direct  References):可以是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
  • 初始化

主观计划去初始化类变量和其他资源。

类加载器

把类加载阶段中的“通过一个类的全限定名来获取此类的二进制字节流”这个动作放到Java JVM外部实现。实现这个动作模块称为“类加载器”。

  • 类与类加载器

比较2个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则同一个Class文件,类加载器不同,则这两个类必定不相等。

package org.student.badwritten.load;

import java.io.IOException;
import java.io.InputStream;

/**
 * 类加载器演示
 * @author Bad Written
 *
 */
public class ClassLoaderTest {

	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //简单的类加载器		
		ClassLoader myload=new ClassLoader() {			
			public Class<?> loadClass(String name) throws ClassNotFoundException{	
				
				try {					
					//获取文件的全称
					String filename=name.substring(name.lastIndexOf(".")+1)+".class";
					//从当前类所在的包下查找该资源
					InputStream input=getClass().getResourceAsStream(filename);
					if(input==null){	
						//加载 类名.class 字节码文件的工具
						return super.loadClass(name);
					}
					byte[] b=new byte[input.available()];
					input.read(b);
					//将定义的字节码文件经过字节数组流解密之后,将该字节流数组生成字节码文件
					return defineClass(name, b, 0, b.length);
				}catch (IOException e) {
					throw new ClassNotFoundException(name);
				}						
			}
		};
		
		Object obj=myload.loadClass("org.student.badwritten.load.ClassLoaderTest").newInstance();
		System.out.println(obj.getClass());
		System.out.println(obj instanceof org.student.badwritten.load.ClassLoaderTest);
	}
}

 

  • 双亲委派模型

类加载器的双亲委派模型(Parents Delegation Model):除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器,这里类加载器之间父子关系一般使用组合(Composition)关系来复用父加载器代码。

3个类加载器用途:

  1. 启动类加载器(Bootstrap  ClassLoader):将存放在<JAVA_HOME>\lib目录中的类库加载到虚拟机(JVM)中。

  2. 扩展类加载器(Extension  ClassLoader):加载<JAVA_HOME>\lib\ext目录中所有类库。

  3. 应用程序类加载器(Application  ClassLoader):负责加载用户类路径(ClassPath)上所指定的类库。

过程:

  1. 类加载器收到一个加载请求。

  2. 委派给父类加载器完成,每层都如此。

  3. 最终将加载请求传至顶层的启动类加载器中,判断是否能完成加载请求,完成便加载,完成不了,子加载器尝试加载。

特点:java类具备优先级层次关系。

  1. 启动类加载器(Bootstrap  ClassLoader):将存放在<JAVA_HOME>\lib目录中的类库加载到虚拟机(JVM)中。

  2. 扩展类加载器(Extension  ClassLoader):加载<JAVA_HOME>\lib\ext目录中所有类库。

  3. 应用程序类加载器(Application  ClassLoader):负责加载用户类路径(ClassPath)上所指定的类库。

  • 破坏双亲委派模型

在OSGi环境下,类加载器不再是双亲委派模型中树状结构,而是复杂的网状结构。

当收到类加载请求时,OSGi按照下面顺序进行类搜索:

  1. 将以java.*开头的类为派给福类加载器加载。
  2. 否则,将委派列表单内的类委派给父类加载器加载。
  3. 否则,将import列表中的类为派给Export这个类的Bundle的类加载器加载。
  4. 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
  5. 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
  6. 否则,查找Dynamic Import列表的Bundle,委派给对应的类加载器加载。
  7. 否则,类查找失败。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值