深入理解JVM(一,JVM初识类的加载和类加载器)

JVM初始工具使用。
JConsole – java监视和管理控制台
jvisualvm --(监视)可视化展示很多信息

类加载(ClassLoading)与类加载器
类加载:在java代码中,类型(类本身)的加载、连接、与初始化过程都是在程序运行期间(runtime)完成的。类的加载是指将.class文件中的二进制数据都入到内存中,将其放在运行时数据区的方法区中,然后在内存中创建一个java.lang.Class对象(规范中并没有说明将Class对象放在哪里,Hotspot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构。
类加载(ClassLoading)
① 加载:查找并加载类的二进制数据,即将字节码的.class文件加入内存。配置option:“-XX:+TraceClassLoading”,在控制台打印出追踪类的加载过程。
这里补充配置问题:

-XX : ±

② 连接:① 验证:确保被加载的类的正确性;② 准备:为类的静态变量分配内存,并将其初始化为默认值;③ 解析:把类中的符号引用转换为直接引用。简单来说,即类与类之间的相互调用,校验字节码文件等。

③ 初始化:为静态变量的赋予正确的初始值。

④ 类的使用—类的实例化 : (程序员日常的操作)为新的对象分配内存,为实例变量赋默认值,为实例变量赋正确的初始值。java编译器为它编译的每一个类至少生成一个初始化方法。在java的class文件中,这个实例初始化方法被称为“。针对源代码中每一个类的构造方法。java编译器都产生一个“方法

⑤垃圾回收和对象终结( 类的卸载 ): 卸载的场景很难遇到。

	public static int a = 1; // 静态变量的解释:在准备阶段a=0,初始化阶段后a=1;

深入理解JVM(一,JVM初识类的加载和类加载器)_第1张图片
上述的类的加载、连接、与初始化化过程中:
1,java程序对类的使用方式可分为两种:
①,主动使用。
a.创建类的实例。// 举例就是去new一个东西
b.访问某个类或者是接口的静态变量,或者对该静态变量赋值。// 举例对静态变量的取值和赋值。记助符:getstatic/putstatic
c.调用类的静态方法。// 同b,invokestatic
d.反射。// Class.forName(“包名”);
e.初始化一个类的子类。 // 子类初始化时,父类以及多重父类都会初始化。
f.java虚拟机启动时被标明为启动类
g.从jdk1.7开始提供了动态语言支持,java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化。
记助符:
ide:表示将int,float或是String类型的常量池中推送至栈顶。
bipush:表示将单字节的常量(-128,127)推送至栈顶。
sipush:表示将短整型常量值(-32768,32767)推送至栈顶。
icont_1:表示将int类型1推送至栈顶( icont_1, icont_5)。超过6去到bipush
anewarrary:表示创建引用类型的(如类,接口,数组)数组,并将其引用压入栈顶。
newarrary:表示创建一个指定的原始类型(如int,flaot,char)的数组,并将其引用值压入栈顶。

②,被动使用。除了上面的情况,都被看作被动使用,不会导致类的初始化。
2,所有的java虚拟机实现必须在每个类或者接口被java程序“首次主动使用”时才初始化他们。

package com.auto.demo;

/**
 * 主动使用的说明
 * 对静态字段来说,只有直接定义了该字段的类才会被初始化。
* @author zhouyi
 *
 */
public class MyAuto {
public static void main(String[] args) {
    // System.out.println(Children.str);
    System.out.println(Children.str1);
    }
}

class MyParent1 {
public static String str = "hello";
static {
    System.out.println("MyParent1 maybe block");
}
}

class Children extends MyParent1 {
public static String str1 = "welcome";
static {
    System.out.println("Children maybe block");
}
}

运行结果:(主动被动使用举例,下面的结果是放开注释的得到的两个结果对比)
深入理解JVM(一,JVM初识类的加载和类加载器)_第2张图片

public class MyAuto {
public static void main(String[] args) {
    System.out.println(MyParent1.str);
    //System.out.println(Children.str1);
}
}

class MyParent1 {
// 我们发现加上final,静态块没有被打印
// 原因在编译阶段常量就会被存入到调用这个常量的这个方法所在的类的常量池当中。
// 比如这里会放到MyAuto类的常量池中。
public static final String str = "hello";
static {
    System.out.println("MyParent1 maybe block");
}
}

运行结果:
在这里插入图片描述

public class MyAuto {
public static void main(String[] args) {
    System.out.println(MyParent1.str);
}
}

class MyParent1 {
// 前面实在编译期就已经确定,但这里是在运行期才能确定主动使用
// 显然导致了这个类被初始化
public static final String str = UUID.randomUUID().toString();
static {
    System.out.println("MyParent1 maybe block");
}
}

深入理解JVM(一,JVM初识类的加载和类加载器)_第3张图片

public class MyAuto {
public static void main(String[] args) {
    MyParent1 myParent1 = new MyParent1(); // new时主动使用
}
}

class MyParent1 {
static {
    System.out.println("MyParent1 maybe block");
}
}

运行结果:
在这里插入图片描述

public class MyAuto {
public static void main(String[] args) {
    MyParent1[] myParent1 = new MyParent1[1]; // new类数组时,不会初始化(高维数组一样)这里是类数组,延伸原始类型的数组
    System.out.println(myParent1.getClass()); // 运行期jvm帮助我们动态生成,表示为[Lcom.auto.demo.MyParent1
}  
}

class MyParent1 {
static {
    System.out.println("MyParent1 maybe block");
}
}

运行结果:
在这里插入图片描述

public class MyAuto {

public static void main(String[] args) {
    Singleton singleton = Singleton.getInstance();

    System.out.println("counter1:" + singleton.counter1);
    System.out.println("counter2:" + singleton.counter2);
}
}

class Singleton {

public static int counter1;

private static Singleton singleton = new Singleton();

private Singleton() {
    counter1++; // 准备阶段的意义,不用赋值也拿来使用的尴尬。
    counter2++;

    System.out.println(counter1); // 类的加载:连接过程的初始赋值
    System.out.println(counter2);
}

public static int counter2 = 0; // 真正的初始化

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

运行结果:(初始化,从上到下的)
深入理解JVM(一,JVM初识类的加载和类加载器)_第4张图片

当一个接口在初始化时,并不要求其父类接口都完成了初始化,只有到真正使用到父接口的时候,才会初始化。 (不好举例子,不直观,就不给出)

加载.class的方式:
① 从本地系统中直接加载(第一方库)
② 通过网络下载.class文件(第二方库)
③ 从zip,jar等归档文件中加载.class文件(例如:第三方库)
④ 从专有的数据库中提取.class文件
⑤ 将java源文件动态编译为.class

2,类加载器(ClassLoader): java虚拟机与程序的生命周期。
在如下几种情况下,java虚拟机将结束生命周期。
① 执行了System.exit()方法。
② 程序正常执行结束。
③ 程序在执行过程中遇到了异常或错误而异常终止。
④ 由于操作系统出现错误而导致java虚拟机进程终止。

类的加载最终产品是位于内存中的class对象,Class对象封装了类在方法区内的数据结构,并且向java程序员提供了方法区内的数据结构的接口。
有两种类型的类加载器:
1,java虚拟机自带的加载器:根类加载器(Bootstrap)、扩展类加载器(Extension)、系统(应用)加载器(System)。
2,用户自定义的类加载器:java.lang.ClassLoader的子类、用户可以定制类的加载方式。

根类加载器(Bootstrap):该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等。它并没有继承java.lang.ClassLoader类。
扩展类加载器(Extension) :它的父类加载器为根加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre\lib\ext子目录中(扩展目录)下加载类库,如果把用户创建的jar文件放在这个目录下,也会自动由扩展类加载器加载,扩展类加载器是纯java类,是java.lang.ClassLoader类的子类。
系统(应用)加载器(System):它的父类加载器为扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器。系统类加载器是纯java类,是java.lang.ClassLoader类的子类。
类加载器双亲委托机制详解图:加载过程由根→到自定义加载(简单来说父亲不行,找下一辈,还不成功,再找下一辈)
直接加载某个类的加载器被称为定义类加载器;所有能成功返回class对象引用的类加载器都被称为初始类加载器。比如,有一个类是自定义加载器直接加载的,但是返回class对象的是系统类加载器。
自顶向下去尝试加载类,自底向上检查是否已经加载。
在这里插入图片描述

类加载器并不需要等到某个类被“首次主动使用”时再加载它(这是有点颠覆我们的认知的)
这是因为JVM规范允许类加载器在预料某个类将要被使用时,就预先加载它,如果在预先加载的过程中遇到了class文件缺失或者存在错误,类加载器必须在程序首次主动使用该类时才会报告错误(LinkageError错误)。如果这个类一直不被使用,那么类加载器就不会报告错误。

类的验证:类被加载后,就进入到连接阶段,连接就是将已经读入到内存的二进制数据合并到虚拟机的运行环境中去。
包括:类文件的结构检查、语义检查、字节码验证、二进制兼容性的验证。

类的初始化步骤:假如这个类还没有被加载和连接,那就先进行加载和连接;假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类;加入类中存在初始化语句,那就依次执行这些初始化语句。

类的初始化时间:java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这个条规则并不适用于接口;在初始化一个类时,并不会先初始化他所实现的接口;初始化一个接口时,并不会要先初始化初始化他的父接口;
因此,一个父接口并不会因为他的子接口或者实现类的初始化而初始化,只有当程序首次使用特定的接口的静态变量时,才会导致该接口的初始化。

类加载器:类加载器把类加载到java虚拟机中。从JDK1.2开始,类的加载过程采用父亲委托机制,这种机制能更好地保证java平台的安全。在此机制中,除了java虚拟机自带的根加载器以外,其余的类加载器都有且只有一个父加载器。当java程序请求加载器loader1加载Sample类时,loader1首先委托自己的父加载器去加载Sample类,若父加载器能加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载Sample类。

类加载器双亲委托机制
Bootstrap ClassLoader 启动类加载器:$JAVA_HOME中的jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类。

Extension ClassLoader 扩展类加载器:负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中的jre/lib.*jar或者-Djava.ext.dirs指定目录下的jar包

App ClassLoader 系统类加载器:负责加载classpath中指定的jar包及目录中class.

public class MyAuto {

public static void main(String[] args) throws Exception {
    Class clazz1 = Class.forName("java.lang.String");
    System.out.println(clazz1.getClassLoader());
    Class clazz = Class.forName("com.auto.demo.Zy");
    System.out.println(clazz.getClassLoader());
}
}

class Zy {

}

运行结果:
在这里插入图片描述

public class MyAuto {

static {
    System.out.println("MyAuto:hahahah");
}

public static void main(String[] args) throws Exception {
   System.out.println(Zyy.bb);
}
}

class Zy {
public static int aa = 3;
static {
    System.out.println("Zy:hahahah");
}
}

class Zyy extends Zy {
public static int bb = 4; // 这里如果加上final的结果是MyAuto:hahahah和4
static {
    System.out.println("Zyy:hahahah");
}
}

运行结果:(验证子类加载之前加载父类,多重继承则多次加载)
深入理解JVM(一,JVM初识类的加载和类加载器)_第5张图片

public class MyAuto {

static {
    System.out.println("MyAuto:hahahah");
}

public static void main(String[] args) throws Exception {
    Zy zy;
    System.out.println("-----------------");
    zy = new Zy();
    System.out.println("-----------------");
    System.out.println(zy.aa);
    System.out.println("-----------------");
   System.out.println(Zyy.bb);
}
}

class Zy {
public static int aa = 3; //之前已经初始化,所以不会再次初始化,不会打印两次。
static {
    System.out.println("Zy:hahahah");
}
}

class Zyy extends Zy {
public static int bb = 4; 
static {
    System.out.println("Zyy:hahahah");
}
}

运行结果:
深入理解JVM(一,JVM初识类的加载和类加载器)_第6张图片

public class MyAuto {

static {
    System.out.println("MyAuto:hahahah");
}

public static void main(String[] args) throws Exception {
   System.out.println(Zyy.bb);
   Zyy.speak();
}
}

class Zy {
public static int aa = 3;
static {
    System.out.println("Zy:hahahah");
}
static void speak() {
    System.out.println("please speak");
}
}

class Zyy extends Zy {
public static int bb = 4; // 这里如果加上final的结果是MyAuto:hahahah和4
static {
    System.out.println("Zyy:hahahah");
}
}

运行结果:(定义在哪里就主动使用,这里是对 Zy直接使用,不是Zyy)
深入理解JVM(一,JVM初识类的加载和类加载器)_第7张图片

/**
 * 调用ClassLoader类的loadClass方法加载一个类,并不是对垒的主动使用,不会导致类的初始化。
 * @author zhouyi
 *
 */
public class MyAuto {

public static void main(String[] args) throws Exception {
    ClassLoader clLoader = ClassLoader.getSystemClassLoader(); // 获取系统加载器
    Class clazz = clLoader.loadClass("com.auto.demo.Cloader");
    System.out.println(clazz);
    System.out.println("---------");
    clazz = Class.forName("com.auto.demo.Cloader");
    System.out.println(clazz);
}
}

class Cloader {
static {
    System.out.println("Zy:hahahah");
}
}

运行结果:
深入理解JVM(一,JVM初识类的加载和类加载器)_第8张图片

不同类加载器作用和加载动作分析

public class MyAuto {

public static void main(String[] args) throws Exception {
    ClassLoader clLoader = ClassLoader.getSystemClassLoader(); // 获取系统加载器

    System.out.println(clLoader);

    while (clLoader != null) {
        clLoader = clLoader.getParent();
        System.out.println(clLoader);
    }

}
}

运行结果:
在这里插入图片描述

public class MyAuto {

public static void main(String[] args) throws Exception {
    ClassLoader clLoader = Thread.currentThread().getContextClassLoader(); // 获取线程的上下文加载器
    String resource = "com/auto/demo/MyAuto.class";
    Enumeration urls = ClassLoader.getSystemResources(resource);
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        System.out.println(url);// .class文件所在磁盘位置路径
    }
}
}

运行结果:
在这里插入图片描述
另一种方式找到.class路径以及文件:(之后alt+enter就可以看到路径了)
深入理解JVM(一,JVM初识类的加载和类加载器)_第9张图片

获取ClassLoader的途径:
① clazz.getClassLoader(); 获得当前类的ClassLoader
② Thread.currentThread().getContextClassLoader(); 获得当前线程上下文的ClassLoader
③ ClassLoader.getSystemClassLoader(); 获得当前系统的ClassLoader。
④ DriverManager.getCallerClassLoader(); 获得调用者的ClassLoader。

public class MyAuto {

public static void main(String[] args) throws Exception {
    Class clazz = String.class; // 启动类加载器
    System.out.println(clazz.getClassLoader());
    Class clazz1 = Class.forName("com.auto.demo.MyAuto"); // 自定义
    System.out.println(clazz1.getClassLoader());
}
}

运行结果:
在这里插入图片描述

public class MyAuto {

public static void main(String[] args) throws Exception {
   String[] strs = new String[1]; // 要给大小的
   System.out.println(strs.getClass().getClassLoader());
   System.out.println("----------");
   MyAuto[] myAutos = new MyAuto[1]; // 对象数组
   System.out.println(myAutos.getClass().getClassLoader());
   System.out.println("----------");
   int[] ints = new  int[1];  // 基本类型(原生类型),没有ClassLoader
   System.out.println(ints.getClass().getClassLoader());
}
}

运行结果:
深入理解JVM(一,JVM初识类的加载和类加载器)_第10张图片
常识补充:delegation model : 委托模型(这里是指双亲模型) ,newInstance : 实例

自定义加载举例:

package com.auto.demo;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
*利用ClassLoader编写自定义类加载器
 * @author zhouyi
 *
*/
public class MyAuto extends ClassLoader { // 利用ClassLoader自定义类加载器

private String classLoaderName;
private final String fileExtension = ".class";

public MyAuto(String classLoaderName) {
    super(); // 将系统类加载器当作该类加载器的父类加载器
    this.classLoaderName = classLoaderName;
    System.out.println("将系统类加载器当作该类加载器的父类加载器");
}

public MyAuto(ClassLoader parent,String fileExtension) {
    super(parent); // 显示指定该类加载器的父加载器。
    this.classLoaderName = classLoaderName;
    System.out.println("显示指定该类加载器的父加载器");
}

@Override
public String toString() { // 对toString的重写,这里说下快捷重写方法,直接写方法名toString
    System.out.println("["+ this.classLoaderName +"]");
    return "["+ this.classLoaderName +"]";
}

@Override
protected Class findClass(String className) throws ClassNotFoundException {
    // 寻找一个类,返回一个类对象
    System.out.println("findClass invoked:" + className);
    System.out.println("class loader name:" + this.classLoaderName);

    byte[] data = null;
    try {
        data = this.loadClassData(className);
    } catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println(this.defineClass(className, data, 0, data.length));
    return this.defineClass(className, data, 0, data.length);
}

private byte[] loadClassData (String className) throws IOException {
    InputStream ism = null;
    byte[] data = null;
    ByteArrayOutputStream baos = null;
    try {
        this.classLoaderName = this.classLoaderName.replace(".", "/");
        ism = new FileInputStream(new File(className + this.fileExtension));
        baos = new ByteArrayOutputStream();

        int ch = 0;
        while (-1 != (ch = ism.read())) {
            baos.write(ch);
        }
        data = baos.toByteArray();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            ism.close();
            baos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    System.out.println(data.toString());
    return data;
}

public static void test(ClassLoader classLoader) throws Exception {
    Class clazz = classLoader.loadClass("com.auto.demo.MyTest"); // 自己随便建一个类。
    Object object = clazz.newInstance();
    System.out.println(object);
    System.out.println(object.getClass().getClassLoader());
}

public static void main(String[] args) throws Exception {
    MyAuto cloader = new MyAuto("cloader");
    test(cloader);
}
}

运行结果:(MyTest成功加载,但是调试结果却很奇怪,根本没有调用我们自己写的findclass等,不是我们自定义一的加载器,而是AppClassLoader加载器(也就是系统或应用加载器))
深入理解JVM(一,JVM初识类的加载和类加载器)_第11张图片

package com.auto.demo;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class MyAuto extends ClassLoader { // 利用ClassLoader自定义类加载器

private String classLoaderName;
private String path;
private final String fileExtension = ".class";

public MyAuto(String classLoaderName) {
    super(); // 将系统类加载器当作该类加载器的父类加载器
    this.classLoaderName = classLoaderName;
    System.out.println("将系统类加载器当作该类加载器的父类加载器");
}

public MyAuto(ClassLoader parent,String fileExtension) {
    super(parent); // 显示指定该类加载器的父加载器。
    this.classLoaderName = classLoaderName;
}

public void setPath(String path) {
    this.path = path;
}

@Override
public String toString() { // 对toString的重写,这里说下快捷重写方法,直接写方法名toString
    System.out.println("["+ this.classLoaderName +"]");
    return "["+ this.classLoaderName +"]";
}

@Override
protected Class findClass(String className) throws ClassNotFoundException {
    // 寻找一个类,返回一个类对象
    System.out.println("findClass invoked:" + className);
    System.out.println("class loader name:" + this.classLoaderName);

    byte[] data = null;
    try {
        data = this.loadClassData(className);
    } catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println(this.defineClass(className, data, 0, data.length));
    return this.defineClass(className, data, 0, data.length);
}

private byte[] loadClassData (String className) throws IOException {
    InputStream ism = null;
    byte[] data = null;
    ByteArrayOutputStream baos = null;

    className = className.replace(".", "/");
    try {
        ism = new FileInputStream(new File(this.path + className + this.fileExtension));
        baos = new ByteArrayOutputStream();

        int ch = 0;
        while (-1 != (ch = ism.read())) {
            baos.write(ch);
        }
        data = baos.toByteArray();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            ism.close();
            baos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    System.out.println(data.toString());
    return data;
}

 public static void main(String[] args) throws Exception {
    MyAuto cloader = new MyAuto("cloader");
    cloader.setPath("E:/ownStudy/");  // 随便选择一个路径

    Class clazz = cloader.loadClass("com.auto.demo.MyTest");
    System.out.println("class" + clazz.hashCode()); // hashCode方法,将某些东西变为散列值(即一串数字)
    Object object = clazz.newInstance();

    System.out.println(object);
}
}

这段代码验证没通过,还需要验证。不过思想是设置path,让他去执行我们自定义类加载。
先说说思路,我们要去删除classpath下的MyTest.class文件,再去执行,因为前面已经加载过了,就会默认由系统类加载器去加载,而不走自定义加载器。

经过长时间验证终于解决问题了:
原因:我验证上面的说法,删除说找不到,不在删除又不是自定义加载器。原因就在着这句话,分析发现。我的编译文件放的位置有问题。简单两说,加载后的和编译的两者出现了覆盖,即放在了同一个位置。于是我将.class文件放在了和下面这句话的路径之下,完美运行。
总结:都是上面自己随便建立了路径的错???????,这里也说明已经加载过就默认父类加载器加载,如果没有加载过多次加载也可以是自定义加载器加载
深入理解JVM(一,JVM初识类的加载和类加载器)_第12张图片
运行结果:(使用了cloader)
深入理解JVM(一,JVM初识类的加载和类加载器)_第13张图片
最好的解决方式:将main函数改为下面的()

public static void main(String[] args) throws Exception {
    MyAuto cloader = new MyAuto("cloader");
    cloader.setPath("E:/ownStudy/jvmStudy/bin/");  // 随便选择一个路径-----------------这里改了哟,以后不再随意取名了

    Class clazz = cloader.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz.hashCode()); // hashCode方法,将某些东西变为散列值(即一串数字)
    Object object = clazz.newInstance();

    System.out.println(object);
}

命名空间
关于上面利用hasCode得到数字。就是每个类自己的命名空间,命名空间由该类加载器及所有父加载器所加载的类组成。
同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。
在不同的命名空间中,有可能出现类的完整名字(包括类的包名)相同的两个类。
总结:
子加载器所加载的类能够访问父加载器的类;父加载器所加载的类无法访问到子加载器所加载的类。

    public static void main(String[] args) throws Exception {
    MyAuto cloader = new MyAuto("cloader");
    cloader.setPath("E:/ownStudy/");

    Class clazz = cloader.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz.hashCode());
    Object object = clazz.newInstance();

    System.out.println(object);

    MyAuto cloader1 = new MyAuto("cloader1");
    cloader1.setPath("E:/ownStudy/"); // jvmStudy/bin/

    Class clazz1 = cloader1.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz1.hashCode());
    Object object1 = clazz1.newInstance();

    System.out.println(object1);
}

运行结果:(在没有加载过的前提下,加载各自的自定义加载器,cloader1 和 cloader 是有各自的命名空间,是不同的,相互不可见的)
深入理解JVM(一,JVM初识类的加载和类加载器)_第14张图片

将上面main改变为:(cloader 与cloader1 是父子关系)

    public static void main(String[] args) throws Exception {
    MyAuto cloader = new MyAuto("cloader");
    cloader.setPath("E:/ownStudy/");

    Class clazz = cloader.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz.hashCode());
    Object object = clazz.newInstance();

    System.out.println(object);

    MyAuto cloader1 = new MyAuto(cloader,"cloader1");
    cloader.setPath("E:/ownStudy/"); // jvmStudy/bin/

    Class clazz1 = cloader1.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz1.hashCode());
    Object object1 = clazz1.newInstance();

    System.out.println(object1);
}

运行结果:
深入理解JVM(一,JVM初识类的加载和类加载器)_第15张图片

ublic static void main(String[] args) throws Exception {
    MyAuto cloader = new MyAuto("cloader");
    cloader.setPath("E:/ownStudy/");

    Class clazz = cloader.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz.hashCode());
    Object object = clazz.newInstance();

    System.out.println(object);

    MyAuto cloader1 = new MyAuto(cloader,"cloader1");
    cloader1.setPath("E:/ownStudy/"); // jvmStudy/bin/

    Class clazz1 = cloader1.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz1.hashCode());
    Object object1 = clazz1.newInstance();

    System.out.println(object1);

    MyAuto cloader2 = new MyAuto("cloader2");
    cloader2.setPath("E:/ownStudy/"); // jvmStudy/bin/

    Class clazz2 = cloader2.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz2.hashCode());
    Object object2 = clazz2.newInstance();

    System.out.println(object2);
}

运行结果:
深入理解JVM(一,JVM初识类的加载和类加载器)_第16张图片

public static void main(String[] args) throws Exception {
    MyAuto cloader = new MyAuto("cloader");
    cloader.setPath("E:/ownStudy/");

    Class clazz = cloader.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz.hashCode());
    Object object = clazz.newInstance();

    System.out.println(object);

    MyAuto cloader1 = new MyAuto(cloader,"cloader1");
    cloader1.setPath("E:/ownStudy/"); // jvmStudy/bin/

    Class clazz1 = cloader1.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz1.hashCode());
    Object object1 = clazz1.newInstance();

    System.out.println(object1);

    MyAuto cloader2 = new MyAuto(cloader1,"cloader2");
    cloader2.setPath("E:/ownStudy/"); // jvmStudy/bin/

    Class clazz2 = cloader2.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz2.hashCode());
    Object object2 = clazz2.newInstance();

    System.out.println(object2);
}

运行结果:(以上所有结果,可以清楚看到自定义类加载器的复杂关系)

深入理解JVM(一,JVM初识类的加载和类加载器)_第17张图片

类的卸载
当Sample类被加载、连接、初始化后,他的生命周期就开始了。当代表Sample类的class对象不在被引用,即不可触发时,Class对象就会结束生命周期,Sample类在方法区内的数据也会被卸载,从而结束Sample类的生命周期。一个类何时生命周期结束,取决于他的class对象何时结束他的生命周期。
由java虚拟机自带的类加载器所加载的类,在虚拟机生命周期中,始终不会被卸载,前面已经说过了,java虚拟机自带的类加载器包括根加载器、扩展类加载器和系统加载器。java虚拟机本身会始终引用这些类加载器,而这些类加载器则始终引用它们所加载的类的class对象,因为这些对象始终是可触及的。
由用户自定义的类加载器所加载的类是可以被加载的。

    public static void main(String[] args) throws Exception {
    MyAuto cloader = new MyAuto("cloader");
    cloader.setPath("E:/ownStudy/");

    Class clazz = cloader.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz.hashCode());
    Object object = clazz.newInstance();

    System.out.println(object);

    //cloader = null; // 这是为了体现垃圾回收
   //clazz = null;
    //object = null;
    //System.gc(); // 垃圾强制回收

   cloader = new MyAuto("cloader");
   cloader.setPath("E:/ownStudy/");

    clazz = cloader.loadClass("com.auto.demo.MyTest");
    System.out.println("class:" + clazz.hashCode());
    object = clazz.newInstance();
    }

运行结果:(可以看到中间已经发生过类的卸载,因为HashCode不一样)
深入理解JVM(一,JVM初识类的加载和类加载器)_第18张图片
实际中是类中加载各种类(重要)
前提准备类:(依然沿用上面的自己写的MyAuto类加载器)
Animal类

package com.auto.demo;

public class Animal {
// 这里解释下:为什么有getClass方法,从前面可知,一个类是通过加载器加载的.java对应的.class
// 这就体现了它存在的意义。
public Animal() {
    System.out.println("Animal is loaded by: " + this.getClass().getClassLoader());
}
}

Cat类

package com.auto.demo;

public class Cat {

public Cat() {
    System.out.println("Cat is loaded by: " + this.getClass().getClassLoader());

    new Animal();
}
}

测试类:

package com.auto.demo;

public class MyTestClass {

public static void main(String[] args) throws Exception {
    MyAuto loader1 = new MyAuto("loader1");
    loader1.setPath("E:/ownStudy/");

    Class clazz = loader1.loadClass("com.auto.demo.Cat");
    System.out.println("clazz: " + clazz.hashCode());
    // 显然已经加入Cat,但是没有被实例化。是因为路径没设置,于是上面设置了路径,你想要理解可以将路径删除对比看看结果。

    Object object = clazz.newInstance();
}
}

运行结果:(因为自定义加载器,这里我们可以看出是有加载了Cat的类加载器去加载Animal)–这里可能会给人一种疑惑,在上面不是已经说过了类的加载过程:根加载器去加载,找不到,根加载器委托扩展类加载器去加载,找不到,扩展类加载器委托系统类加载,还找不到,系统类加载委托自定义加载器。比如上面可以去尝试将Cat.class和Animal.class删除,你会发现会异常,原因因为找不到对应的文件又只能由自定义类加载器去加载。
深入理解JVM(一,JVM初识类的加载和类加载器)_第19张图片
上面的测试需要注意的是:(一定要找到对应的Class文件,剪切到自己所建的路径下,保证原路径没有对应的class文件,自己所在的路径下存在)
深入理解JVM(一,JVM初识类的加载和类加载器)_第20张图片

类加载器的双亲委托模型的好处:
① 可以确保java核心库的类型安全:所有的java应用都至少回引用java.lang.Object类,也就是说在运行期,java.lang.Object这个类会被加载java的JVM中;如果这个加载过程是由java应用自己的类加载器所加载的,那么久很可能会在JVM中存在多个版本的java.lang.Object类,而这些类之间还是不兼容的,相互不可见的(正是命名空间的作用)。借助于双亲委托机制,java核心库中类的加载工作都是由启动类加载器来统一完成的,从而确保了java应用所使用的都是同一个把呢不能的java核心库,他们之间是相互兼容的。
② 可以确保就java核心库所提供给的类不会被自定义的类所替代。
③ 不同的类加载器可以为相同的名称的类创建额外的命名空间,相同名称的类可以并存咋java虚拟机中,只需要用不同的类加载器来加载即可,不同的类加载器所加载的类之间是不兼容的,这就相当于在java虚拟机内部创建了一个有一个 相互隔离的java类空间,这类技术在实际中很多方面都得以应用。

在运行期,一个java类是由该的完全限定名(binary name,二进制名)和用于加载该类的定义类加载器(defining loader)所 共同决定的。如果名字相同(即相同的完全限定名)的类是由两个不同的加载器所加载,那么这些类就是不同的,即便.class文件的字节码完全相同,并且加载位置相同。

扩展类加载器和启动类加载器深入分析
1,扩展类加载器:在实际项目中,很多情况,我们是把.java对应的.class打成jar包去执行的。这里对这些打成jar包的访问就是利用了扩展类加载器。那么应用类加载器久不再去加载。
2,启动类加载器:不是纯java语言的,是一切的开始。
建议去看package sun.misc下的Launcher源码

import sun.misc.Launcher; // 在这个这下你可以i清楚看到加载的过程。


public class MyTest1 {

public static void main(String[] args) {
    System.out.println(System.getProperty("sun.boot.class.path")); // 启动类加载器所加载的路径
    System.out.println(System.getProperty("java.ext.dirs")); // 扩展类加载器加载的路径
    System.out.println(System.getProperty("java.class.path")); // 系统类加载器加载的路径
    // Launcher
}

}
运行结果:
在这里插入图片描述

你可能感兴趣的:(JVM)