JVM学习笔记(二)jvm类加载机制
来源:互联网 发布:手机测量软件安卓版 编辑:程序博客网 时间:2024/05/21 05:06
类加载过程
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称链接。
加载(装载)、验证、准备、初始化和卸载这五个阶段顺序是固定的,类的加载过程必须按照这种顺序开始,而解析阶段不一定;它在某些情况下可以在初始化之后再开始,这是为了运行时动态绑定特性。值得注意的是:这些阶段通常都是互相交叉的混合式进行的,通常会在一个阶段执行的过程中调用或激活另外一个阶段。
加载
获得类的二进制字节流
将这个字节流所代表的静态存储结构转为方法区的运行时数据结构
在Java堆中生成对应的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证
验证是链接阶段的第一步,这一步主要的目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。
验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。
1.文件格式验证
是否以魔术0xCAFEBABE开头
主、次版本号是否在当前虚拟机处理范围之内
2.元数据验证
保证起字节码描述的信息符合java语言规范要求。
是否有父类
是否继承了不允许被继承的类(被final修饰的)
非抽象类实现了所有的抽象方法
3.字节码验证
栈数据类型和操作码数据参数吻合(栈里放的是int类型,使用时却按long类型来加载入本地变量表)
跳转指令跳转到合理的位置
方法体中的类型转换有效,例如可以把一个子类对象赋值给父类数据类型,这是安全的,但不能把一个父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系的、完全不想干的一个数据类型。
4.符号引用验证
确保解析动作能正常执行
常量池中描述类是否存在
访问的方法和字段是否存在且有足够的权限(private,protected,public,default)
准备
为类变量分配内存(方法区中分 配),并为类变量设置初始值。
public static int value = 1;
在准备阶段中, v会被设置为0
在初始化的< clinit >()中才会被设置为1
对于static final类型,在准备阶段就会被赋上正确的值
public static final int value = 1
解析
将常量池中的符号引用替换为直接引用
- 符号引用:字符串,引用对象不一定被加载
- 直接引用:指针或者地址偏移量,引用对象一定在内存
初始化
- 执行类构造器< clinit >方法由下面两部分合成:
– static变量的赋值语句
– static{}块
//静态语句块中只能访问定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中只能赋值不能访问public class Test{ static{ i = 0; //给变量赋值可以正常编译通过 System.out.println(i); //提示“非法向前引用” } static int i = 1;}
- 子类的< clinit >调用前保证父类的< clinit >方法已经执行完毕
– 虚拟机中第一个被执行< clinit >方法的类是java.lang.Object - 一个类的< clinit >方法是线程安全的
类加载器
ClassLoader是一个抽象类
ClassLoader的实例将读入Java字节码将类装载到JVM中
ClassLoader可以定制,满足不同的字节码流获取方式
ClassLoader负责类装载过程中的加载阶段
package com.kelly.classloader;import java.io.IOException;import java.io.InputStream;public class ClassLoaderTest { public static void main(String[] args) throws Exception { ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String filename = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(filename); if (is == null) return super.loadClass(name); byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; Object obj = myLoader.loadClass("com.kelly.classloader.ClassLoaderTest").newInstance(); System.out.println(obj.getClass()); System.out.println(obj instanceof com.kelly.classloader.ClassLoaderTest); }}
上面代码输出结果;
class com.kelly.classloader.ClassLoaderTestfalse
因为虚拟机中存在两个ClassLoaderTest类,一个由系统应用程序类加载器加载的,另外一个是我们自定义的类加载器加载的。
双亲委派模型
类加载器分类
启动类加载器(Bootstrap ClassLoader)
这个类加载器负责将< JAVA_HOME >\lib目录中的,或者被 -Xbootclasspath参数所指定的路径中,并且被虚拟机所识别的类库加载到虚拟机内存中。开发者不能直接使用启动类加载器。
扩展类加载器(Extension ClassLoader)
负责加载< JAVA_HOME >\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。开发者可以直接使用类加载器。
应用程序类加载器
负责加载用户路径上(ClassPath)上所指定的类库。默认的类加载器。
图2. 类加载器双亲委派模型
双亲委派模型的代码都集中在java.lang.ClassLoader的loadClass()方法中,如下代码
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先检查请求的类是否被加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 如果父类加载抛出ClassNotFoundException // 说明父类加载器无法完成加载请求 } if (c == null) { //如果父类加载器无法完成加载 //再调用自身的findClass方法来进行类加载 long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
破坏双亲委派模型
优点:对于通用类,各个类加载器去加载都会是同一个类。(比如Object类)。
缺点:顶层的ClassLoader,无法加载底层ClassLoader的类。
思考一个问题:java框架(rt.jar类)如何加载应用的类? javax.xml.parsers包中定义了xml解析的类接口Service Provider Interface (SPI) 位于rt.jar 即接口在启动ClassLoader中。而SPI的实现类,在AppLoader。但启动类记载其不可能认识这些实现类的代码,怎么办呢?
解决:线程上下文加载器(Thread Context ClassLoader)。通过Thread.setContextClassLoader()方法进行设置。它的基本思想是在顶层ClassLoader中,传入底层ClassLoader的实例。
//设置类加载器contextClassLoader public void setContextClassLoader(ClassLoader cl) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("setContextClassLoader")); } contextClassLoader = cl; }
代码来自javax.xml.parsers.FactoryFinder展示如何在启动类加载器中加载AppLoader的类
static private Class<?> getProviderClass(String className, ClassLoader cl, boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException { try { if (cl == null) { if (useBSClsLoader) {//使用bootstrap classLoader加载这个类 return Class.forName(className, false, FactoryFinder.class.getClassLoader()); } else { cl = ss.getContextClassLoader(); if (cl == null) { throw new ClassNotFoundException(); } else { return Class.forName(className, false, cl);//使用上下文加载器去加载这个类 } } } else {//使用当前提供的类加载器去加载这个类 return Class.forName(className, false, cl); } } catch (ClassNotFoundException e1) { if (doFallback) { // Use current class loader - should always be bootstrap CL return Class.forName(className, false, FactoryFinder.class.getClassLoader()); } else { throw e1; } } }
双亲委派模型是默认的模式,并不是必须要这么做。Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent。OSGi的ClassLoader形成网状结构,根据需要自由加载Class。
- JVM学习笔记(二)jvm类加载机制
- JVM学习笔记(二)——类加载机制
- jvm学习笔记(6)类加载机制
- JVM学习笔记-类加载机制
- JVM学习笔记三:JVM类加载机制
- JVM笔记6:JVM类加载机制
- JVM笔记:JVM类加载机制
- JVM学习笔记(6)-类加载机制
- JVM学习笔记(1)----Java类的加载机制
- JVM虚拟机类加载机制(二)
- JVM学习笔记(二):类加载器
- JVM学习笔记(二)---类加载器
- 【JVM】JVM类加载机制
- JVM学习笔记之虚拟机类加载机制
- JVM学习笔记——虚拟机类加载机制
- JVM学习笔记(四)之类加载机制
- JVM学习笔记(五)之类加载机制
- JVM类加载学习笔记
- cmd 中 net start mysql 提示发生系统错误 5
- 求解汉诺塔问题
- Servlet是什么?有什么用?
- 以书的前言作为博客的开篇
- Kali信息收集
- JVM学习笔记(二)jvm类加载机制
- Python装饰器各种类型详解
- dnspod上提示 域名 NS 地址还未修改
- 引用方式
- insert子查询
- c++第五次实验
- python-pip操作基本指令介绍
- linux下wget下载整个网站
- Storm线程进程分配方法