黑马程序员---类加载

------- android培训java培训java学习型技术博客、期待与您交流! ----------


类加载

一:JVM与类

         当我们调用java命令运行java程序的时候,该命令将启动一个java虚拟机进程。不管该程序多么复杂,该程序启动了多少线程,他们都处于同一个进程里面。当系统出现一下几种情况时候,JVM进程将被终止:

  • 程序运行最后正常借宿
  • 程序使用System.exit()或者Runtime.getTuntime().exit()代码处结束
  • 程序运行中遇到未捕获异常或错误
  • 所在平台强制结束程序

 

class A
 {
         publicstatic int a = 6 ;
 }
public class ADemo1
{
         publicstatic void main (String [] args )
         {
                   Aa = new A();
                   //让a实力的a自加
                   a.a++; 
                   System.out.println(a.a);
         }
}
public class ADemo2
{
         publicstatic void main (String [] args )
         {
                   Ab = new A();
                   //打印结果
                   System.out.println(b.a);
 
         }
}

         在以上程序中,运行的结果分别是7,6。因为当运行第二个程序的时候,启动的是一个新JVM虚拟机进程,所以ADemo1做的修改全部丢失了。

 

二:类的加载与初始化

         当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、链接、初始化3个步奏来对该类进行初始化,一般情况下,JVM会连续完成这3个步奏

类的加载由类加载器进行,负责将class文件加载到内存中。当类被加载后,系统为之生成一个Class对象,接着进入链接阶段,把类的二进制数据合并到JRE中。

在类的初始化当中,虚拟机负责对类进行初始化:1,声明静态成员时候显性初始化,2,使用静态初始化块进行初始化,例如下面代码:

         

publicclass Test
         {
                   //显性初始化
                   staticint a = 5;
                   staticint b ;
                   staticint c ;
                   static
                   {
                            b= 6 ;
                   }
         }

以上程序abc结果为5,6,0.当没有对静态成员进行赋值时,系统会采用默认值进行填充

 

三:类加载器

1、概述:

         类加载器负责将.class文件(可能在银盘上,也可能在磁盘上)加载到内存中,并为之生成对应的java.lang.Class对象。类加载器负责加载所有的类,一旦一个类被加载到内存中,同一个类就不会再次载入了。那么,怎么样才算是同一个类呢?

         在JVM中,一个类用气全县定类名和类加载器作为唯一标识。例如:

         在pg包中有一个Person类,被类加载器ClassLoader的实例kl负责家宅,则Person类对应的标志为(Person、pg、kl).

         当JVM启动时候,会形成由3个类加载器组成的初始类加载器层次结构

         1,Bootstrap ClassLoader: 根类加载器

         2,ExtensionClassLoader :  拓展类加载器

         3,System   ClassLoader :系统类加载器

         其中:

BootstrapClassLoader被称为引导加载器,负责加载java的核心类。根类加载器非常特殊,它并不是javalang.ClassLoader的子类,而是有JVM自身实现的。

         ExtensionClassLoader是拓展类加载器,负责加载JRE拓展目录中的包(&JAVA_HOME%/jre/lib/ext).

         SystemClassLoader 是系统加载器,负责在JVM启动时加载来自java命令的制定的类,如果没有特别指定,则用户自定义的类加载器都是以系统类加载器作为父类。程序可以通过ClassLoader的静态方法getSystemClassLoader来获取系统类加载器。


2、类加载机制

         JVM的类加载机制主要有如下3中:

         1,全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式指定使用另外的加载器。

         2,父类委托:就是先让父类加载器试图加载Class,只有在父类加载器无法加载该Class时,才尝试从自己的路径中加载该Class。

         3,缓存机制:缓存机制保证所有加载过的类会被缓存,当程序需要某个Class时,先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象是,系统才会读取二进制数据加载Class。

         下图是类加载机制可以看出类加载的路径:

<span style="font-size:12px;">public class ClassLoaderDemo
{
         publicstatic void main (String [] args)
         {
                ClassLoadersystemLoader = ClassLoader.getSystemClassLoader();
                System.out.println(“系统类加载器:”+systemLoader);
                  
                ClassLoaderextensionLoader = systemLoader.getParent ();
                System.out.println(“拓展类加载器:”+extnsionLoader);
<span style="white-space:pre">		</span>System.out.println(“系统类加载器父类:”+ extnsionLoader.getParent ());
         }
}</span>


以上代码输出结果为:

         系统类加载器:sun.misc.Launcher$AppClassLoader@1b00e7

         拓展类加载器:sun.misc.Launcher$ExtClassLoader@b76fa

         拓展类加载器父类:null

事实上,根类加载器并不是java实现的,并没有继承ClassLoader,所以拓展类的父类是根类加载器,但是getParent()是返回null的。

类加载时,类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类加载器去进行加载。当回退到最初的发起者类装载器时,如果它自己也不能完成类的装载,那就会抛出ClassNotFoundException异常。这时就不会再委托发起者加载器的子类去加载了,如果它还有子类的话。

 

3、创建自定义的类加载器

       JVM中除了根类加载器之外所有类加载器都是ClassLoader的子类,我们可以通过继承ClassLoader类并重写ClassLoader的方法来实现自定义加载类。其中,ClassLoader中有两个很关键的方法:

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

        2,findClass(String name):根据二进制名称来查找类

如果需要实现自定义类,自定义类,可以通过重写以上两个方法来实现。推荐重写findClass(String name) 而不是loadClass()方法。loadClass()方法执行步奏如下:

1,调用findLoadClass(String)来检查是否已经加载类,若是,则直接返回;

2,调用父类的loadClass()方法,如果父类为null,则使用根类加载器进行加载;

3,调用findClass()方法查找类。

简单说,就是先由发起者将类一级级委托为BootStrap,从父级开始找,找到了直接返回,没找到再助剂让其子级找,直到发起者,再没找到就报异常。

以下代码实例,实现从配置文件中获取类名,在加载类之前,先判断是否存在Class文件,或原文件是否改动过,否则重新编译源文件再加载:


class MyLoadClass extends ClassLoader 
{
	*/
	//重写了方法,当class文件不存,或者原文件被修改过的时候,从新编译
	protected Class<?> findClass (String name)
		throws ClassNotFoundException
	{
		Class<?> clazz = null ;
		String javaName = name + ".java";
		String className = name + ".class";
		File javaFile = new File(javaName);
		File classFile = new File (className);
		s(classFile);
		//当class文件不存,或者原文件被修改过的时候,从新编译
		if(javaFile.exists()&&(!classFile.exists()||javaFile.lastModified()>classFile.lastModified()))
		{
			try{
				//如果编译失败并且class文件也不存在的时候,返回异常
			if ((!compile(javaFile))||!classFile.exists())
			{

			throw new ClassNotFoundException("文件不存在");
			}
			}catch(Exception e){}
		}
		//如果文件存在,则调用difineClass方法加载进内存
		if (classFile.exists())
		{
			/*
			try{
			byte [] raw = getBytes(classFile);
			clazz = defineClass(name,raw,0,raw.length,ProtectionDomain);
			}catch (Exception ie){}
			*/
			s("正在加载中---");
			clazz = loadClass(name);
			s("类加载完成---");
		}
		if (clazz == null)
		{
			throw new ClassNotFoundException();
		}
		//返回加载的文件
		return clazz;
	}
	//编译java文件
	public boolean compile(File javaFile) throws Exception
	{
		s("正在编译中");	
		//通过Runtime启动javac进行编译,暂停其他线程知道编译线程完成
		Process p = Runtime.getRuntime().exec("javac "+javaFile);
		try{p.waitFor();}catch (InterruptedException e ){}
		int ret = p.exitValue();
		return ret == 0;
	}
	public static void s(Object obj)
	{
		System.out.println(obj);	
	}
}

import java.io.*;
import java.util.*;
import java.lang.reflect.*;

class ReflectDemo 
{
	static Class <?> clazz = null;
	public static void main(String[] args) throws Exception
	{
		reflect();
		//System.out.println(getName());
	}
	//从配置文件获取需要使用类名
	public static  String getName() throws IOException
	{
		Properties ips = new Properties();

		FileReader fr = new FileReader("ips.properties");

		ips.load(fr);

		fr.close();

		return ips.getProperty("className");
	}

	//加载类,
	public static Class<?> loadClass(String className)  throws Exception
	{

		MyLoadClass rf = new MyLoadClass();

		File file = new File( className.replace(".","/")+".java");

		//线判断源码是否存在,若存在,则使用findClass进行查找加载,若是系统类或者源码不存在,则使用系统方式加载
		if (file.exists())
		{
			System.out.println("(′▽`)我是来卖萌的,请忽略我;⊙﹏⊙!");
		return ( rf.findClass(className));
		}else{
		return ( rf.loadClass(className));
		}
		/*
		s(clazz.getProtectionDomain());
		*/

	}
	//使用反射方式调用配置文件中的类的相关内容
	public static  void reflect() throws Exception
	{
		Class<?> clazz = loadClass(getName());

		Method [] mts = clazz.getMethods();
		
		for(Method mt :mts)
		{
			System.out.println(mt);
		}
		s("\r\n");

		//获取主函数,运行
		Method m = clazz.getMethod("setName",String.class);

		Object obj = clazz.newInstance();

		m.invoke(obj,"张三");

		Field f = clazz.getDeclaredField("name");
		f.setAccessible(true);
		f.set(obj,"李四");
		s(f.get(obj));
	}
	public static void s(Object obj)
	{
		System.out.println(obj);
		
	}
}

以上代码仅仅实现了在运行之前先编译java原文件的功能。实际上,完全可以通过自己的重写,实现更多的功能,比如:执行代码前自动验证数字签名,根据用户需求动态加载类等等。

个人小结:

类加载这块内容不算太多,也没有出现在25天的视频中,但是对于一个合格的程序员来说,必须要了解类加载的原理,类加载的委托机制这些内容。只有这样才能够编写相关程序的时候更加得心应手。简单来说,类加载其实很简单,就是几个小点而已:1,父类委托:子类加载首先将Class交给父类执行;2,loadClass方法:这是加载的关键方法,里面的实现逻辑基本是,先调用findLoadClass()查找内存中是否已经加载类,否则调用父类loadClass(),父类都没有加载则调用自身findClass()查找类,findClass()方法中调用了defineClass()方法讲二进制文件转换为对象。大概理顺了这个流程后,我们就可以按照我们的需要,区重写findClass()了。

本来是打算将类加载与反射一起写的,因为我练习的时候是结合了两者一起,从配置文件中读取信息,通过反射进行加载类,加载前先做必要的判断操作比如编译之类的。但是由于篇幅问题,反射将在下一篇博客里面详细写。


 ------- android培训java培训java学习型技术博客、期待与您交流! ----------

 


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值