java class加载_Java 类加载

从一个诡异的问题说起

测试案例一:

packageecut.classloader;public classSun {protected static int a = 100;protected static intb ;protected static Sun instance = newSun() ;publicSun() {

a++;

b++;

}

}

packageecut.classloader;public classSunTest {

@SuppressWarnings("unused")public static voidmain(String[] args) {

Sun s=Sun.instance;

System.out.println( Sun.a );

System.out.println( Sun.b );

}

}

运行结果如下:

101

1

测试案例二:

packageecut.classloader;public classMoon {protected static Moon instance = newMoon() ;protected static int a = 100;protected static intb ;publicMoon() {

a++;

b++;

}

}

packageecut.classloader;public classMoonTest {

@SuppressWarnings("unused")public static voidmain(String[] args) {

Moon s=Moon.instance;

System.out.println( Moon.a );

System.out.println( Moon.b );

}

}

运行结果如下:

100

1

类的生命周期

1、 JVM 的生命周期 ( 在线程部分 ) :当一个 Java 程序执行时,将启动一个 JVM 进程 ,当程序执行结束或抛出异常时 JVM 退出。

2、对象的声明周期: 当使用 new 关键字 创建一个类的实例时,一个对象(实例)的生命周期即宣告开始

Student s = new Student();  // 将导致创建一个Student实例并对该实例中的实例属性进行初始化

实例属性的初始化:

private int  id = 0 ;

private String studentNo ;

{

studentNo = "ECUT-00000000" ;

}

private String name ;

public Student( String name ){

this.name = name ;

}

使用对象 ( 使用对象的 属性 、方法 等 )

当某个对象不再被任何一个引用变量所引用时,它可能会被GC回收,如果被回收,它的生命周期将宣告结束

3、类的生命周期

java.lang.Object 是整个 Java 类继承体系的根类

java.lang.Class 类 也继承了 Object 类

java.lang.Class 类的实例表示正在运行的 Java 应用程序中的类和接口

java.lang.Object.class 表示正在运行的 Java 程序中的 那个 Object 类 对应的 Class 类型的对象

Java语言中万事万物都可以当作对象来对待,即使是一个类,也可以当对对象对待。

343507c9d7442829ba710534a463166b.png

类的加载

将 字节码文件( .class ) 读入到 JVM 所管理的内存中

将 字节码文件对应的类的数据结构 保存在方法区

最后生成一个与该类对应的 java.lang.Class 类型的对象 ( 在堆区 )

590c7d48f293a12ed9512618e8e0d15d.png

类的链接

连接是把已读入到内存的类的二进制数据合并到Java运行时环境(JRE)中去。

连接又分为三个阶段:验证、准备、解析。

验证:验保证类有正确的内部结构,并且与其它类协调一致如果JVM 检查到错误,就会抛出Error 对象。

类文件的结构检查: 确保文件遵循Java 文件的固定格式

语义检查: 确保类本身符合Java 语言的语法规定

字节码验证: 确保字节码流可以被JVM 安全地执行

» 字节码流代表Java 方法(含静态和非静态),它是被称作操作码的单字节指令组成的序列,每个操作码后都跟着一个或多个操作数

» 字节码验证会检查每个操作码是否合法,即是否有合法的操作数二进制兼容的验证: 确保相互引用的类之间协调一致

» 比如A 类中调用B 类的b() 方法,检查B 中是否有b() 方法存在

准备:  在准备阶段,JVM 为类的静态变量分配内存,并设置默认值(byte 、short 、int 默认值都是 0,long 默认值是 0L,float 默认值是 0.0F,double 默认值是 0.0,boolean 默认值是 false,char 默认值是 \u0000,  引用类型的默认值是 null)。

解析: 将符号引用解析为直接引用

类的初始化

初始化阶段,JVM执行类的初始化语句,为静态变量赋予初始值

静态变量的初始化途径:在静态变量的声明处进行初始化,在静态代码块中进行初始化

初始化代码可能是(声明变量时的赋值语句): protected static int a = 100 ;也可以是(静态代码块):

static {

a = 10000 ;

}

类初始化的一般步骤

如果该类还没有被加载和连接,那么先加载和连接该类

如果该类存在直接父类,但该父类还未初始化,则先初始化其直接父类

如果该类中存在初始化语句,则依次执行这些初始化语句

类的初始化时机

JVM 只有在首次主动使用某个类或接口时才会初始化它

被动使用不会导致本类的初始化

诡异的问题 解析测试案例:

packageecut.classloader;public classSun {protected static int a = 100 ;//链接(准备):0//初始化: a:100

protected static int b ;//链接(准备):0//初始化: b:0

protected static Sun instance = new Sun() ;//链接(准备):null//初始化: a:101 b:1

publicSun() {

a++;

b++;

}

}

packageecut.classloader;public classMoon {protected static Moon instance = new Moon() ;//链接(准备):null//初始化: a:1 b:1

protected static int a = 100 ;//链接(准备):0//初始化: a:100

protected static int b ;//链接(准备):0//初始化: b:1

publicMoon() {

a++;

b++;

}

}

类的使用

主动使用 会导致 类被初始化

a>、创建类的实例 ( new 、反射、反序列化 、克隆 )

b>、调用类的静态方法

c>、访问类 或 接口的 静态属性 ( 非常量属性 )  ( 取值 或 赋值 都算 )

访问类 或 接口 的 非编译时常量,也将导致类被初始化:

public static final long time = System.currentTimeMillis();

d>、调用反射中的某个些方法,比如 Class.forName( "edu.ecut.Student" );

e>、初始化某个类时,如果该类有父类,那么父类将也被初始化

f>、被标记为启动类的那些类(main)

被动使用 不会导致类被初始化

a>、程序中对编译时常量的使用视作对类的被动使用

对于final 修饰的变量,如果编译时就能确定其取值,即被看作编译时常量

» 编译时常量如: public static final int a = 2 * 3 ;

» JVM 的加载和连接阶段,不会在方法区内为某个类的编译时常量分配内存

对于final 修饰的变量,如果编译时就不能确定其取值,则不被看作编译时常量

» 非编译时常量如: public static final long time = System.currentTimeMillis() ;

» 使用该类型的静态变量将导致当前类被初始化( 主动使用)

b>、JVM初始化某个类时,要求其所有父类都已经被初始化,但是 该规则不适用 于 接口 类型

一个接口不会因为其子接口或实现类的初始化而初始化,除非使用了该接口的静态属性

c>、只有当程序访问的静态变量或静态方法的确在当前类或接口定义时,

才能看作是对类或接口的主动使用:

比如使用了 Sub.method() ,而 method() 是继承自 Base ,则只初始化 Base 类

d>、调用 ClassLoader 的 loadClass( ) 加载一个类,不属于对类的主动使用

主动使用和被动使用测试案例一:

packageecut.classloader;public classPanda {//编译时常量(对于final 修饰的变量,如果编译时就能确定其取值,即被看作编译时常量)

public static final String HOMETOWN = "中国";//非编译时常量(对于final 修饰的变量,如果编译时就不能确定其取值,则不被看作编译时常量)

public static final long time =System.currentTimeMillis();public static inta ;static{

System.out.println("static code , a = " +a );

a= 100;

System.out.println("static code , a = " +a );

}

}

packageecut.classloader;public classPandaTest {public static voidmain(String[] args) {

System.out.println( Panda.HOMETOWN );//编译时常量被动使用//System.out.println( Panda.a );//访问静态变量(不是常量) 主动使用//使用该类型的静态变量将导致当前类被初始化( 主动使用)

System.out.println( Panda.time );//非编译时常量主动使用,静态代码块只执行一次因为初始化操作只执行一次

System.out.println( Panda.time );//只有第一次使用才完成初始化操作,所以值是固定的不变的

}

}

运行结果如下:

中国static code , a = 0

static code , a = 100

1522485492485

1522485492485

主动使用和被动使用测试案例二:

packageecut.classloader;public classInitTest {public static voidmain(String[] args) {//比如使用了 Child.hometown ,而 hometown是继承自 Father ,则只初始化 Father 类//System.out.println(Child.hometown);//初始化某个类时,如果该类有父类,那么父类将也被初始化//System.out.println(Child.name);//new Father();//new Father();

newChild();newChild();

}

}classFather {protected staticString hometown ;static{

System.out.println("Father : static code block.");

hometown= "Sinaean";

}//new Fater()时静态代码块最先执行,只执行一次

{ System.out.println( "Father : non-static code block." );}//每一次new Fater()都执行,仅此静态代码块执行

publicFather(){

System.out.println("Father construction.");

}//每一次new Fater()都执行,最后执行

}class Child extendsFather {protected staticString name ;static{

System.out.println("Child : static code block.");

name= "Child";

}

{ System.out.println("Child : non-static code block.");}publicChild(){

System.out.println("Child construction.");

}

}

运行结果如下:

Father : staticcode block.

Child :staticcode block.

Father : non-staticcode block.

Father construction.

Child : non-staticcode block.

Child construction.

Father : non-staticcode block.

Father construction.

Child : non-staticcode block.

Child construction.

类的卸载:当一个类不再被任何对象所使用时,JVM会卸载该类。

类加载器

1、类加载器用来把类加载到JVM 中

从JDK 1.2 版本开始,类的加载过程采用父亲委托机制

设loader 要加载A 类,则loader 首先委托自己的父加载器去加载A 类,如果父加载器能加载A 类则由父加载器加载,否则才由loader 本身来加载A类。

这种机制能更好地保证Java 平台的安全性

父亲委托机制中,每个类加载器都有且只有一个父加载器,除了JVM 自带的根类加载器( Bootstrap Loader )

2、JVM 的三种主要类加载机制

全盘负责

当一个类加载器负责加载某个类时,该类所依赖和引用的其它类也将由当前的类加载器负责载入,除非显式使用了另外一个类加载器来载入

父类委托

先让父加载器加载某个类,只有父加载器无法加载该类时子加载器才加载

当需要加载某个类时,加载这个类的类加载器会将加载操作委托给父加载器

JVM 提供了 根 加载器 : Bootstrap Loader ,它 是 JVM 的一个组成部分 ( 由JVM的实现着实现 )

缓存机制

使用缓存把所有的被加载过的类缓存起来,当程序中需要用到某个类时,类加载器先从缓存中搜寻该类,如果缓存中不存在该类,系统将读取该类对应的二进制数据并转换成Class 对象并存入cache 中

这正是修改源文件后只有重启一个JVM 才能看到修改后的执行效果的原因

3、JVM 自带的类加载器

根类加载器(BootstrapLoader)

负责加载虚拟机的核心类库,比如java.lang.* 等

从系统属性sun.boot.class.path 所指定的目录中加载类库

该加载器没有父加载器,它属于JVM 的实现的一部分(用C++实现)

扩展类加载器(ExtClassLoader)

其父加载器为BootstrapLoader 类的一个实例

该加载器负责从java.ext.dirs 系统属性所指定的目录中加载类库或者从JDK_HOME/jre/lib/ext 目录中加载类库

该加载器对应的类是纯Java 类,其父类是java.lang.ClassLoader

系统类加载器(AppClassLoader)

也称作应用类加载器,其父加载器默认为ExtClassLoader 类的一个实例

负责从CLASSPATH 或系统属性java.class.path 所指定的目录中加载类库

它是用户自定义类加载器的默认父加载器

其父类也是java.lang.ClassLoader

ClassLoader测试案例一:

packageecut.classloader;importjava.util.ArrayList;public classClassLoaderTest1 {public static voidmain(String[] args) {

Class> c = String.class; //java.lang.String

ClassLoader loader =c.getClassLoader();

System.out.println(loader);//null ( Bootstrap Loader )

Object o= new ArrayList<>(); //java.util.ArrayList

c=o.getClass();

loader=c.getClassLoader();

System.out.println(loader);//null ( Bootstrap Loader )

c= ClassLoaderTest1.class;

loader= c.getClassLoader(); //获得 ClassLoaderTest1 这个类的类加载器

System.out.println(loader); //AppClassLoader//获得 loader 这个 "类加载器" 的 父加载器

ClassLoader parent =loader.getParent();

System.out.println(parent);//ExtClassLoader

ClassLoader root=parent.getParent();

System.out.println( root );//null ( Bootstrap Loader )

}

}

运行结果如下:

null

nullsun.misc.Launcher$AppClassLoader@73d16e93

sun.misc.Launcher$ExtClassLoader@15db9742nul

4、类加载器的层次

注意这里的层次关系不是类与类的继承关系

f7188f98c192b43559b7edda9f3821ac.png

各层次的类加载器加载的类

31f4ebe399f2719dea9cb4dbb03d9966.png

ClassLoader测试案例二:

packageecut.classloader;importjava.util.Iterator;importjava.util.Properties;importjava.util.Set;public classClassLoaderTest2 {public static voidmain(String[] args) {

Properties props=System.getProperties();

System.out.println(props);

System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");

Set keys =props.keySet();for(Object key : keys) {

Object value=props.get(key);

System.out.println(key+ " : " +value);

}

System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");

Iterator it =keys.iterator();while(it.hasNext()) {

Object key=it.next();

Object value=props.get(key);

System.out.println(key+ " : " +value);

}

System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");

System.out.println(System.getProperty("java.ext.dirs"));

System.out.println(System.getProperty("java.class.path"));

}

}

运行结果如下:

..........~~~~~~~~~~~~~~~~~~~~~~~C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext;C:\Windows\Sun\Java\lib\ext

D:\java_workspace\Java\JavaAdvanced\bin

5、自定义类加载器

JVM 允许开发者开发自己的类加载器

扩展java.lang.ClassLoader 类即可

重写其中的方法

ClassLoader 中的关键方法

Class loadClass(String name )该方法为ClassLoader 的入口点,根据指定二进制名称来加载类

Class findClass( String name )根据二进制名称来查找类(一般重写该方法即可)

Class defineClass(String name, byte[] b, int off, int len)根据加载到的二进制数据返回一个Class 对象。

部分源码:

public Class> loadClass(String name) throwsClassNotFoundException {return loadClass(name, false);

}protected Class> loadClass(String name, booleanresolve)throwsClassNotFoundException

{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);//父类加载器为空,看根加载器(Bootstrap Loader)有没有加载

}

}catch(ClassNotFoundException e) {//如果类没有发现抛出ClassNotFoundException

}if (c == null) {//如果仍然没有找到,然后调用findClass为了找到类。

long t1 =System.nanoTime();

c=findClass(name);//这是定义类装入器;记录统计数据

sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 -t0);

sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);

sun.misc.PerfCounter.getFindClasses().increment();

}

}if(resolve) {

resolveClass(c);

}returnc;

}

}protected Class> findClass(String name) throwsClassNotFoundException {throw newClassNotFoundException(name);

}

除了 Bootstrap Loader 之外,其它的所有的类加载器对应的类的父类都是 java.lang.ClassLoader,loadClass方法最终调用的是findClass方法,因此自定义加载器时应该继承java.lang.ClassLoader并重写findClass方法。

自定义加载器测试案例:

用记事本新建一个Student.java,再使用命令行生成Student.class文件

Student.java有包名,直接运行Java命令会无法加载主类,因为用Javac 虽然可以编译但是没有生成正确的目录结构,包结构不对,main方法无法执行,应该带着包一起编译。并且运行java 命令需要在包名的上级目录下运行,且带上完整类名(包名.类名)

错误的编译方式:

b82d97f1033c39bb05f065eaf36d9a80.png

正确的编译方式:

d0210271ec62e6afad56efdc31a860a8.png

cb457be3fc71df610b7ded6e2f76ff25.png\

packageecut.classloader.entity;public classStudent{privateString name;private intid;public voidsetName(String name){this.name =name;

}publicString getName(){returnname;

}public void setId (intid){this.id =id;

}public intgetId(){returnid;

}public static voidmain(String[] args) {

System.out.println("Hello World");

}

}

packageecut.classloader;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.io.InputStream;importjava.nio.file.Files;importjava.nio.file.Path;importjava.nio.file.Paths;/*** 除了 Bootstrap Loader 之外,

* 其它的所有的类加载器对应的类的父类都是 java.lang.ClassLoader*/

public class EcutClassLoader extendsClassLoader {privateString path ;publicEcutClassLoader(String path) {super();this.path =path;

}

@Overrideprotected Class> findClass(final String name) throwsClassNotFoundException {

Class> c = null;

System.out.println("将要加载的类: " +name );

String s= name.replace( '.', '/' ) + ".class";

Path p=Paths.get( path , s );if( Files.exists( p ) ){try{

ByteArrayOutputStream baos= newByteArrayOutputStream();

InputStream in=Files.newInputStream( p );intn ;byte[] bytes = new byte[1024];while( ( n = in.read( bytes ) ) != -1){

baos.write( bytes ,0, n );

}final byte[] byteCode = baos.toByteArray(); //获得 ByteArrayOutputStream 内部的数据

c= this.defineClass( name , byteCode , 0, byteCode.length );

}catch(IOException e) {

e.printStackTrace();

}

}else{throw new ClassNotFoundException( "类: " + name + " 未找到.");

}returnc ;

}

}

packageecut.classloader;importjava.lang.reflect.Field;public classEcutClassLoaderTest {public static void main(String[] args) throwsException {final String path = "D:/Amy";//创建一个自定义的类加载器 ( 实例 )

EcutClassLoader loader = newEcutClassLoader( path );final String className = "ecut.classloader.entity.Student";

Class> c =loader.loadClass( className );

System.out.println( c );

System.out.println( c.getName() );

System.out.println( c.getSimpleName() );

System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

Object o=c.newInstance();

System.out.println( o );

Field idField= c.getDeclaredField( "id");

idField.setAccessible(true);

Object value= idField.get( o ); //o.id

System.out.println( value );

idField.set( o ,250 ); //o.id = 250 ;

value= idField.get( o ); //o.id

System.out.println( value );

}

}

运行结果如下:

将要加载的类: ecut.classloader.entity.Studentclassecut.classloader.entity.Student

ecut.classloader.entity.Student

Student~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ecut.classloader.entity.Student@4e25154f

0

250

java.net.URLClassLoader 类

是ClassLoader 的URL 版实现

该类也是系统类加载器类和扩展类加载器类的父类,这两个类继承了该类,该类又继承了java.security.SecureClassLoader,而java.security.SecureClassLoader 则继承了ClassLoader

URLClassLoader 功能比较强大,可以从本地文件系统中获取二进制文件来加载类,也可以从远程主机获取二进制文件来加载类

常用构造

URLClassLoader( URL[] urls )

URLClassLoader( URL[] urls , ClassLoader parent )

获得实例的静态方法

static URLClassLoader newInstance(URL[] urls)

static URLClassLoader newInstance(URL[] urls, ClassLoader parent)

待解决问题

URLClassLoader

转载请于明显处标明出处

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值