jvm课程学习一

课堂PPT

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述spi的原因:

实现由spi内置于jdk核心包,由启动类加载器进行加载,但是实现类却由应用类加载器加载,在程序的运行中通过thread.currentThead.getContextClassloader 改变类的加载器

违背了双亲模型 因为双亲模型中子类由某个类加载,则父类也由它加载
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

类加载

  • 在java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的
  • 提供了更大的灵活性,增加了更多的可能性
    1)此处类型代表class对象
    2)在runtime期间完成加载、连接和初始化
    3)加载:class文件加载到内存中

类加载器深入剖析

  • java虚拟机与程序的生命周期
  • 在如下几种情况下,java虚拟机将结束生命周期
    1)执行了system.exit()方法
    2)程序正常执行结束
    3)程序在执行的过程中遇到了异常或错误而异常终止(抛异常不处理抛到main方法导致jvm退出)
    4)由于操作系统出现错误而导致java虚拟机进程终止

类的加载、连接和初始化

  • 加载:查找并加载类的二进制数据到内存

  • 连接:
    1)验证:确保被加载的类符合jvm的规范
    2)准备:为类的静态变量分配内存,并将其初始化为默认值(int置为0,Boolean置为false)
    3)解析:将类中的符号引用转化为直接引用

  • 初始化:为类的静态变量赋予正确的初始化值

public class Test {
    /*
    在准备阶段a为默认值0,
    在初始化阶段初始化为1
    * */    
    public static int a=1;
    
}

符号引用:字符串,能根据这个字符串定位到制定数据,比如java/lang/StringBuilder
直接引用:指针
方法区:jvm中定义的一个概念,用于存储类信息,常量池,静态变量等信息。永久代是hotspot中的方法区的实现。

类的使用与卸载

在这里插入图片描述

public class Test {
    /*
    初始化后仍旧为0
    * */
    public static int a;

}

类的加载、连接与初始化

  • java程序对类的使用方式分为两种
    1)主动使用
    2)被动使用

  • 所有的java虚拟机实现必须在每个类或者接口被java程序“首次主动使用”时才初始化它们
    1)首次
    2)主动使用

主动使用(七种)

  • 创建类的实例(new对象)
  • 访问某个类或接口的静态变量,或者对该静态变量赋值(拥有静态变量的类,比如静态变量在父类声明,但是使用子类去访问,不会触发子类的初始化,只会触发父类的初始化)
  • 调用类的静态方法(拥有静态方法的类,同上)
  • 反射(如Class.forName(“com.test.Test”))
  • 初始化一个类的子类,要是父类没有初始化此时会触发父类的初始化
  • java虚拟机启动时被标明启动类的类(java Test)
  • JDK1.7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化则初始化

除了以上七种情况,其他使用java类的方式被看做是对类的被动使用,都不会导致类的初始化

虽然不初始化但是可能会加载

类的加载

  • 类的加载是指将类的.class文件中的二进制数据读入内存中,将其放在运行时数据区的方法区内,然后再内存中创建一个java.lang.Class对象(规范并未说明Class对象放在哪里,hotspot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构

  • 加载.class文件的方式
    1)从本地文件系统中直接加载
    2)通过网路下载.class文件
    3)从zip,jar等归档文件中加载.class文件
    4)从专用数据库中提取.class文件
    5)将java源文件动态编译为class文件(动态代理)

public class Test1 {

    /*
    *  System.out.println(MyChild.str1);只会触发父类的初始化,子类不会初始化
    * System.out.println(MyChild.str);父类没有初始化会触发父类的初始化
    * -XX:+TraceClassLoading
    * System.out.println(MyChild.str1);可以触发子类的加载
    * -XX:+<option> :开启option选项
    * -XX:-<option> :关闭option选项
    * -XX:<option>=<value> :给option赋值value
    *
    * */
    public static void main(String[] args) {
//        System.out.println(MyChild.str);
        System.out.println(MyChild.str1);
    }

}

class MyChild extends MyParent{
    public static String str = "hello";
    static {
        System.out.println("MyChild");
    }
}

class MyParent{
    public static String str1 = "parent";
    static {
        System.out.println("MyParent");
    }
}
public class MyTest2 {

    /*
    *  public static final String str="hello";常量在编译器会被保存在调用这个常量的类的(MyTest2)常量池中,此时可以将
    * 本质上,调用并不会调用直接引用的定义常量的类,所以不会触发初始化。
    * 之后MyParent2和MyTest2没有任何关系,删除编译后的MyParent2.class文件,程序仍旧可以正常运行
    * ldc表示将int,float或者string类型的常量值从常量池中取出推送至栈顶
    * bipush表示将单字节(-128~127)内的常量值推送至栈顶
    * sipush表示将短整型常量值(-32768~32767)
    * iconst_1表示将int类型的1推送至栈顶(iconst_1~iconst_5)
    * 这些助记符可以在jdk中找到相应的定义
    * */
    public static void main(String[] args) {
        System.out.println(MyParent2.str);
    }
}

class MyParent2 {

    public static final String str="hello";

    static {
        System.out.println("MyParent2");
    }
}
javap jdk自带的反编译工具
G:\code\jvm\out\production\jvm\com\jvm\study>javap -c MyTest2.class
Compiled from "MyTest2.java"
public class com.jvm.study.MyTest2 {
  public com.jvm.study.MyTest2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #4                  // String hello
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

源码如下:
public class MyTest2 {

    /*
    *  public static final String str="hello";常量在编译器会被保存在调用这个常量的类的(MyTest2)常量池中,此时可以将
    * 本质上,调用并不会调用直接引用的定义常量的类,所以不会触发初始化。
    * 之后MyParent2和MyTest2没有任何关系,删除编译后的MyParent2.class文件,程序仍旧可以正常运行
    * */
    public static void main(String[] args) {
        System.out.println(MyParent2.str);
    }
}

public class MyTest3 {

    /*
    * 当一个常量的值并非在编译期间可以确定的,那么其值就不会被找到并放到常量池中
    * 这是在程序运行时,会导致主动使用这个常量所在的类,显然会导致这类的初始化
    * */
    public static void main(String[] args) {
        System.out.println(MyParent3.str);
    }

}

class MyParent3{
    static final String str= UUID.randomUUID().toString();
    static {
        System.out.println("MyParent3");
    }
}

public class MyTest4 {

    /*
    对于数组来书,其类型实jvm在运行期间动态生成的,表示为class [Lcom.jvm.study.MyParent4
    这种形式,动态生成的类型,其夫类型就是object

    对于数组来说,javaDoc经常将构成数组的元素为component,实际上就是将数组降低一个维度后的类型

    此时不会触发MyParent4的初始化

    助记符:
    anewarray:表示创建一个引用类型的(如类,接口,数组)数组,并将其压入栈顶
    newarray:表示创建一个原始的(如int,char,short,boolean,byte等)数组,并将其压入栈顶
     */

    public static void main(String[] args) {
//        MyParent4 myParent4 = new MyParent4();
        MyParent4[] myParent4s = new MyParent4[1];
        System.out.println(myParent4s.getClass());

        MyParent4[][] myParent4s1 = new MyParent4[1][1];
        System.out.println(myParent4s1.getClass());
        System.out.println(myParent4s.getClass().getSuperclass());
        System.out.println(myParent4s1.getClass().getSuperclass());

        int[] ints = new int[0];
        System.out.println(ints.getClass());
        System.out.println(ints.getClass().getSuperclass());

        char[] chars = new char[0];
        System.out.println(chars.getClass());
        System.out.println(chars.getClass().getSuperclass());

        short[] shorts = new short[0];
        System.out.println(shorts.getClass());
        System.out.println(shorts.getClass().getSuperclass());

        byte[] bytes = new byte[0];
        System.out.println(bytes.getClass());
        System.out.println(bytes.getClass().getSuperclass());
    }

}
class MyParent4{

    static {
        System.out.println("MyParent4");
    }
}

此时并不会触发数组类对象的加载

[Loaded com.jvm.study.MyTest4 from file:/G:/code/jvm/out/production/jvm/]
[Loaded sun.launcher.LauncherHelper$FXHelper from C:\Program Files\Java\jdk1.8.0_202\jre\lib\rt.jar]
[Loaded java.lang.Class$MethodArray from C:\Program Files\Java\jdk1.8.0_202\jre\lib\rt.jar]
[Loaded java.lang.Void from C:\Program Files\Java\jdk1.8.0_202\jre\lib\rt.jar]
[Loaded com.jvm.study.MyParent4 from file:/G:/code/jvm/out/production/jvm/]
class [Lcom.jvm.study.MyParent4;
class [[Lcom.jvm.study.MyParent4;
class java.lang.Object
class java.lang.Object
class [I
class java.lang.Object
class [C
class java.lang.Object
class [S
class java.lang.Object
class [B
class java.lang.Object
public class MyTest5 implements MyParent5{

    /*
    * 当一个接口在初始化时,并不要求父类也初始化
    * 是由在真正使用父接口时才会初始化
    *
    * 接口中的变量默认添加final
    *
    * */

    static String str = "hello";

    public static void main(String[] args) {
//        System.out.println(MyParent5.parent);
//        System.out.println(MyTest5.str);
        System.out.println(MyChild5.uuid);
//        System.out.println(MyChild5.string);
//        System.out.println(MyChild5.child);
    }

}
interface  MyParent5{
    public static Thread parent=new Thread(){
        {
            System.out.println("MyParent5");
        }
    };
}
interface MyChild5 extends MyParent5{
    String string = "hello child";
    String uuid = UUID.randomUUID().toString();
    public static Thread child=new Thread(){
        {
            System.out.println("MyChild5");
        }
    };
}

public class MyTest7 {

    /*
    * string 为rt包下,有bootstrapclassload进行加载,所以为空
    * C在classpath路径下,有系统类加载器(应用加载器)
    * */
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(C.class.getClassLoader());
    }

}
class C{

}

//输出
null
sun.misc.Launcher$AppClassLoader@18b4aac2

命名空间

  • 每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类构成
  • 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
  • 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
  • 同一命名空间内的类是相互可见的,非同一命名空间内的类是不可见的
  • 子加载器可以见到父加载器加载的类,父加载器也不能见到子加载器加载的类

类的卸载

  • 当一个类被加载、连接和初始化之后,它的生命周期就开始了。当此类的class对象不再被引用,即不可触及时,class对象就会结束生命周期,类在方法区内的数据也会被卸载
  • 一个类何时结束生命周期,取决于代表它的class对象何时结束生命周期
  • 由java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期汇总,始终不会被卸载。java虚拟机本身会始终引用这些加载器,而这些类加载器则会始终引用他们所加载的类的class对象,因此这些class对象是可触及的
  • 有用户自定义的类加载器所加载的类可以被卸载的。(jvisualvm查看当前java进程 -XX:+TraceClassUnloading用于追踪类的卸载情况)

类的加载

  • jvm规范允许类加载器在预料某个类将要被使用是就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载必须在程序首次主动使用该类的才报告错误(LinkageError错误)
  • 如果这个类一直没有被程序主动使用,那么该类加载器就不会报告错误

在这里插入图片描述

public class MyTest20 {
	
    public static void main(String[] args) throws Exception {
        CustomerClassLoader customerClassLoader1 = new CustomerClassLoader("classloader");
        CustomerClassLoader customerClassLoader2 = new CustomerClassLoader("classloader");
        customerClassLoader1.setPath("C:\\Users\\xingkong\\Desktop\\jvm");
        customerClassLoader2.setPath("C:\\Users\\xingkong\\Desktop\\jvm");
        Class<?> class1 = customerClassLoader1.loadClass("com.jvm.study.MyPerson");
        Class<?> class2 = customerClassLoader2.loadClass("com.jvm.study.MyPerson");
        System.out.println(class1 == class2);

        Object objec1=class1.newInstance();
        Object objec2=class2.newInstance();
        Method methods = class1.getMethod("setMyPerson", Object.class);
        methods.invoke(objec1, objec2);
    }

}

结果输出为:

false
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.jvm.study.MyTest20.main(MyTest20.java:26)
Caused by: java.lang.ClassCastException: com.jvm.study.MyPerson cannot be cast to com.jvm.study.MyPerson
	at com.jvm.study.MyPerson.setMyPerson(MyPerson.java:20)
	... 5 more

不同的类加载器的命名空间关系

  • 同一个命名空间内的类是相互可见的,子加载器的命名空间包含所有的父加载器的命名空间,因此由子加载器加载的类能看见父加载器加载的类。例如系统类加载加载的类能看见根类加载器加载的类。由父加载器加载的类不能看见子加载器加载的类。如果两个加载器之间没有直接或者间接的父子关系,那么它们各自加载的类相互不可见。
  • 可见不代表可以访问,可访问通过修饰符进行控制

类加载器的双亲委托模型的好处

  • 可以确保java核心库的类型安全:所有的java应用都是至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object这个类会被加载到java虚拟机中,如果这个加载过程是否java应用自己的类加载器所完成的,那么很可能就会在jvm中存在多个版本的java核心类,二期这些类之间换是不兼容的,相互不可见的(正式命名空间在发挥作用)。借助于双亲委托机制,java核心类库中的类的加载工作都是由启动类加载来完成,从而确保了java应用所使用的都是同一个版本的java核心类库,他们之间是兼容的。
  • 可以确保java核心类库所提供的的类不会被自定义的类所代替
  • 不同的类加载器可以为相同的名称(binary name)的类创建额外的命名空间,相同名称的类可以并存在java虚拟机中,只需要用不同的类加载来加载即可,不同类加载器所加载的类之间是不兼容的,这就相当于在java虚拟机内部创建了一个又一个相互隔离的java类空间,这类技术在很多框架汇总都得到了实际应用。
    在这里插入图片描述
  • 内建于jvm中的启动类加载器会加载java.lang.ClassLoader以及其他的java平台类。当jvm启动时,一块特殊的机器码会执行,它会加载扩展类加载器和系统类加载去。这块特殊的机器码叫做启动类加载器(Bootstrap)。启动类加载器并不是java,而其他的加载器都是java类
    。启动类加载是特定于平台的机器指令,它负责开启整个加载过程。所有的类加载(处理启动类加载器)都被实现为java类,不过,总归要有一个组件来加载一个java类加载器。从而让整个加载过程能够顺利运行下去,加载第一个纯java类加载是就是启动类加载的职责。 启动类加载器换负责加载供jre正常运行所需的基本组件,这包括java.util和java.lang包中的类
//	SPI
        Class.forName("com.mysql.jdbc.Drive");
        Connection connection = Driver.getConnection();
        Statement statement = connection.getStatement();
当前类加载器(current class loader)
 每个类都会使用自己的类加载器(即加载自身的类加载)来气加载其他类(指的是所依赖的类),  
 线程上下文类加载(context classloader)    
 线程上下文类加载是从jdk1.2开始引入,类thread中的getContextClassLoader()与
 setContextClassloader()分别用来获取和设置上下文类加载器    
 如果没有通过setContextClassLoader(classLoader c1)运行设置的话,线程将继承其类线程的上下文类加载器,
 java应用运行时的和初始线程上下文类加载器是系统类加载器,在线程中运行的代码可以通过该类加载器来加载类与资源

 线程上下文类加载的重要性:
 SPI(services provider  Interface)

 父classloader可以使用当前的线程thread.currentThread(),getContextClassLoader()所指定的classLoader
 加载的类,这就改变了父classLoader不能使用子classloader或是其他没有直接父子类关系的classloader加载的
 类的情况,即改变了双亲委托模型

 线程上下文类加载器就是当前线程的current  classloader,

 在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托上进行加载,但是对于spi来说,有些接口
 是java核心类库所提供的,而java核心类库是由启动类加载器来加载的,而这些就接口的实现却来自与不同的jar包,
(厂商提供),java的启动类加载是不回家在其他来源的jar包,这样传统的双亲委托模型就无法满足spi的需求,
 而通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载来实现对于接口类型类的加载。
  •    线程上下文类加载器的一般使用模式(获取-使用-还原)
    classLoader c1=Thread.currentThread().getContextClassLoader();
     try{
         Thread.currentThread().setContextClassLoader(targetTest1);
     }finally{
         Thread.currentThread().setContextClassLoader(targetTest1);
     }
    
     myMethod里面则调用了Thread.currentThread().getContextClassLoader(),
     获取当前线程的的上下文类加载器做某些事情,如果一个类由类加载器A加载,那么
     这个类的依赖类也是由相同的类加载加载的(如果该依赖之前没有加载过)
     
     contextClassLoader的作用就是为了破坏java的类加载器委托机制
     
     当高层提供了统一的接口让底层去实现,同时又要在高层加载(或实例化)
     底层的类时,就必须通过线程上下文类加载器来帮助高层classloader找到
     并加载该类。
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为什么要学JVM1、一切JAVA代码都运行在JVM之上,只有深入理解虚拟机才能写出更强大的代码,解决更深层次的问题。2、JVM是迈向高级工程师、架构师的必备技能,也是高薪、高职位的不二选择。3、同时,JVM又是各大软件公司笔试、面试的重中之重,据统计,头部的30家互利网公司,均将JVM作为笔试面试的内容之一。4、JVM内容庞大、并且复杂难学,通过视频学习是最快速的学习手段。课程介绍本课程包含11个大章节,总计102课时,无论是笔试、面试,还是日常工作,可以让您游刃有余。第1章 基础入门,从JVM是什么开始讲起,理解JDK、JRE、JVM的关系,java的编译流程和执行流程,让您轻松入门。第2章 字节码文件,深入剖析字节码文件的全部组成结构,以及javap和jbe可视化反解析工具的使用。第3章 类的加载、解释、编译,本章节带你深入理解类加载器的分类、范围、双亲委托策略,自己手写类加载器,理解字节码解释器、即时编译器、混合模式、热点代码检测、分层编译等核心知识。第4章 内存模型,本章节涵盖JVM内存模型的全部内容,程序计数器、虚拟机栈、本地方法栈、方法区、永久代、元空间等全部内容。第5章 对象模型,本章节带你深入理解对象的创建过程、内存分配的方法、让你不再稀里糊涂。第6章 GC基础,本章节是垃圾回收的入门章节,带你了解GC回收的标准是什么,什么是可达性分析、安全点、安全区,四种引用类型的使用和区别等等。第7章 GC算法与收集器,本章节是垃圾回收的重点,掌握各种垃圾回收算法,分代收集策略,7种垃圾回收器的原理和使用,垃圾回收器的组合及分代收集等。第8章 GC日志详解,各种垃圾回收器的日志都是不同的,怎么样读懂各种垃圾回收日志就是本章节的内容。第9章 性能监控与故障排除,本章节实战学习jcmd、jmx、jconsul、jvisualvm、JMC、jps、jstatd、jmap、jstack、jinfo、jprofile、jhat总计12种性能监控和故障排查工具的使用。第10章 阿里巴巴Arthas在线诊断工具,这是一个特别小惊喜,教您怎样使用当前最火热的arthas调优工具,在线诊断各种JVM问题。第11章 故障排除,本章会使用实际案例讲解单点故障、高并发和垃圾回收导致的CPU过高的问题,怎样排查和解决它们。课程资料课程附带配套项目源码2个159页高清PDF理论篇课件1份89页高清PDF实战篇课件1份Unsafe源码PDF课件1份class_stats字段说明PDF文件1份jcmd Thread.print解析说明文件1份JProfiler内存工具说明文件1份字节码可视化解析工具1份GC日志可视化工具1份命令行工具cmder 1份学习方法理论篇部分推荐每天学习2课时,可以在公交地铁上用手机进行学习。实战篇部分推荐对照视频,使用配套源码,一边练习一遍学习课程内容较多,不要一次性学太多,而是要循序渐进,坚持学习。      

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值