黑马程序员---类加载

来源:互联网 发布:华为matebook x知乎 编辑:程序博客网 时间:2024/06/05 03:50

------- 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