javaJVM_虚拟机_类加载器

2018-09-12  本文已影响0人  Shokka

https://www.cnblogs.com/prayers/p/5515245.html
https://www.cnblogs.com/eastday/p/8124580.html
https://www.cnblogs.com/IUbanana/p/7067362.html
http://www.cnblogs.com/ityouknow/p/5603287.html
http://www.importnew.com/25295.html

一、java虚拟机的生命周期

虚拟机存在的目的是让java程序能够正常运行,所以它的生命周期也是伴随着java程序的初始化与终止。服务器上有多少个java程序正在运行,就有多少个java虚拟机在维护这个程序。
java虚拟机的初始化以java程序main方法作为入口,main方法必须是public static void 并以字符串数组作为参数,可以不适用main作为方法名,但是必须在虚拟机中配置好。main方法被初始线程初始化为初始化线程。
java线程分为守护线程与普通线程,守护线程是java虚拟机的线程,负责守护普通线程的正常运行,例如垃圾回收线程。
普通线程为java程序内的线程,当所有普通线程执行完毕结束,则java虚拟机生命周期结束。
通过thread.setDaemon(true)可以将普通线程设置为守护线程

public class JAVAtest {

    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("守护开始");
                for (int i = 0 ; i < 100 ; i ++) {
                    try {
                        Thread.sleep(100) ;
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(i);
                }
            }
        };
//        thread.setDaemon(true);
        thread.start();

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(12);
            }
        };
        Thread thread1 = new Thread(runnable);
        thread1.start();
    }
}

二、java虚拟机的体系结构

1.类加载器子系统:负责加载程序中的类型(类和接口)
2.执行引擎:负责执行被加载类中包含的指令
3.运行时数据区:负责保存字节码、被加载类的其他额外信息

三、类加载器子系统

1.类的唯一性:

对于任意一个类,都需要由加载它的类加载器和类的全限定名一同确定其在Java虚拟机中的唯一性。(此处的类加载器由于采用双亲委派模型,所以真正加载类的可能并不是你所使用的类加载器,例如两个自定义类加载器继承了ClassLoader,所以加载类的为同一个顶层类加载器)每一个类被加载为Class类的实例,类加载器对象与class对象都保存在堆中,类信息保存在方法区中。

2.类加载器:
2.1类加载器类别:

从java虚拟机的角度划分:
a.启动类加载器(Bootstrap ClassLoader):这个类加载器使用C++语言实现,是虚拟机自身的一部分。
b.其他的类加载器:这些类加载器由Java语言实现,独立于虚拟机,并且全部都继承自抽象类java.lang.ClassLoader。

从java继承级别划分:
a.启动类加载器(Bootstrap ClassLoader):虚拟机的一部分,由c++编写。这个类加载器负责将存放在<JAVA_HOME>\lib目录中,或者被-Xbootclasspath虚拟机参数指定的路径中,并且是虚拟机识别的类库加载到虚拟机内存中。
b.扩展类加载器(Extension ClassLoader):该加载器器是用JAVA编写,且它的父类加载器是Bootstrap,是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类库。开发者可以这几使用扩展类加载器。
c.应用程序类加载器(Application ClassLoader):由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

注意:类加载器的体系并不是“继承”体系,而是委派体系,大多数类加载器首先会到自己的parent中查找类或者资源,如果找不到才会到自己本地查找。类加载器的委托行为动机是为了避免相同的类被加载多次。

2.3双亲委派模型:
image.png image.png

类加载器之间的层次关系,称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父类加载器。注意,这里类加载器之间的父子关系一般不会以继承的关系实现,而是使用组合关系来复用父加载器的代码。

2.4双亲委派模型的工作过程:

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该首先传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

2.5双亲委派模型的实现

类加载器均是继承自java.lang.ClassLoader抽象类。首先,我们看一看java.lang.ClassLoader类的loadClass()方法:

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 {
                        //父加载器为null,说明this为扩展类加载器的实例,父加载器为启动类加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果父加载器抛出ClassNotFoundException
                    // 说明父加载器无法完成加载请求
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
 
                if (c == null) {
                    // 如果父加载器无法加载
                    // 调用本身的findClass方法来进行类加载
                    // 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;
        }
    }

通过进一步分析标准扩展类加载器(sun.misc.LauncherExtClassLoader)和系统类加载器(sun.misc.LauncherAppClassLoader)的代码以及其公共父类(java.net.URLClassLoader和java.security.SecureClassLoader)的代码可以看出,都没有重写java.lang.ClassLoader中默认的委派加载规则——loadClass(…)方法。所以这些类加载器都沿用了ClassLoader的双亲委派规则

2.6双亲委派模型的优点

a.安全性:通过使用双亲委派模型,同一个类由不同的类加载器加载出来仍然是同一个类,不会出现使java程序出现混乱。避免类被重复加载

3.类加载机制

加载 — 验证 — 准备 — 解析 — 初始化
加载:通过一个类的全限定名获取其对应的二进制流,将二进制流内类的静态存储结构转变为方法区中的运行时数据结构。在java堆中生成一个代表这个类的对象,作为方法区对应数据的入口。(这个阶段可控性是最高的,因为可以在此阶段选择加载类的类加载器)
注意此时堆中的对象为类对象。

验证:为了确保class文件字节流中包含的信息符合虚拟机的要求,不会对虚拟机造成威胁。
准备:为方法区中的静态数据初始化,此时的初始化是初始化null,0,等默认值,如果数据被final static修饰,则初始化指定值。
解析:把类中的符号引用转变为直接引用
初始化:根据程序执行类构造器<client>,为类变量赋值。构造器方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。

以下不会触发初始化:
1.通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
2.定义对象数组,不会触发该类的初始化。
3.常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
4.通过类名获取Class对象,不会触发类的初始化。
5.通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
6.通过ClassLoader默认的loadClass方法,也不会触发初始化动作。

上一篇下一篇

猜你喜欢

热点阅读