JVM学习笔记-类加载器和类加载机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_31793791/article/details/82186315

1.类加载器Classloader:

每一次运行程序都会启动一个Java虚拟机,程序依靠jvm运行,jvm结束,程序结束

<1>.java虚拟机结束生命周期的情况:

a.System.exit();

b.程序正常结束

c.程序异常或错误非正常退出

d.操作系统错误导致虚拟机结束

<2>.类的加载,连接,初始化

a.加载:查找并加载类的二进制数据

class文件中的二进制数据从硬盘到内存中,将其存放到方法区中,

然后堆区创建java.lang.Class对象,用来封装类在方法内的数据结构,Class时整个反射的接口,通过它获取类的数据结构

·加载.class文件方式:

-文件系统直接加载

-网络下载.class文件

-zip,jar中加载.class文件

-专用数据库提取

-将java源文件.java文件动态编译为.class文件(比如web应用等,spring)

(类加载的最终产品是位于堆区的Class对象)

b.连接:验证:确保被加载的类的正确性,(恶意用户手工生成class字节码文件,平时javac生成不会出错)

准备:为类的静态变量分配内存,并将其初始化为默认值。默认值:整形0,boolean false,引用null

解析:符号引用转换为直接引用

c.初始化:为类的静态变量赋予正确的初始值,即用户赋予的值

               public class test{

                    private static int a = 3;
               }

               //相当于
               public class test{
                    private static int a;//连接步骤值为0
                    static{
                        a = 3;//初始化时候赋值3
                    }
               })

<3>.java对类的使用方式分为两种:

--主动使用

·创建类的实例(new test())

·访问类或者接口的静态变量,或复制(int b = test.a)

·调用类的静态方法(test.dosomething())

·反射(Class.forName("testclass"))

·初始化类的子类(b extends a;b b1 = new b());

·jvm启动时被表明启动的类,比如包含main的类

--被动使用

除了上述六种外都叫做对垒的被动使用,都不会导致类的初始化

!!!!所有的java虚拟机实现必须在每个类或接口被java虚拟机主动使用时才初始化他们

<4>.举个栗子:

静态块的代码编写顺序导致静态变量初始化后值不同:

class Singleton{

    //位置1
    //private static Singleton singleton = new Singleton();
    //准备阶段值为null,初始化阶段指向实例
  
    public static int counter1;
    //位置1:准备阶段0,singleton初始化时赋值1
    //位置2:准备阶段0,singleton初始化时赋值1

    public static int counter2 = 0;
    //位置1:准备阶段0,初始化实例时候为1,初始化counter2时候为0,最终为0
    //位置2:准备阶段0,初始化时为0,singleton初始化时为1,最终为1
  
    //位置2
    private static Singleton singleton = new Singleton();

    private Singleton() {
        counter1++;
        counter2++;
    }

    public static Singleton getInstance(){
        return singleton;
    }
}


public class t1 {
    public static void main(String[] args){
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1=" + singleton.counter1);
        System.out.println("counter2=" + singleton.counter2);

    }
}

2.java提供的类加载器

<1>.java虚拟机自带的加载器

--根类加载器(Bootstrap, c++编写的,java中无法获得)

--扩展类加载器 (Extend)

--系统/应用加载器 (System)

<2>.用户自定义的类加载器:

a.都是java.lang.Class的子类

<3>.类的加载

!!!!类加载器不需要等到某各类被首次主动使用时才加载他

-jvm允许类加载器在预料到某各类将要被使用时预先加载他,如果出现错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误,

比如jdk1.6编译的.class放到jdk1.5可能报错)

-若这个类一直没有被程序主动使用,那么类加载器就不会报告错误

<4>.类的验证

将读入内存类的二进制数据合并到虚拟机的运行环境中去

类的验证的内容:

-类文件结构检查,确保类文件遵从java文件的固定格式

-语义检查,确保类本身符合java语言规定,比如验证final类型的类没有子类

-字节码验证

-二进制兼容性测试(jdk版本等)

<5>.类的准备

jvm为类的静态变量分配内存,并设置默认的初始值。

        public class Sample{

            private static int a = 1;

            private static long b;

            static{

                b = 2;

            }

        }

准备阶段为a准备4个字节空间并分配初值0,b8个字节

<6>.类的解析

java虚拟机吧类的二进制中的符号引用替换为直接引用。如worker类的gotoWork()方法引用Car类的run()方法

在解释阶段,把符号引用替换为指针,指针指向了Car中run方法在方法区的内存位置

<7>.类的初始化

初始化阶段执行类的初始化语句,为类的静态变量赋予初始值,按照顺序执行

a.在静态变量声明处初始化

b.在静态代码块中初始化

3.类加载器

父亲委托机制:类加载器加载累到jvm中,jdk1.2开始使用父亲委托机制,除了根类加载器外,其他的类加载器都只有一个父亲加载器。当Loader1请求加

载sample类时,

若父亲加载器能够加载,则使用父亲加载器,否则是有自身加载器加载。

jvm自带三种加载器:

bootstrap:没有父加载器,加载虚拟机的核心类库,比如java.lang包等。以来底层操作系统,没有继承java.lang.ClassLoader。属于虚拟机实现的一部分。

extend:父加载器时bootstrap,加载jdk目录下jre\lib\ext或者java.et.dirs下加载类库。

system:父加载器是extend,用户自定义的类加载器的默认父加载器,从classpath下加载类,时java.lang.ClassLoader的子类。

(父子加载器并非继承关系,也就是说子加载器不一定继承父加载器,父子加载器可能是同一个加载器的两个实例,但是是父子加载器)

用户自定义的类加载器必须继承ClassLoader。父子加载器形成树形结构,除了根类加载器,其他的有且仅有一个父加载器。

(自定义loader尝试加载sample,若加载了,直接返回sample类的引用,否则,loader请求system加载,system请求extend,extend请求bootstrap。bootstrap

尝试加载,不行抛给extend加载,以此类推。若都无法加载,抛出classnotfoundException)

定义类加载器:成功加载sample类的加载器

初始化加载器:定义类加载器的子加载器都称作初始化加载器

用户自定义类加载器时若没有指定父加载器,则默认将系统类加载器作为他的父加载器。

优点:提高软件的安全性,在这种机制下,用户自定义的类加载器不可能加载应该由父加载器加载的可靠品类,从而防止不可靠甚至恶意的代码代替父加载器加载

可靠代码。

---命名空间:每个加载器都有自己的命名空间,由该加载器和所有父加载器所加载的类构成。同一个命名空间中,不会出现类的完整名字相同的两个类。

---运行时包:有同一个类加载器加载的属于相同包的类组成运行时报。定义类加载器和包名都相同则同一个运行时包。只有属于同一个运行时包的类才能相互访问包

可见的类和类成员。比如用户自定义包com.self.spy和核心类库java.lang.*,不同的运行时包,保证了com.self.spy不能访问java.lang.*中的成员。

4.用户自定义类加载器

自己写类加载器需要继承java.lang.Classloader并且重写findClass方法。

尝试构造这样的一个结构:(案例Sample, Dog, MyClassLoader)

bootstrap

| \

extend loader3

|

system

|

loader1

|

loader2

其中,loader1指定加载路径为serverlib(Dog, Sample),loader2指定加载路径为clientlib(空),loader3为otherlib(Dog, Sample)。在syslib下执行

java MyClassLoader可以看到执行结果。可以直接运行MyClassLoader,相当于loader2可以依靠System加载Dog和Sample

5.类的卸载

由jvm自带的类加载器加载的类在jvm整个生命周期内不会被卸载,用户自定义的类加载器加载的类是可以被卸载的。loader = null;clazz = null;sample = null;

猜你喜欢

转载自blog.csdn.net/qq_31793791/article/details/82186315