浅析java类的加载机制

浅析java类的加载机制

1.加载,验证,准备,初始化,卸载的顺序是确定的,解析的顺序是不确定的,解析可以在初始化阶段之前,也可以在初始化之后(动态加载);

1>    加载:通过全类名定义此类的二进制字节流,将字节流所代表的静态存储结构转化为方法区的运行时数据结构,然后在堆中生成一个java.lang.Class对象,作为方法区访问的入口

2>    验证:验证加载的数据是否符合javac编译的约定规则(文件格式的验证,元数据验证,字节码验证,符号引用验证);

3>    准备:为类的静态变量赋初始值(不包括实例变量),设定值的设置是在程序解析后类的初始化完成,实例变量是在对象实例化随着对象一起分配在java堆中。

4>    解析:将常量池中的符号引用变为直接引用(符号引用是一组符合命名规则的字面量,与虚拟机实现的内存布局无关,符号引用的目标不一定被加载到了内存之中;直接引用:直接引用可以是指针,相对偏移量或者是一个能简洁定位到目标的句柄,是与内部布局相关的,且不同虚拟机实例出来的直接引用一般不会相同),符号引用需要进一步成为直接引用才能被系统识别(在运行时将符号引+用解析为指向内存地址的直接引用)v

5>    初始化:给静态部分赋初始值,初始化过程是一个执行类初始化<clinit>()方法和实例化初始化init方法的过程

6>    卸载:System.exit(),正常结束,异常和错误终止,虚拟机进程终止

2.类的加载

Java的加载是由类加载器完成,利用全类名采用双亲委托机制,作用防止环境被污染和恶意攻击,这也是一种保证安全牺牲效率的一种方式,类加载器加载后的产物.class对象,类的加载并不是在某个类首次调用时加载,而是预期某个类将要使用时加载它,如果出现LinkageError错误,那么很大可能是不兼容的原因

3.类加载器

1>启动类加载器Bootstrap ClassLoader:负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机识别(按文件名识别,如rt.jar)的类,使用c++实现,不允许直接通过引用进行操作

2>扩展类加载器Extension ClassLoader:负责加载 JAVA_HOME\lib\ext 目录中的,开发者可以通过把自己的.jar 文件或库文件加入到扩展目录的classpath,使其可以被扩展类加载器。3>应用程序类加载器Application ClassLoader:负责加载用户路径上的类库,开发者可以直接使用标准扩展类加载器,也称为系统类加载器,例如:自定义包下的类;

4>自定义类加载器:继承java.lang.ClassLoader的方式实现自定义类加载器

实现顺序为:自定义类加载器,应用程序类加载器,拓展类加载器,启动类加载器。

5>线程上下文类加载器:默认为应用程序类加载器类加载器,可通过Thread.currentThread()

.setContextClassLoader(ClassLoader)来设置,每个线程都可以将线程上下文类加载器预先设置为父线程的类加载器。这个主要用于打破双亲委派模型,容许父类加载器通过子类加载器加载所需要的类库

4.双亲委托机制的实现源码分析

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

              }

                if (c == null) {

                    // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能

                    long t1 = System.nanoTime();

                                          //find方法可以重写

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

            }

            return c;

        }

    }

5.Java类加载的过程

当一个类收到类加载的加载任务,会先给自己的父类加载,只有在父类加载器无法完成加载任务的时候(在识别的区域内无法找到相应的类),才自己去加载,在jvm中假如需要两个类相等,那么需要是同一个类加载器加载,这是前提条件,不同类加载器加载的类是不相等的,就算是相同的类在不同的类加载器下加载也是不相等的。

6.初始化

6.1类的初始化

<clinit>()方法为了让编译器自动收集类中的所有类成员变量的赋值动作和静态语句块(static{}块)中的语句合并而产生的,编译器合并的顺序是由语句在源文件中出现的顺序所决定。

<clinit>()方法与类的构造函数不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕,因此在虚拟机中第一个执行的<clinit>()方法的类一定是java.lang.Object。也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。<clinit>()方法对于类或者接口来说并不是必需的,如果一个类中没有静态语句块也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。接口中可能会有变量赋值操作,因此接口也会生成<clinit>()方法。但是接口与类不同,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量被使用时,父接口才会被初始化被调用的部分。另外,接口的实现类在初始化时也不会执行接口的<clinit>()方法,常量在编译阶段会加入常量池,本质上没有直接引用到定义的常量类,转化成为了对自身常量池的引用,所以不会触发定义常量的类的初始化,虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁和同步。如果有多个线程去同时初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其它线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,那么就可能造成多个进程阻塞。

6.2实例初始化的入口 (类加载的最底层类)

静态部分的调用,new,反射,main方法,相信看完<clinit>()方法后对上一篇的静态代码块的执行顺序有了一定的理解,例如未实例化初始化子类,且不在子类的静态方法中调用父类的静态部分,那么父类才是类加载的最底层类(顶层类是Object类)等

6.3实例的初始化

init方法:实现实例的初始化,使实例初始化的方式有反射,new等

7实例:

1.见浅析java的基本程序,结合上述对浅析java基本程序实例做出合理的解释

2.类加载器

public class ValidateClassLoader {

     public static void main(String[] args) {

             ClassLoader loader = Thread.currentThread().getContextClassLoader();

         System.out.println(loader);

         System.out.println(loader.getParent());

         System.out.println(loader.getParent().getParent());

        }

}// sun.misc.Launcher$AppClassLoader@6d06d69c

//sun.misc.Launcher$ExtClassLoader@70dea4e      null

原因:null的原因,是因为Bootstrap Loader(启动类加载器)是用C语言实现的,在jvm中无法找到相应的能识别的对象;

3.自定义类加载器

package com.xrq.classloader;

public class Person

{

    private String name;

      public Person()

    {

       

    }

   

    public Person(String name)

    {

        this.name = name;

    }

   

    public String getName()

    {

        return name;

    }

   

    public void setName(String name)

    {

        this.name = name;

    }

   

    public String toString()

    {

        return " " + name;

    }

}

public class MyClassLoader extends ClassLoader

{

        private String root;

        public String getRoot() {

        return root;

    }

    public void setRoot(String root) {

        this.root = root;

    }

      protected Class<?> findClass(String name) throws ClassNotFoundException

    {

        try

        {

            byte[] bytes = getClassBytes(name);

            Class<?> c = this.defineClass(name, bytes, 0, bytes.length);

            return c;

        }

        catch (Exception e)

        {

            e.printStackTrace();

        }

       

        return super.findClass(name);

    }

   

    private byte[] getClassBytes(String name) throws Exception

    {

      try {

                         String fileName = root + File.separatorChar

                + name.replace('.', File.separatorChar) + ".class";

            InputStream ins = new FileInputStream(fileName);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            int bufferSize = 1024;

            byte[] buffer = new byte[bufferSize];

            int length = 0;

            while ((length = ins.read(buffer)) != -1) {

                baos.write(buffer, 0, length);

            }

            return baos.toByteArray();

        } catch (IOException e) {

            e.printStackTrace();

        }

        return null;}

    public static void main(String[] args) throws Exception

    {

        MyClassLoader  classloader = new MyClassLoader();

classloader.setRoot=”E:”;     

        Class<?> c1 = Class.forName("com.Person", true, classloader);

        Object obj = c1.newInstance();

        System.out.println(obj.getClass().getClassLoader());

    }

}

/*这里实际添加的是流文件bytes  ,然后调用Class<?> c = this.defineClass(name, bytes, 0, bytes.length)生成class对象;return c;并且返回*/

/*或许你输出的是应用程序类加载器,那是因为这里没有破坏双亲委托机制,在内存中有相关的class对象已经被加载器加载,删除后清除缓存即可,或者在重写loadclass的时候破坏双亲委托机制*/

说明:不想打破双亲委派只需要重写findClass方法即可,假如需要打破可以通过重写loadClass方法来实现

注意:这里传递的地址需要是全限定名的方式,自定义的类加载器用的比较少,而且自定义的类加载器在某些情况下(重写loadclass方式来实现)会破坏双亲委托机制

ClassLoader.getResourceAsStream与Class.getResourceAsStream的区别

不同点:Class的getResourceAsStream(String name)方法,参数不以"/"开头则默认从此类对应的.class文件所在的packge下取相应的资源,以"/"开头则从CLASSPATH下获取

ClassLoader的getResourceAsStream(String name)方法,默认就是从CLASSPATH下获取资源,参数不可以以"/"开头

相同点:用来读入指定的资源的输入流,并将该输入流返回给用户,资源可以是图像、声音、.properties文件等

猜你喜欢

转载自www.cnblogs.com/gg128/p/9226023.html