深入理解java 类在jvm中的生命周期

**

深入理解java 类在jvm中的生命周期

**

一、理解java程序的在java虚拟机中的生命周期

(一)加载

(二)连接

     1.验证

     2.准备

     3.解析

(三)初始化 
(四)卸载 
二、java程序如何退出生命周期

(一)执行了System.exit()方法导致程序退出

(二)程序正常执行完退出生命周期

(三)系统出现错误导致java虚拟机进程终止(一瓢冷水泼你计算机上就可以终止了)

(四)Java程序在执行的过程中遇到异常(Exception)或者错误(Error)

三、深入理解生命周期-加载**

(一)概念:类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 
这里写图片描述 
类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口

(二)加载.class方式: 
– 从本地系统中直接加载 
– 通过网络下载.class文件 
– 从zip,jar等归档文件中加载.class文件(最常用) 
– 从专有数据库中提取.class文件 
– 将 Java源文件动态编译为.class文件(托管服务器直接上传java源码,他就能给你编译为.class)

(三)加载.class的两种类型的加载器(classloader) 
1.–Java虚拟机自带的加载器 
• 根类加载器(Bootstrap):使用C语言编写,sun公司未公布 
• 扩展类加载器(Extension):使用java语言编写 
• 系统类加载器(System)(应用加载器):使用java语言编写

2.用户自定义的类加载器 
• java.lang.ClassLoader的子类, 用户可以定制类的加载方式

3.如何去查看虚拟机使用了那个类加载器? 
调用java.lang.Class.getClassLoader() 
如果方法返回null则代表使用的是根类加载器,返回sun.misc.Launcher$AppClassLoader则代表使用的是 系统类加载器 
以下是我在测试时候写的代码,大致发现如果是jdk源码都是使用根加载器,如果是用户自定义类就是使用系统加载器。

public class Test1 {
    public static void main(String[] args) {
        Object object=new Object();
        Class<Object> clz=Object.class;
        System.out.println(clz.getClassLoader());
        Class<A> clz1=A.class;
        System.out.println("------------------------------------------------");
        System.out.println(clz1.getClassLoader());
    }
}
class A {
}

类加载器并不需要等到某个类被“首次主动使用”时再加载它。JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误) 
(四)关于主动使用的定义:Java程序对类的使用方式可分为两种– 主动使用– 被动使用 
– 创建类的实例 
– 访问某个类或接口本身定义(不包括父类)的静态变量,或者对该静态变量赋值 
– 调用类的静态方法 
– 反射(如Class.forName(“test.classloader.Test1”) 
– 初始化一个类的子类 
– Java虚拟机启动时被标明为启动类的类(Java Test1)

除了这六种情况以外类加载器都不加载类

(四)类加载器 
这里写图片描述 
1. Bootstrap ClassLoader(引导类加载器):作为JVM的一部分无法在应用程序中直接引用,由C/C++实现(其他JVM可能通过Java来实现)。负责加载①/jre/lib目录 或 ②-Xbootclasspath参数所指定的目录 或 ③系统属性sun.boot.class.path指定的目录 中特定名称的jar包在JVM启动时将通过Bootstrap ClassLoader加载rt.jar,并初始化sun.misc.Launcher从而创建Extension ClassLoader和System ClassLoader实例,和将System ClassLoader实例设置为主线程的默认Context ClassLoader(线程上下文加载器)

注意:Bootstrap ClassLoader只会加载特定名称的类库,如rt.jar等。假如我们自己定义一个jar类库丢进/jre/lib目录下也不会被加载的!

test.classloader.TestBootstrapClassloader 看看Bootstrap ClassLoader到底加载了哪些jar包

package test.classloader;

import java.net.URL;
import sun.misc.*;
public class TestBootstrapClassloader {
    public static void main(String[] args) {

        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
        for (URL url : urls)
          System.out.println(url.toExternalForm());
    }
}
/* 输出   
    file:/usr/local/java/jdk1.8.0_111/jre/lib/resources.jar
    file:/usr/local/java/jdk1.8.0_111/jre/lib/rt.jar
    file:/usr/local/java/jdk1.8.0_111/jre/lib/sunrsasign.jar
    file:/usr/local/java/jdk1.8.0_111/jre/lib/jsse.jar
    file:/usr/local/java/jdk1.8.0_111/jre/lib/jce.jar
    file:/usr/local/java/jdk1.8.0_111/jre/lib/charsets.jar
    file:/usr/local/java/jdk1.8.0_111/jre/lib/jfr.jar
    file:/usr/local/java/jdk1.8.0_111/jre/classes
*/

由上面的输出可以看出只有这些.class才能被Bootstrap加载到内存

2.Extension ClassLoader(扩展类加载器):仅含一个实例,由 sun.misc.Launcher$ExtClassLoader 实现,负责加载①/jre/lib/ext目录(如果用户把jar文件放在这个文件加下面默认也会使用这个加载器加载) 或 ②系统属性java.ext.dirs所指定的目录 中的所有类库。是由纯java编写的加载器,并且继承于java.lang.ClassLoader

3.App/System ClassLoader(系统类加载器):仅含一个实例,由 sun.misc.Launcher$AppClassLoader 实现,可通过 java.lang.ClassLoader.getSystemClassLoader 获取。负责加载 ①系统环境变量ClassPath 或 ②-cp 或 ③系统属性java.class.path 所指定的目录下的类库。他是用户自定义类加载器的默认父加载器,是纯java语言编写的,是java.lang.ClassLoader子类

private static void getSystemClassLoader() {
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        /*输出*/
        /*sun.misc.Launcher$AppClassLoader@73d16e93*/

    }

4.Custom ClassLoader(用户自定义类加载器):可同时存在多个用户自定义的类加载器,所有的用户自定义加载器必须继承于java.lang.ClassLoader,具体如何定义请参考后文。

除了上面的4种类加载器外,JDK1.2开始引入了另一个类加载器——Context ClassLoader(线程上下文加载器)。

5 . Context ClassLoader(线程上下文加载器):默认为System ClassLoader,可通过Thread.currentThread().setContextClassLoader(ClassLoader)来设置,可通过ClassLoader Thread.currentThread().getContextClassLoader()来获取。每个线程均将Context ClassLoader预先设置为父线程的Context ClassLoader。该类加载器主要用于打破双亲委派模型,容许父加载器通过子加载器加载所需的类库。

这里写图片描述 
(五)类加载器的父委托机制 
在父委托机制中,各个加载器按照父子关系形成了树型结构,除了根加载器外每一个加载器都有且只有一个确定的父加载器

父委托机制中的几个概念:

定义类加载器:若有一个类能够加载该类那么这个加载器就叫做该类的定义类加载器。 
初始类加载器:当定义类加载器加载完类以后它会把生成的Class对象的引用返回给它的直接的子加载器,子加载器又会继续返回给它的子加载器,直到返回最外层的加载器,我们把能够返回该类的Class对象引用的加载器称为该类的初始化类加载器。 
命名空间每个加载器都有自己的命名空间,命名空间由该加载器及该加载器的父加载器加载的所有类组成(类似java中的package的概念),在同一个命名空间中不会出现类的权限定名完全相同的两个类,在不同的命名空间中可能会出现类的权限定名完全相同的两个类(因为两个相同的类可以从不同的路径中加载出来)同一个加载器加载出来的并且属于同一个包下面的类共同组成一个运行时包,决定两个类是否属于同一个运行时包的两个条件:定义类加载器相同属于统一包,只有属于同一运行时包中的两个类才能相互访问包可见的类和类成员。这样的限制限定了用户自定义的类冒充核心库中类,去访问核心库中包可见的类或者类成员。假设,用户自定义了一个类java.lang.MyClass并且由用户自定义的类加载器加载,由于它和核心库中的类使用了不同的加载器加载所以他们属于不同的运行时包,所以java.lang.MyClass不能访问核心库java.lang中的包可见成员。

树型结构的父委托结构

这里写图片描述

对于图的理解是loader2首先从自己的命名空间中查找Sample类是否被加载,如果已经被加载,如果已经加载那么直接返回Sample类的Class对象引用,如果没有被加载,那么loader2首先请求loader1加载,loader1请求系统类加载器加载,系统类请求扩展类加载器加载,扩展类加载器在请求根类加载器加载,如果根类加载器器类加载,那么就将加载以后的Class引用返回给扩展类加载器,再返回给你系统类加载器一层层直到返回给loader2,如果根类加载器无法加载,那么请求会返回给扩展类加载器,如果扩展类加载器能加载Class引用就像之前一样一层层的返回到loader2,如果不能加载那么继续将请求返回给系统类加载器,jvm依次处理类加载请求,并且一层层的返回Class对象引用,如果请求返回到loader2都不能加载该类那么loader2最终会抛出一个java.lang.ClassNotFoundException

父委托机制的优越性:能够提高软件系统的安全性。因为在此机制下用户自定义的类加载器不可能加载应该有父加载器加载德邦可靠类,从而防止不可靠类甚至是恶意代码代替由父加载器加载的可靠代码。比如说java.lang.Object只能由BootStrap加载器加载,其他用户自定义的加载器不可能向内存中加载入含有恶意代码的java.lang.Object类 
(六)非父委托机制 
双亲委派模型解决了类重复加载的乱象。但现在问题又来了,双亲委派模型仅限于子加载器将加载请求转发到父加载器,请求是单向流动的,那如果通过父加载器加载一个在子加载器管辖类来源的类,那怎么办呢?再说真的有这样的场景吗?

首先我们将 “通过父加载器加载一个在子加载器管辖类来源的类” 具体化为 “在一个由Bootstrap ClassLoader加载的类中动态加载其他目录路径下的类库”,这样我们就轻松地找到JNDI、JAXP等SPI(Service Provider Interface)均符合这种应用场景。以下就以JAXP来介绍吧!

JAXP(Java API for XML Processing),用于处理XML文档的API,接口和默认实现位于rt.jar中,但增强型的具体实现则由各个厂家提供且以第三方jar包的形式部署在项目的CLASSPATH下。其中抽象类 javax.xml.parsers.DocumentBuilderFactory的类方法newInstance(String factoryClassName, ClassLoader classLoader) 可根据二进制名称获取由各厂家具体实现的DocumentBuilderFactory实例。现在以 javax.xml.parsers.DocumentBuilderFactory.newInstance(” org.apache.xerces.jaxp.DocumentBuilderFactoryImpl”, null) 的调用形式来深入下去。

首先假设newInstance内部是以以下方式加载类的

Class.forName("org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
// 或
this.getClass().getClassLoader.loadClass("org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");

由于DocumentBuilderFactory是由Boostrap ClassLoader加载的,因此上述操作结果是通过Bootstrap ClassLoader来加载第三方类库,结果必须是ClassNotFoundException的。也就是说我们需要获取System ClassLoader或它的子加载器才能成功加载这个类。

首先想到的是通过ClassLoader.getSystemClassLoader()方法来获取System ClassLoader。然而JDK1.2又引入了另一个更灵活的方式,那就是Context ClassLoader(线程上下文类加载器,默认为System ClassLoader),通过Context ClassLoader我们可以获取System ClassLoader或它的子加载器,从而可以加载CLASSPATH和其他路径下的类库。

newInstance(String, ClassLoader)的实际实现是调用FactoryFinder.newInstance方法,而该方法则调用getProviderClass方法来获取Class实例,getProviderClass方法中则通过SecuritySupport的实例方法getContextClassLoader()来获取类加载器,代码片段如下:

ClassLoader getContextClassLoader()
    throws SecurityException
  {
    return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction()
    {
      public Object run() {
        ClassLoader cl = null;

        cl = Thread.currentThread().getContextClassLoader();

        if (cl == null) {
          cl = ClassLoader.getSystemClassLoader();
        }
        return cl;
      }
    });
  }

注意:Context ClassLoader可是要慎用哦!因为可以通过setContextClassLoader方法动态设置线程上下文类加载器,也就是有可能每次调用时的类加载器均不相同(所管辖的目录路径也不相同),在并发环境下就更容易出问题了。

(七)从源码去理解父委托机制的实现 
首先我们看看ExtClassLoader和AppClassLoader是如何创建的,目光移到sun/misc/Launcher.java文件中,而ExtClassLoader和AppClassLoader则以Luancher的内部类的形式实现。在Launcher类进入初始化阶段时会创建一个Launcher实例,其构造函数中会实例化ExtClassLoader,然后以ExtClassLoader实例作为父加载器来实例化AppClassLoader,并将AppClassLoader实例设置为主线程默认的Context ClassLoader。

public Launcher()
  {
    ExtClassLoader localExtClassLoader;
    try
    {
      // 实例化ExtClassLoader
      localExtClassLoader = ExtClassLoader.getExtClassLoader();
    } catch (IOException localIOException1) {
      throw new InternalError("Could not create extension class loader");
    }

    try
    {
      // 实例化AppClassLoader
      this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);
    } catch (IOException localIOException2) {
      throw new InternalError("Could not create application class loader");
    }
    // 主线程的默认Context ClassLoader
    Thread.currentThread().setContextClassLoader(this.loader);

    String str = System.getProperty("java.security.manager");
    if (str != null) {
      SecurityManager localSecurityManager = null;
      if (("".equals(str)) || ("default".equals(str)))
        localSecurityManager = new SecurityManager();
      else
        try {
          localSecurityManager = (SecurityManager)this.loader.loadClass(str).newInstance();
        } catch (IllegalAccessException localIllegalAccessException) {
        } catch (InstantiationException localInstantiationException) {
        } catch (ClassNotFoundException localClassNotFoundException) {
        }
        catch (ClassCastException localClassCastException) {
        }
      if (localSecurityManager != null)
        System.setSecurityManager(localSecurityManager);
      else
        throw new InternalError("Could not create SecurityManager: " + str);
    }
  }

ExtClassLoader和AppClassLoader均继承了java.net.URLClassLoader,并且仅对类的加载、搜索目录路径作修改而已。如AppClassLoader的getAppClassLoader方法:

static class AppClassLoader extends URLClassLoader
  {
    public static ClassLoader getAppClassLoader(final ClassLoader paramClassLoader)
      throws IOException
    {
      // 获取搜索、加载类的目录路径
      String str = System.getProperty("java.class.path");
      final File[] arrayOfFile = str == null ? new File[0] : Launcher.getClassPath(str);

      return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction()
      {
        public Launcher.AppClassLoader run() {
          URL[] arrayOfURL = this.val$s == null ? new URL[0] : Launcher.pathToURLs(arrayOfFile);
          // 设置类加载器的搜索、加载类的目录路径,并创建一个类加载器实例
          return new Launcher.AppClassLoader(arrayOfURL, paramClassLoader);
        }
      });
    }

在研究URLClassLoader之前我们先看看java.lang.ClassLoader,除Bootstrap ClassLoader外所有类加载器必须继承自ClassLoader。还记得 ClassLoader.getSystemClassLoader().loadClass(“org.apache.xerces.jaxp.DocumentBuilderFactoryImpl”) 吧,现在我们就从loadClass出发,看看整个类加载机制吧!

 /**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     *
     * <ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     *
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     *
     *   <li><p> Invoke the {@link #findClass(String)} method to find the
     *   class.  </p></li>
     *
     * </ol>
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     *
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     *
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @param  resolve
     *         If <tt>true</tt> then resolve the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     */
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            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 thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    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;
        }
    }

首先可以看到由于不允许同一个类加载器重复加载同一个类,因此当对同一个类重复进行加载操作时,则通过findLoadedClass方法来返回已有的Class实例。继续往下看,loadClass方法内部主要为双亲委派模型的实现,实际的类加载操作是在findClass方法中实现的。

ClassLoader中指提供findClass的定义,具体实现由子类提供。而URLClassLoader的findClass则是通过URLClassPath实例来获取类的二进制数据,然后调用defineClass对二进制数据进行初步验证,然后在由ClassLoader的defineClass进行其余的验证后生成Class实例返回。

protected Class<?> findClass(final String paramString)
    throws ClassNotFoundException
  {
    try
    {
      return (Class)AccessController.doPrivileged(new PrivilegedExceptionAction()
      {
        public Class run() throws ClassNotFoundException {
          // ucp为URLClassPath实例
          // 通过URLClassPath实例获取类的二进制数据
          String str = paramString.replace('.', '/').concat(".class");
          Resource localResource = URLClassLoader.this.ucp.getResource(str, false);
          if (localResource != null) {
            try {
              // 调用URLClassLoader的defineClass方法验证类的二进制数据并返回Class实例
              return URLClassLoader.this.defineClass(paramString, localResource);
            } catch (IOException localIOException) {
              throw new ClassNotFoundException(paramString, localIOException);
            }
          }
          throw new ClassNotFoundException(paramString);
        }
      }
      , this.acc);
    }
    catch (PrivilegedActionException localPrivilegedActionException)
    {
      throw ((ClassNotFoundException)localPrivilegedActionException.getException());
    }
  }

总结一下, 类加载过程为loadClass -> findClass -> defineClass。loadClass为双亲委派的实现,defineClass为类数据验证和生成Class实例,findClass为获取类的二进制数据。

(八)实现自定义的类加载器 
要点:继承java.lang.Classloader。实现findClass方法



import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MyClassLoader extends ClassLoader{
    String dir;
    String name;

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        MyClassLoader myclassloader1=new MyClassLoader("myclassloader1");
        myclassloader1.setDir("/usr/local/Workspaces/MyEclipse 2015 CI/classloader/dir1");

        MyClassLoader myclassloader2=new MyClassLoader(myclassloader1,"myclassloader2");
        myclassloader2.setDir("/usr/local/Workspaces/MyEclipse 2015 CI/classloader/dir2");

        MyClassLoader myclassloader3=new MyClassLoader("myclassloader3");
        myclassloader3.setDir("/usr/local/Workspaces/MyEclipse 2015 CI/classloader/dir3");

        testLoad1(myclassloader2);

        testLoad2(myclassloader2);

//      testLoad1(myclassloader3);
//      
//      testLoad2(myclassloader3);

        System.out.println(System.nanoTime());


    }
    public static void testLoad1(MyClassLoader classLoader) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
        Class<?> clazz = classLoader.loadClass("AA");

        Object object = clazz.newInstance();
    }
    public static void testLoad2(MyClassLoader classLoader) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
        Class<?> clazz = classLoader.loadClass("AA");

        System.out.println("classloader name->"+classLoader);
        Object object = clazz.newInstance();
        AA aa=new AA();
        System.out.println("a : "+aa.a);
    }
    MyClassLoader(ClassLoader parent,String name){
        super(parent);// 显式指定该类加载器的父加载器
        this.name=name;
    }
    public MyClassLoader(String name) {
        super();//让系统类加载器成为该类加载器的父加载器
        if(name == null || name.length() <= 0)  
            throw new NullPointerException();  
        this.name=name;
    }
    public String getDir() {
        return dir;
    }


    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setDir(String dir) {
        this.dir = dir;
    }


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String separatorProperty = System.getProperty("file.separator");
        String path=name.replace(".", separatorProperty).concat(".class");
        byte[] classByteData=null;
            try(FileInputStream fileInputStream = new FileInputStream(dir+separatorProperty+path)) {

                classByteData=new byte[fileInputStream.available()];
                fileInputStream.read(classByteData, 0, classByteData.length);

            } catch (FileNotFoundException e) {
                throw new ClassNotFoundException();
            } catch (IOException e) {
                throw new ClassNotFoundException();
            }

        return defineClass(name, classByteData, 0,classByteData.length);
    }
}

附属的AA类



import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MyClassLoader extends ClassLoader{
    String dir;
    String name;

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        MyClassLoader myclassloader1=new MyClassLoader("myclassloader1");
        myclassloader1.setDir("/usr/local/Workspaces/MyEclipse 2015 CI/classloader/dir1");

        MyClassLoader myclassloader2=new MyClassLoader(myclassloader1,"myclassloader2");
        myclassloader2.setDir("/usr/local/Workspaces/MyEclipse 2015 CI/classloader/dir2");

        MyClassLoader myclassloader3=new MyClassLoader("myclassloader3");
        myclassloader3.setDir("/usr/local/Workspaces/MyEclipse 2015 CI/classloader/dir3");

        testLoad1(myclassloader2);

        testLoad2(myclassloader2);

//      testLoad1(myclassloader3);
//      
//      testLoad2(myclassloader3);

        System.out.println(System.nanoTime());


    }
    public static void testLoad1(MyClassLoader classLoader) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
        Class<?> clazz = classLoader.loadClass("AA");

        Object object = clazz.newInstance();
    }
    public static void testLoad2(MyClassLoader classLoader) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
        Class<?> clazz = classLoader.loadClass("AA");

        System.out.println("classloader name->"+classLoader);
        Object object = clazz.newInstance();
        AA aa=new AA();
        System.out.println("a : "+aa.a);
    }
    MyClassLoader(ClassLoader parent,String name){
        super(parent);// 显式指定该类加载器的父加载器
        this.name=name;
    }
    public MyClassLoader(String name) {
        super();//让系统类加载器成为该类加载器的父加载器
        if(name == null || name.length() <= 0)  
            throw new NullPointerException();  
        this.name=name;
    }
    public String getDir() {
        return dir;
    }


    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setDir(String dir) {
        this.dir = dir;
    }


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String separatorProperty = System.getProperty("file.separator");
        String path=name.replace(".", separatorProperty).concat(".class");
        byte[] classByteData=null;
            try(FileInputStream fileInputStream = new FileInputStream(dir+separatorProperty+path)) {

                classByteData=new byte[fileInputStream.available()];
                fileInputStream.read(classByteData, 0, classByteData.length);

            } catch (FileNotFoundException e) {
                throw new ClassNotFoundException();
            } catch (IOException e) {
                throw new ClassNotFoundException();
            }

        return defineClass(name, classByteData, 0,classByteData.length);
    }
}

附属的BB类



public class BB {
    public BB() {
        System.out.println("---------B----------------"+this.getClass().getClassLoader());
    }
}

程序说明: 
1.把三个文件编译到同一文件夹下,那么默认都是用系统加载器来加载 
2.如果将MyClassloader 和BB放到/usr/local/Workspaces/MyEclipse 2015 CI/classloader/dir1 把 AA放到/usr/local/Workspaces/MyEclipse 2015 CI/classloader/dir2下,可知 当前MyClassLoader由系统加载器加载,而AA指定由classloader2加载,classloader2将加载请求一级级的委托后最终还是只能由classloader2自己才能加载,而在AA中创建BB对象的时候因为AA类的加载器为classloader2,所以加载BB的请求在经由classloader2向父加载器委托后最终被系统加载器加载。代码继续加载回到MyCLassloader中在加载到AA aa=new AA();时由于MyClassloader的加载器为系统加载器,所以加载AA aa=new AA()这句代码的请求只能由系统加载器向父加载器委托,但是父加载器都不能加载,系统加载器自身也不能加载,所以抛出了ClassNotFoundException。为了使程序不报错可以使用java -cp 重新指定classpath 为/usr/local/Workspaces/MyEclipse 2015 CI/classloader/dir2 ,因为AA由classloader2加载而AA与MyClassloader属于不同的命名空间。

注意:如果两个不同的命名空间中的相互不可见的时候,可以通过java的反射来访问到访问到类的属性和方法,同一个命名空间中的类是相互可见的,子加载器命名空间包括所有的父加载器命名空间,所以所有父加载器加载出来的类都是对子加载器可见的所以AA类中可以使用MyClassloader类,父加载器命名空间中的类不能访问子命名空间中的类。如果两个类的加载器不存在父子关系,那么他们是相互不可访问的。

四、深入理解Java程序生命周期-连接 
(一)类的验证: 
1.类文件的结构检查,确保类文件遵从Java类文件的固定格式。

2.语义检查,确保类本身符合Java语言的语法规定,比如验证final类型的类没有子类,以及final类型的方法没有被覆盖。注意,语义检查的错误在编译器编译阶段就会通不过,但是如果有程序员通过非编译的手段生成了类文件,其中有可能会含有语义错误,此时的语义检查主要是防止这种没有编译而生成的class文件引入的错误。(比如说如果使用javac编译的.class文件是标准的格式,但是如果有人恶意的使用手动编写的方式产生一个class并且使他继承于一个final修饰的类那么就违反了java的的语法结构)

3.字节码验证,确保字节码流可以被Java虚拟机安全地执行。 字节码流代表Java方法(包括静态方法和实例方法),它是由被称作操作码的单字节指令组成的序列,每一个操作码后都跟着一个或多个操作数。字节码验证步骤会检查每个操作码是否合法,即是否有着合法的操作数。

4.二级制兼容性的验证,确保相互引用的类之间的协调一致。例如,在Worker类的gotoWork()方法中会调用Car类的run()方法,Java虚拟机在验证Worker类时,会检查在方法区内是否存在Car类的run()方法,假如不存在(当Worker类和Car类的使用的编译版本不同版本不兼容就会出现这种问题,一般jdk都是向前兼容的,但是没有向后兼容的能力),就会抛出NoSuchMethodError错误。

(二)类的准备 
在准备阶段,Java虚拟机为类的静态变量分配内存空间并且设置对应的默认初始值。在分配的时候按照基本类型所占的内存空间给他们分配空间,比如int4字节,double8字节等等并且问他们附上初值零。 
(三)类的解析 
在解析阶段,Java虚拟机会把类的二级制数据中的符号引用替换为直接引用。例如在Worker类的gotoWork()方法中会引用Car类的run()方法。

 public void gotoWork() {

             car.run();// 这段代码在Worker类的二进制数据中表示为符号引用

      }

在Worker类的二进制数据中,包含了一个对Car类的run()方法的符号引用,它由run()方法的全名和相关描述符组成。 
  在解析阶段,Java虚拟机会把这个符号引用替换为一个指针,该指针指向Car类的run()方法在方法区内的内存位置,这个指针就是直接引用。(这里以往大家的理解都是在java中是不存在指针的,现在应该应该纠正为在面向应用开发的时候java是不存在指针的) 
  (四)类的初始化 
  步骤 
  1.类是否被加载和连接,没有则先进行加载和连接 
  2.类是否存在直接父类,如果有并且直接父类没有被初始化,那就先进行直接父类的初始化。 
  3.类中是否存在初始化语句,如果存在,存在则从上至下执行这些初始化语句

  初始化途径 
  1.在静态变量的申明出进行初始化; 
  2.在静态代码块中进行初始化 
  初始化时机 
  满足主动使用的六种情况 
  调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。 
   
  特别注意:只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用

五、类的卸载

当AA类被加载、连接和初始化后,它的生命周期就开始了。

  当代表AA类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,AA类在方法区内的数据也会被卸载,从而结束Sample类的生命周期。

  由此可见,一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期。 
   
引用关系

这里写图片描述 
  加载器和Class对象:

  在类加载器的内部实现中,用一个Java集合来存放所加载类的引用。

  另一方面,一个Class对象总是会引用它的类加载器。调用Class对象的getClassLoader()方法,就能获得它的类加载器。

  由此可见,Class实例和加载它的加载器之间为双向关联关系。

  类、类的Class对象、类的实例对象:

  一个类的实例总是引用代表这个类的Class对象。

  在Object类中定义了getClass()方法,这个方法返回代表对象所属类的Class对象的引用。

  此外,所有的Java类都有一个静态属性class,它引用代表这个类的Class对象。

类的卸载

  由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。

  前面介绍过,Java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。

  Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。

  由用户自定义的类加载器加载的类是可以被卸载的。

 classloader2变量和aa变量间接应用代表AA类的Class对象,而clazz变量则直接引用它(classloader2)。

  如果程序运行过程中,将上图左侧三个引用变量都置为null,此时AA对象结束生命周期,MyClassLoader对象结束生命周期,代表AA类的Class对象也结束生命周期,AA类在方法区内的二进制数据被卸载。

  当再次有需要时,会检查AA类的Class对象是否存在,如果存在会直接使用,不再重新加载;如果不存在AA类会被重新加载,在Java虚拟机的堆区会生成一个新的代表Sample类的Class实例(可以通过哈希码查看是否是同一个实例)。 
  文章最后附上阿里往年关于类的生命周期的笔试题,如果大家可以不看解答就能把这个题目做出说明大家对类的生命周期已经很清楚了。 
  

public class T  implements Cloneable {
    public static int k = 0;
    public static T t1 = new T("t1");
    public static T t2 = new T("t2");
    public static int i = print("i");
    public static int n = 99;

    public int j = print("j");

    {
        print("构造块");
    }

    static {
        print("静态块");
    }

    public T(String str) {
        System.out.println((++k) + ":" + str + "    i=" + i + "  n=" + n);
        ++n;
        ++i;
    }

    public static int print(String str) {
        System.out.println((++k) + ":" + str + "   i=" + i + "   n=" + n);
        ++n;
        return ++i;
    }

    public static void main(String[] args) {

    }
}

二、加载过程分析:

执行main时,先加载所在类,声明静态变量,并初始化静态变量执行静态代码块(按顺序执行)

初始化到t1时,暂停类加载,先实例化,此时k=0,而i,n都未初始化,系统默认值为0 
初始化j时,k自增为1,i,n为0,输出“1:j i=0 n=0”,n,i自增为1 
执行代码块,输出“2:构造块 i=1 n=1”,n,i自增为2 
执行构造函数,输出“3:t1 i=2 n=2”,n,i自增为3

初始化到t2时,暂停类加载,先实例化,此时k=3,i,n都还未初始化,但已自增为3 
初始化j时,k自增为4,i,n未初始化为3,输出“4:j i=3 n=3”,n,i自增为4 
执行代码块,输出“5:构造块 i=4 n=4”,n,i自增为5 
执行构造函数,输出“6:t2 i=5 n=5”,n,i自增为6

初始化i,输出“7:i i=6 n=6”,n,i自增为7,返回自增后的i赋值给i 
初始化n,赋值99 
执行静态块,输出“8:静态块 i=7 n=99”,i自增为8,n自增为100

完成类加载

三、涉及知识点: 
1.类加载过程: 
加载某类前先加载其父类 
加载某类时,先声明静态成员变量,初始化为默认值,再初始化静态成员变量执行静态代码块 
初始化静态成员变量执行静态代码块时,是按顺序执行(初始化静态成员变量的本质就是静态代码块)

2.实例化过程: 
对某类实例化前,先对其父类进行实例化 
实例化某类时,先声明成员变量,初始化为默认值,再初始化成员变量执行代码块 
初始化成员变量执行代码块时,是按顺序执行

3.在某类加载过程中调用了本类实例化过程(如new了本类对象),则会暂停类加载过程先执行实例化过程,执行完毕再回到类加载过程

4.类的主动使用与被动使用: 
主动使用例子: 
1):最为常用的new一个类的实例对象 
2):直接调用类的静态方法。 
3):操作该类或接口中声明的非编译期常量静态字段 
4):反射调用一个类的方法。 
5):初始化一个类的子类的时候,父类也相当于被程序主动调用了 
(如果调用子类的静态变量是从父类继承过来并没有复写的,那么也就相当于只用到了父类的东东,和子类无关, 
所以这个时候子类不需要进行类初始化)。 
6):直接运行一个main函数入口的类。

所有的JVM实现,在首次主动使用某类的时候才会加载该类。

被动使用例子: 
7):子类调用父类的静态变量,子类不会被初始化。只有父类被初始化。对于静态字段,只有直接定义这个字段的类才会被初始化. 
8):通过数组定义来引用类,不会触发类的初始化,如SubClass[] sca = new SubClass[10]; 
9):访问类的编译期常量,不会初始化类

5.编译期常量: 
写到类常量池中的类型是有限的:String和几个基本类型 
String值为null时,也不会写到类常量池中 
使用new String(“xx”)创建字符串时,得到的字符串不是类常量池中的

6.对于通过new产生一个字符串(假设为 ”china” )时,会先去常量池中查找是否已经有了 ”china” 对象, 
如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此 ”china” 对象的拷贝对象。

7.类成员变量才会有默认的初始值 
byte:0(8位) 
short:0(16位) 
int:0(32位) 
long:0L(64位) 
char:\u0000(16位),代表NULL 
float:0.0F(32位) 
double:0.0(64位) 
boolean: flase

8.局部变量声明以后,Java 虚拟机不会自动的为它初始化为默认值。 
因此对于局部变量,必须先经过显示的初始化,才能使用它。 
如果编译器确认一个局部变量在使用之前可能没有被初始化,编译器将报错。

9.数组和String字符串都不是基本数据类型,它们被当作类来处理,是引用数据类型。 
引用数据类型的默认初始值都是null。

至此java中类在jvm中的整个生命周期解析就已结束,希望能帮到大家。有错的地方也欢迎指正。

猜你喜欢

转载自blog.csdn.net/Weidong32768/article/details/82021223