加载_加载器 - CSDN
精华内容
参与话题
  • 为什么编辑器加载不了图片?

    千次阅读 2014-09-18 19:52:28
    如果需要编辑器载入图片,那图片所在站点的根目录下也要放置crossdomain.xml,例如图片url是http://t3.baidu.com/it/u=3205204196,1723632348&fm=24&gp=0.jpg,那...一般情况下百度是不会让您抓取的,所以要加载的图片
    如果需要编辑器载入图片,那图片所在站点的根目录下也要放置crossdomain.xml,例如图片url是http://t3.baidu.com/it/u=3205204196,1723632348&fm=24&gp=0.jpg,那crossdomain.xml的url就应该是http://t3.baidu.com/crossdomain.xml
    一般情况下百度是不会让您抓取的,所以要加载的图片还是要放在自己服务器上。
    如果crossdomain.xml已经按要求放置了还是不能加载图片,应检查下是否是防盗链情况导致无法加载图片
    展开全文
  • Java中类的加载顺序介绍(ClassLoader)

    万次阅读 多人点赞 2018-06-02 10:05:37
    Java中类的加载顺序介绍(ClassLoader)1、ClassNotFoundExcetpion 我们在开发中,经常可以遇见java.lang.ClassNotFoundExcetpion这个异常,今天我就来总结一下这个问题。对于这个异常,它实质涉及到了java技术体系中...

    Java中类的加载顺序介绍(ClassLoader)


    1、ClassNotFoundExcetpion 
      我们在开发中,经常可以遇见java.lang.ClassNotFoundExcetpion这个异常,今天我就来总结一下这个问题。对于这个异常,它实质涉及到了java技术体系中的类加载。Java的类加载机制是技术体系中比较核心的部分,虽然它和我们直接打交道不多,但是对其背后的机理有一定理解有助于我们排查程序中出现的类加载失败等技术问题。 
    2、类的加载过程 
     一个java文件从被加载到被卸载这个生命过程,总共要经历5个阶段,JVM将类加载过程分为: (加链初使卸)
      加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载 
    (1)加载 
      首先通过一个类的全限定名来获取此类的二进制字节流;其次将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;最后在java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问入口。总的来说就是查找并加载类的二进制数据。 
    (2)链接: 
      验证:确保被加载类的正确性; 
      准备:为类的静态变量分配内存,并将其初始化为默认值; 
      解析:把类中的符号引用转换为直接引用; 
    (3)为类的静态变量赋予正确的初始值 
    3、类的初始化 
    (1)类什么时候才被初始化 
      1)创建类的实例,也就是new一个对象 
      2)访问某个类或接口的静态变量,或者对该静态变量赋值 
      3)调用类的静态方法 
      4)反射(Class.forName(“com.lyj.load”)) 
      5)初始化一个类的子类(会首先初始化子类的父类) 
      6)JVM启动时标明的启动类,即文件名和类名相同的那个类 
    (2)类的初始化顺序 
      1)如果这个类还没有被加载和链接,那先进行加载和链接 
      2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口) 
      3)加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。 

      4)总的来说,初始化顺序依次是:(静态变量、静态初始化块)–>(变量、初始化块)–> 构造器

    如果有父类,则顺序是:父类static方法 –> 子类static方法 –> 父类构造方法- -> 子类构造方法 

    4、类的加载 
      类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class对象,用来封装类在方法区类的对象。如: 
    这里写图片描述 
    这里写图片描述

      类的加载的最终产品是位于堆区中的Class对象。Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。加载类的方式有以下几种: 
      1)从本地系统直接加载 
      2)通过网络下载.class文件 
      3)从zip,jar等归档文件中加载.class文件 
      4)从专有数据库中提取.class文件 
      5)将Java源文件动态编译为.class文件(服务器) 
    5、加载器 
      JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述: 
       
    这里写图片描述

    (1)加载器介绍 
    1)BootstrapClassLoader(启动类加载器) 
      负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,加载System.getProperty(“sun.boot.class.path”)所指定的路径或jar。 
    2)ExtensionClassLoader(标准扩展类加载器) 
      负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包。载System.getProperty(“java.ext.dirs”)所指定的路径或jar。 
    3)AppClassLoader(系统类加载器) 
      负责记载classpath中指定的jar包及目录中class 
    4)CustomClassLoader(自定义加载器) 
      属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现。

    (2)类加载器的顺序 
    1)加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。 
    2)在加载类时,每个类加载器会将加载任务上交给其父,如果其父找不到,再由自己去加载。 

    3)Bootstrap Loader(启动类加载器)是最顶级的类加载器了,其父加载器为null。

    参考博文:https://blog.csdn.net/eff666/article/details/52203406

         例子补充:

    继承的加载顺序

    由于static块会在首次加载类的时候执行,因此下面的例子就是用static块来测试类的加载顺序。

    package xing.test.thinking.chap7;
    class A{
        static{
            System.out.println("A static");
        }
    }
    class B extends A{
        static{
            System.out.println("B static");
        }
    }
    class C extends B{
        private static D d = new D();
        static{
            System.out.println("C static");
        }
    }
    class D{
        static{
            System.out.println("D static");
        }
    }
    public class ExtendTest {
        public static void main(String[] args) {
            C c = new C();
        }
    }

    在上面的例子中,类C继承B,B继承A,而C有依赖于D。因此当创建C的时候,会自动加载C继承的B和依赖的D,然后B又会加载继承的A。只有A加载完,才能顺利的加载B;BD加载完,才能加载C。这就是类的加载顺序了。

    A static
    B static
    D static
    C static

    所有的变量初始化完,才会执行构造方法

    在类的加载过程中,只有内部的变量创建完,才会去执行这个类的构造方法

    package xing.test.thinking.chap7;
    class A2{
        B2 b2 = new B2();
        static{
            System.out.println("A static");
        }
        public A2() {
            System.out.println("A2()");
        }
    }
    class B2{
        C2 c2 = new C2();
        D2 d2 = new D2();
        static{
            System.out.println("B static");
        }
        public B2() {
            System.out.println("B2()");
        }
    }
    class C2{
        static{
            System.out.println("C static");
        }
        public C2() {
            System.out.println("C2()");
        }
    }
    class D2{
        static{
            System.out.println("D static");
        }
        public D2() {
            System.out.println("D2()");
        }
    }
    public class VarTest {
        public static void main(String[] args) {
            A2 a2 = new A2();
        }
    }

    在上面的例子中,A2里面有B2变量,B2则有C2D2变量。因此类的加载还是先读取到哪个,就执行相应的静态块。
    当依赖的对象都定义完,才会执行构造方法:

    A static
    B static
    C static
    C2()
    D static
    D2()
    B2()
    A2()

    静态成员与普通成员类的加载区别

    在类的加载过程中,静态成员类的对象,会优先加载;而普通成员类的对象则是使用的时候才回去加载。

    package xing.test.thinking.chap7;
    class A3{
        B3 b3 = new B3();
        static C3 c4 = new C3();
        static{
            System.out.println("A3");
        }
    }
    class B3{
        static{
            System.out.println("B3");
        }
    }
    class C3{
        static{
            System.out.println("C3");
        }
    }
    public class StaticTest {
        public static void main(String[] args) {
            A3 a3 = new A3();
        }
    }

    输出:

    C3
    A3
    B3

    总结

    第一点,所有的类都会优先加载基类
    第二点,静态成员的初始化优先
    第三点,成员初始化后,才会执行构造方法
    第四点,静态成员的初始化与静态块的执行,发生在类加载的时候。
    第四点,类对象的创建以及静态块的访问,都会触发类的加载。

    补充类构造方法的顺序

    看代码:

    package xing.test.thinking.chap8;
    class A{
        public A() {
            System.out.println("A");
        }
    }
    class B extends A{
        public B() {
            System.out.println("B");
        }
    }
    class C extends B {
        private D d1 = new D("d1");
        private D d2 = new D("d2");
        public C() {
            System.out.println("C");
        }
    }
    class D {
        public D(String str) {
            System.out.println("D "+str);
        }
    }
    public class ExtendTest {
        public static void main(String[] args) {
            C c = new C();
        }
    }
    

    执行结果:

    A
    B
    D d1
    D d2
    C
    

    因此可以得出结论:

    • 首先会调用基类的构造方法
    • 其次,调用成员的构造方法
    • 最后,调用自己的构造方法
    参考博文:https://www.cnblogs.com/xing901022/p/5507086.html
    展开全文
  • java编译和类加载详述

    千次阅读 2018-11-21 19:44:37
    然后Java虚拟机将编译好的字节码文件加载到内存(这个过程被称为类加载,是由加载器完成的),然后虚拟机针对加载到内存的java类进行解释执行,显示结果。   Java的运行原理 在Java中引入了虚拟机的概念,即在...

    Java程序运行时,必须经过编译和运行两个步骤。首先将后缀名为.java的源文件进行编译,最终生成后缀名为.class的字节码文件。然后Java虚拟机将编译好的字节码文件加载到内存(这个过程被称为类加载,是由加载器完成的),然后虚拟机针对加载到内存的java类进行解释执行,显示结果。

     

    Java的运行原理

    在Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(ByteCode),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。

     

    Java代码编译整个过程

    Java代码编译是由Java源码编译器来完成,流程图如下所示:

    javaç¨åºçæ§è¡è¿ç¨è¯¦è§£

    词法分析器:

    词法分析器一般以函数的形式存在,供语法分析器调用。

    这里的单词是一个字符串,是构成源代码的最小单位。从输入字符流中生成单词的过程叫作单词化(Tokenization),在这个过程中,词法分析器还会对单词进行分类。

    词法分析器通常不会关心单词之间的关系(属于语法分析的范畴),举例来说:词法分析器能够将括号识别为单词,但并不保证括号是否匹配。

    词法分析(lexical analysis)或扫描(scanning)是编译器的第一个步骤。词法分析器读入组成源程序的字符流,并且将它们组织成有意义的词素(lexeme)的序列,并对每个词素产生词法单元(token)作为输出。

    简单的来说,词法分析就是将源程序(可以认为是一个很长的字符串)读进来,并且“切”成小段(每一段就是一个词法单元 token),每个单元都是有具体的意义的,例如表示某个特定的关键词,或者代表一个数字。而这个词法单元在源程序中对应的文本,就叫做“词素”。词法分析注重的是每个单词是否合法,以及这个单词属于语言中的哪些部分

    token流:词法分析器的结果,就是把程序的语句进行分词得到的的一个个“单词”!

    语法分析器:

    是对token流进行语法检查、并构建由输入的单词组成的数据结构(语法树/抽象语法树)。语法分析器通常使用一个独立的词法分析器从输入字符流中分离出一个个的“单词”;语法分析的上下文无关文法注重的是一个一个的推导式,是将词法分析中得到的单词按照语法规则进行组合 

    语法树/抽象语法树:

    源代码的抽象语法结构的树状表现形式,这里特指java的源代码。树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于if(;;){ //当符合条件执行的任务}或者 while(true){//当符合条件执行的任务}这样的条件跳转语句,可以使用带有两个分支的节点来表示。

    语义分析器:语义分析就是要了解各个推导式之间的关系是否合法,主要体现在推导式中使用的终结符和非终结符之间的关系,也就是它们的类型。

    注解抽象语法树:经过 语义分析器将语法树/抽象语法树转化为注解抽象语法树

     

    字节码生成:

    目的:将注解语法树转化成字节码,并将字节码写入*.class文件。

    流程:

    • 将java的代码块转化为符合JVM语法的命令形式,这就是字节码
    • 按照JVM的文件组织格式将字节码输出到*.class文件中

     

    类加载详解:

    在Java 中分为主动引用和被动引用 主动引用都会触发类的加载!比如:访问这个类的静态变量,方法,和 通过new ,jvm标记加载的类(存在main方法的类),反射等,父类在子类加载的时候也会被加载

    被动引用:比如访问静态常量或者创建数组内部对象

    类加载主要是由jvm虚拟机负责的,过程非常复杂,类加载分三步  加载   》  连接  》初始化

    加载

    程序运行之前jvm会把编译完成的.class二进制文件加载到内存,供程序使用,用到的就是类加载器classLoader ,java程序的运行并不是直接依   靠底层的操作系统,而是基于jvm虚拟机。

    类加载器:负责读取字节码,并转换成java.Long.Class类的一个对象存在于方法区

    加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始

    注意:要判断两个类是否“相同”,前提是这两个类必须被同一个类加载器加载,否则这个两个类不“相同”。
    这里指的“相同”,包括类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法、instanceof关键字等判断出来的结果。

    java中加载器的种类大致可以分为四种:

    1.Bootstrap ClassLoader(由C++语言写成)顶级加载器,%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类。

    2.系统加载器(也就是内部类AppClassLoader)   加载classpath下的路径下的class

    3.ExtClassLoader,加载jre\lib\ext\classes文件下的class

    4.java.net.UrlClassLoader. 加载指定的url下的class


    当我们运行一个程序时,首先是找到JDK安装目下的jvm.dll来启动JAVA虚拟机。

    而后Bootstrap ClassLoader产生。

    接下来就是Bootstrap ClassLoader来加载ExtClassLoader,并且指定ExtClassLoader的父加载器为Bootstrap ClassLoader,但是因为Bootstrap ClassLoader用C++语言写的,所以用JAVA的观点来看,这个加载器的实例是不存在的所以ExtClassLoader的父加载器被设置为了null。

    然后就是Bootstrap ClassLoader将AppClassLoader装载,并指定其父加载器为ExtClassLoader。

    双亲委派模型:是通过组合实现的!并不是继承关系!


    JAVA是按照加载器的委派模型来实现的。这种模型是JAVA安全性机制的保证。

    如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都不愿意干活,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的双亲委派模式。

     采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,可以防止核心API库被随意篡改

     

     

    连接

           连接是很重要的一步,过程比较复杂,分为三步  验证  》准备  》解析    

      验证:确保类加载的正确性。一般情况由javac编译的class文件是不会有问题的,但是可能有人的class文件是自己通过其他方式编译出来的,这就很有可能不符合jvm的编译规则,这一步就是要过滤掉这部分不合法文件 

      准备:为类的静态变量分配内存,将其初始化为默认值 。我们都知道静态变量是可以不用我们手动赋值的,它自然会有一个初始值 比如int 类型的初始值就是0 ;boolean类型初始值为false,引用类型的初始值为null 。 这里注意,只是为静态变量分配内存,此时是没有对象实例的 

      解析:将class 内的 符号引用,加载到 运行时常量池 内转化成为 直接引用 的过程解释一下符号引用和直接引用。比如在方法A中使用方法B,A(){B();},这里的B()就是符号引用,它对于方法的调用没有太多的实际意义。但是B方法实际调用时是通过一个指针指向B方法的内存地址,这个指针才是真正负责方法调用,他就是直接引用。

    初始化

    为类的静态变量赋予正确的初始值,上述的准备阶段为静态变量赋予的是虚拟机默认的初始值,此处赋予的才是程序编写者为变量分配的真正的初始值

    类加载完成!!!

    备注:

    当 JVM 遇到 new 指令时,首先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载过,如果没有就先执行类加载。如果类已经被加载过,则会为新生对象分配内存(所需内存大小在类加载后就可以确定),分配对象内存采取的方式是“指针碰撞”或“空闲列表”,前者是在内存比较规整的情况下,后者是在空闲内存和已使用内存相互交错的情况下,而内存是否规整这又取决于垃圾回收器。

    问:我们通过 Java 栈中对象的引用去访问这个对象,访问对象的主流方式有 那些

    答 :使用句柄和直接指针。

    使用句柄访问:在 Java 堆中会划分出一块内存作为句柄池,引用中储存的内容就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。

    直接指针访问:在对象的内存布局中就要放置访问类型数据的指针。

    这两种方式各有优势,使用句柄的好处是引用中存储的是稳定的句柄,对象被移动时(垃圾回收时对象被移动)只需改变句柄中的实例数据的指针,不需要改动引用本身。而使用直接指针的好处是速度更快,它节省了一次指针定位的开销。HotSpot 使用的是第二种方式进行对象的访问。

    问:怎解决内存分配的线程安全问题?

    答:jvm提供了2种解决内存分配的线程安全问题,1.使用cas无锁机制失败重试来保证操作的原子性 2.jvm 会给每条线程分配本地线程分配缓冲TLAB 可以通过配置来决定分配的大小,只有当分配内存用完后才会去锁进行同步操作

     

     

     

     

     

     

     

     

     

     

    展开全文
  • JVM--详解类加载机制

    千次阅读 2018-07-14 19:30:58
    JVM--详解类加载机制转载:https://blog.csdn.net/championhengyi/article/details/78680700Java虚拟机的体系结构前面我们探讨了Class文件的结构,如果你还没有学习,将不利于这部分知识的吸收与掌握,所以请移步:...

    JVM--详解类加载机制

    转载:https://blog.csdn.net/championhengyi/article/details/78680700

    Java虚拟机的体系结构

    前面我们探讨了Class文件的结构,如果你还没有学习,将不利于这部分知识的吸收与掌握,所以请移步:JVM–Class类文件结构(一)

    学习一个东西之前,我们务必要知道,这东西大概是干什么的,有什么作用。

    为了更清楚的阐释类加载机制到底是干什么的,我先将JVM的结构图贴给大家:

    这里写图片描述

    如上图,我们要学的类加载机制就是要搞清楚类加载器是如何找到指定的Class文件以及怎样将Class文件装载进内存,以便执行引擎执行Class文件中存在的数据和指令,从而使你的Java程序跑起来。

    上面的黑体字就是这玩意大概是干啥的,至于学习它有什么作用,有助于你了解Java源代码是怎么从一个普通的文件变成一个可以正在运行的程序这其中的过程。而且,学习了这部分知识,你再回过头看反射机制,会有一种醍醐灌顶的感觉。


    类的生命周期

    先来看一下类的生命周期吧:

    这里写图片描述

    结合上图,类加载机制主要学习加载、验证、准备、解析、初识化这些过程,然后就是需要了解真正可以将类加载进内存的一个玩意(还是代码实现)—类加载器!

    其实,有了前面Class文件结构的基础,这些东西都很简单,不要怕~

    额外补充

    上图中解析和初始化的位置是可以互换的,如果解析一旦在初始化之后开始,这就是我们经常所说的“动态绑定”~~

    除此之外,这些阶段通常都是互相交叉的混合式进行,各个阶段只保证按部就班的开始,并不保证按部就班的进行或完成。


    类加载的过程

    我们根据上面所说的类的生命周期来一点点剖析类的加载过程。

    加载

    我们首先要明白一件事情:什么开始进行类加载过程的第一阶段:加载?Java虚拟机没有进行强制约束,交由虚拟机的具体实现自由把握。

    看完上面的话,我们来看在加载阶段,虚拟机需要完成哪些事情:

    • 通过一个类的全限定名来获取定义此类的二进制字节流
    • 将获取到的二进制字节流转化成一种数据结构并放进方法区
    • 在内存中生成一个代表此类的java.lang.Class对象,作为访问方法区中各种数据的接口

    我们需要注意一些事情:

    对于方法区的认识:被加载的类的信息存储在方法区中,可以被线程所共享,也就是说,加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在了方法区之中。然后你能想起来那个二进制流中都存储了哪些信息吗?

    对于Class对象认识:Class对象虽然是在内存中,但并未明确规定是在Java堆中,对于HotSpot来说,Class对象存储在方法区中。它作为程序访问方法区中二进制字节流中所存储各种数据的接口。你能大概想到反射机制中的Class对象是怎么一回事了吗?为什么可以在运行期通过反射机制得到那么多的类信息你能猜测到吗?


    验证

    从上面类的生命周期一图中我们可以看出,验证是连接的第一步,这一阶段的目的主要是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,从而不会危害虚拟机自身安全。也就是说,当加载阶段将字节流加载进方法区之后,JVM需要做的第一件事就是对字节流进行安全校验,以保证格式正确,使自己之后能正确的解析到数据并保证这些数据不会对自身造成危害。

    验证阶段主要分成四个子阶段:

    • 文件格式验证
    • 元数据验证
    • 字节码验证
    • 符号引用验证

    我不在这里详细的说明每一阶段的校验主要干了什么事情,有兴趣的同学可以自行百度。

    挑点重点来说吧,对字节流进行校验是由一个叫做Class文件检验器的东西所完成,其实还是代码实现。

    而什么叫做元数据呢?

    所谓的元数据是指用来描述数据的数据,更通俗一点就是描述代码间关系,或者代码与其它资源(例如数据库表)之间内在联系的数据,你也可以更简单的认为成框架中的各种@注解,因为这些@注解很简介的描述了大量有关各个类、方法、字段额外的信息或之间的联系。

    元数据验证也就是验证这些额外的信息或它们之间的联系是否正确。

    我们还得注意字节码验证,在字节码验证中涉及到了一个概念:字节码流。

    字节码流 = 操作码 + 操作数。

    操作码就是伪指令,操作数就是普通的Java数据,如int,float等等。

    所以对字节码验证的过程就是对字节码流验证的过程,也就是验证操作码是否合法,操作数是否合法。

    而符号引用验证涉及到常量池解析的知识,在下文中我们顺带着将符号引用验证带过就行,现在先不说。


    准备

    准备阶段你只要掌握两个知识点:

    1.准备阶段的目的:正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存将在方法区中分配。

    注意我的重点:是类变量(static)不是实例变量,还有,我们又知道了在JVM的方法区中不仅存储着Class字节流(按照运行时方法区的数据结构进行存储,上述的二进制字节流是不严谨的说法,只是为了大家好理解),还有我们的类变量。

    2.这里的类变量初始值通常是指数据类型的零值。比如int的零值为0,long为0L,boolean为false… …真正的初始化赋值是在初始化阶段进行的。

    额外一点,如果你设置的类变量还具有final字段,如下:

    public static final int value = 123;

    那么在准备阶段变量的初始值就会被直接初始化为123,具体原因是由于拥有final字段的变量在它的字段属性表中会出现ConstantValue属性。


    解析

    这一阶段我个人觉得不太好理解并且非常重要,但我还是会一点点剖析难点,保证你能听懂,所以开始吧~~

    先来看一下解析阶段的目的:虚拟机将常量池内的符号引用替换为直接引用。

    然后说一下解析阶段最大的特点:发生时间不可预料,有可能和初始化阶段互相交换位置。至于原因,我们等下再说。

    先来说看完解析阶段的目的吧,你有可能有三个疑问。哪个常量池?什么符号引用?什么直接引用?Ok,搞清这三个问题,解析这部分你也就学会了。

    首先来说常量池:在Class的文件结构中我们就花了大量的篇幅去介绍了常量池,我们再来总结一下:常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。

    然后这段话中的常量池指的就是存在于.class文件中的常量池,结果在运行期被JVM装载,并且可以扩充的存在于方法区中的运行时常量池

    然后来看符号引用:在Class文件中我们也讲述了什么是符号引用。总的来说就是常量池中存储的那些描述类、方法、接口的字面量,你可以简单的理解为就是那些所需要信息的全限定名,目的就是为了虚拟机在使用的时候可以定位到所需要的目标。

    最后来看直接引用:直接指向目标的指针、相对偏移量或能间接定位到目标的句柄。

    现在我们对上面那句话进行重新解读:虚拟机将运行时常量池中那些仅代表其他信息的符号引用解析为直接指向所需信息所在地址的指针。

    大概就是这样,我觉得你应该已经完全明白了。

    解决一个遗留的问题:还记得刚才没有说到的符号引用吗?

    这一阶段就是发生在JVM将符号引用转换为直接引用的时候,它的作用就是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验,以确保解析动作能够正常执行!

    在解析阶段主要有以下不同的动作,我只给大家罗列出来,不细讲,有兴趣的同学可以自行百度:

    • 类或接口的解析(注意数组类和非数组类)
    • 字段(简单名称+字段描述符)解析(注意递归搜索)
    • 类方法解析(注意递归搜索)
    • 接口方法解析(注意递归搜索)

    在解析阶段还有一个很有意思的东西:动态连接

    它也是上面解析阶段发生时间不确定的直接原因:大部分JVM的实现都是延迟加载或者叫做动态连接。它的意思就是JVM装载某个类A时,如果类A中有引用其他类B,虚拟机并不会将这个类B也同时装载进JVM内存,而是等到执行的时候才去装载。

    而这个被引用的B类在引用它的类A中的表现形式主要被登记在了符号表中,而解析的过程就是当需要用到被引用类B的时候,将引用类B在引用类A的符号引用名改为内存里的直接引用。这就是解析发生时间不可预料的原因,而且这个阶段是发生在方法区中的。


    初始化

    虚拟机规范定义了5种情况,会触发类的初始化阶段,也正是这个阶段,JVM才真正开始执行类中定义的Java程序代码:

    • new一个对象、读取一个类静态字段、调用一个类的静态方法的时候
    • 对类进行反射调用的时候
    • 初始化一个类,发现父类还没有初始化,则先初始化父类
    • main方法开始执行时所在的类
    • 最后一种情况我也不懂,就不贴了

    额外补充:

    有三种引用类的方式不会触发初始化(也就是类的加载),为以下三种:

    • 通过子类引用父类的静态字段,不会导致子类初始化
    • 通过数组定义来引用类,不会触发此类的初始化
    • 引用另一个类中的常量不会触发另一个类的初始化,原因在于“常量传播优化

    来说一说常量传播优化吧(先看一份代码):

    public class ConstClass {
        static {
            System.out.println("ConstClass init!");
        }
    
        public static final String HELLOWORLD = "hello world";
    }
    
    public class NotInitialization {
    
        public static void main(String[] args) {
            System.out.println(ConstClass.HELLOWORLD);
        }
    }

    这种调用方式不会触发ConstClass的初始化,因为常量传播优化,常量“hello world”已经被存储到了NotInitialization类的常量池中,以后NotInitialization对常量ConstClass.HELLOWORLD的引用实际上都被转化为NotInitialization对自身常量池的引用。

    然后在初识化阶段我们重点掌握的知识就是类构造器<clinit>()了。

    这个东西我也只是提几点重要的: 
    1.<clinit>()是编译器自动收集类中的所有类变量的赋值动作和静态语句块合并产生的。 
    2.父类中定义的静态语句块要优先于子类的变量赋值操作。 
    3.虚拟机保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步。


    方法区使用实例

    上面讲了那么多,不知道大家吸收了没有,学习的过程中一定要学会总结和抓中重点哦~

    咱们来看一个例子将上面类加载的过程来串一下吧,加深一下自己的印象:(其中还涉及了一点执行引擎的知识,没关系,很容易理解)

    class Lava {
    
        private int speed = 5;
        void flow() {
    
        }
    }
    
    public class Volcano {
    
        public static void main(String[] args) {
            Lava lava = new Lava();
            lava.flow();
        }
    }

    不同的虚拟机实现可能会用完全不同的方法来操作,下面描述的只是其中一种可能——但并不是仅有的一种。

    加载:读取一个类的class文件并将其中的二进制字节流组织成正确的数据结构放进运行时方法区中:

    要运行Volcano程序,首先得以某种“依赖于实现的”方式告诉虚拟机“Volcano”这个名字。之后,虚拟机将找到并读入相应的class文件“Volcano.class”,然后它会从导入的class文件里的二进制数据中提取类型信息并放到方法区中。通过执行保存在方法区中的字节码,虚拟机开始执行main()方法,在执行时,它会一直持有指向当前类(Volcano类)的常量池(方法区中的一个数据结构)的指针。

    注意:虚拟机开始执行Volcano类中main()方法的字节码的时候,尽管Lava类还没被装载,但是和大多数(也许所有)虚拟机实现一样,它不会等到把程序中用到的所有类都装载后才开始运行。恰好相反,它只会需要时才装载相应的类。(延迟加载、动态连接

    main()的第一条指令告知虚拟机为列在常量池第一项的类分配足够的内存。所以虚拟机使用指向Volcano常量池的指针找到第一项,发现它是一个对Lava类的符号引用,然后它就检查方法区,看Lava类是否已经被加载了。

    这个符号引用仅仅是一个给出了类Lava的全限定名“Lava”的字符串。为了能让虚拟机尽可能快地从一个名称找到类,虚拟机的设计者应当选择最佳的数据结构和算法。

    当虚拟机发现还没有装载过名为“Lava”的类时,它就开始查找并装载文件“Lava.class”,并把从读入的二进制数据中提取的类型信息放在方法区中。

    解析

    紧接着,虚拟机以一个直接指向方法区Lava类数据的指针来替换常量池第一项(就是那个字符串“Lava”),以后就可以用这个指针来快速地访问Lava类了。这个替换过程称为常量池解析,即把常量池中的符号引用替换为直接引用。

    终于,虚拟机准备为一个新的Lava对象分配内存。此时它又需要方法区中的信息。还记得刚刚放到Volcano类常量池第一项的指针吗?现在虚拟机用它来访问Lava类型信息,找出其中记录的这样一条信息:一个Lava对象需要分配多少堆空间。

    JAVA虚拟机总能够通过存储在方法区的类型信息来确定一个对象需要多少内存,当JAVA虚拟机确定了一个Lava对象的大小后,它就在堆上分配这么大的空间,并把这个对象实例的变量speed初始化为默认初始值0。

    当把新生成的Lava对象的引用压到栈中,main()方法的第一条指令也完成了。接下来的指令通过这个引用调用Java代码(该代码把speed变量初始化为正确初始值5)。另一条指令将用这个引用调用Lava对象引用的flow()方法。


    类加载器

    其实这一部分的知识并不多,你需要了解、掌握的知识只有两点:

    1.类加载器的命名空间 
    2.双亲委派模型

    说一点啊,看到这些高大上的名词你们不要怕,又不让你拿代码去实现,其实其中的原理都是很简单的。

    上面说了那么多,类加载器就是用于实现类加载动作的一段代码实现。好了,明白了它的作用,我们来看看什么是它命名空间。

    类加载器的命名空间:对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类命名空间。也就是说,你现在要比较两个类是否相等,只有在这两个类是同一个类加载器加载的前提下才有意义。

    这就是类加载器的命名空间,不难吧~但是读完上面这段话我们可以提取出另一个重要的信息:你上面所说的话中好像包含这样一种意思:类加载器在JVM中不止一个?你很聪明!好了,接下来说明何谓双亲委派模型。

    双亲委派模型:首先你得知道在JVM中有三种系统提供的类加载器:启动类加载器,扩展类加载器、应用程序类加载器。关于这三种加载器的描述大家自行百度,这也不是重点。

    贴一张图:

    这里写图片描述

    如图,这种层次结构就是双亲委派模型。

    好了,为了让大家印象深刻,我在给大家描述一下双亲委派模型的工作过程吧:

    它是一个递归调用类加载器的模型,也就是说如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是不断请求父加载器,如果父加载器可以完成这个加载请求,那么就由父加载器进行加载,如果父加载器不能完成加载请求(它的搜索范围中没有找到所需的类),子加载器才会尝试自己去加载。

    那么使用这种模型有什么好处?

    Java类随着类加载器一起具备了带有优先级的层级关系。例如java.lang.Object,在程序的各种类加载器环境中都是同一个类。

    关于双亲委派模型的实现代码非常简单,我就不再贴出,有兴趣的同学可以研读《深入理解Java虚拟机》第二版中232页的内容。


    JVM基本结构

    说到最后,我在给大家贴一张图吧(Class对象并没有规定实在Java堆中,但对于HotSpot而言,就是存放在方法区中,因此下面所贴的图有点不符):

    这里写图片描述

    这张图包含的信息量可不小,希望大家结合我上面所总结的,自己理清Class对象、ClassLoad、字节码、class文件之间的联系!


    展开全文
  • 加载加载”是”类加载”过程的一个阶段。在加载阶段,虚拟机需要完成以下3件事情:1.通过一个类的全限定名来获取定义此类的二进制字节流。 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3....
  • js实现图片懒加载原理

    万次阅读 多人点赞 2019-06-01 16:58:48
    有时候一个网页会包含很多的图片,例如淘宝京东这些购物网站,商品图片多只之又多,页面图片多,加载的图片就多。服务器压力就会很大。不仅影响渲染速度还会浪费带宽。比如一个1M大小的图片,并发情况下,达到1000...
  • loading加载

    2018-06-27 17:16:21
    页面加载时需要加载,请求接口也需要loading。在前端loading的需求无处不在,就是为了让用户体验更好。做loading的方式也有很多:下面我处理loading的用一张小的png图,yong .wgui-message{  position: fixed; ...
  • 类的加载机制

    2019-07-01 16:06:53
        类加载器ClassLoader就是加载其他类的类,它负责将字节码文件加载到内存,创建Class对象。     ClassLoader一般是系统提供的,不需要自己实现,不过,通过创建自定义的ClassLoader,可以实现一些强大...
  • jvm之java类加载机制和类加载器(ClassLoader)的详解

    万次阅读 多人点赞 2020-05-29 11:15:41
    当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。 一...
  • 加载的三种方式

    千次阅读 2018-07-27 22:52:22
    加载分为动态加载和静态加载。动态加载是从外存储器中加载类,一般类加载机制分析的也是动态加载。而静态加载本质上是从内存中创建类的实例对象,此时类已经被加载到内存中。 一.静态加载 1. 通过new关键字来...
  • Loading 加载中 图片素材

    万次阅读 2011-07-29 21:55:38
    Loading 加载中 图片素材 网友提供的一个自定义加载图片的网站 http://www.ajaxload.info/
  • 剑指Offer——知识点储备-故障检测、性能调优与Java类加载机制故障检测、性能调优用什么工具可以查出内存泄露 (1)MerroyAnalyzer:一个功能丰富的java堆转储文件分析工具,可以帮助你发现内存漏洞和减少内存消耗; ...
  • Vue项目中实现图片懒加载

    万次阅读 热门讨论 2020-01-03 09:13:24
    此文章已永久迁移至个人博客网站,为不影响使用,请打开如下网址访问 https://www.iiter.cn/blogs/28
  • Glide-加载本地图片

    万次阅读 2017-03-04 16:57:45
    前言:这一节我们将介绍Glide如何加载本地图片Glide 系列目录 1.Glide-入门教程 2.Glide-占位图以及加载动画 3.Glide-加载本地图片 1.在清单文件中增加权限 2.加载其实Glide加载本地图片和加载网络图片调用的方法是...
  • JVM进阶(十六)——JAVA 双亲委派模型

    万次阅读 2020-09-17 16:32:09
    同时也了解到类加载器的存在,每个加载器对应着不同的加载目录,相互配合着,从而使整个加载过程稳定而安全。   那么他们是如何配合的呢?如果我自己写一个类,名字叫做String可以吗?   首先我们来看一张图:...
  • java 类加载

    万次阅读 2018-06-11 17:15:01
    加载器基本概念 什么是类加载器 顾名思义,类加载器(class loader)用来加载 Java 类到Java 虚拟机中。 具体的说就是:Java源程序(.java 文件)在经过Java 编译器编译之后就被转换成 Java 字节代码(.class文件...
  • Word、PowerPoint无法加载mathtype加载

    万次阅读 2018-01-29 15:54:21
    一般我们在卸载mathtype之后,都会出现“Word无法加载mathtype加载项”或者“PowerPoint无法加载mathtype加载项”的问题,这是因为我们卸载mathtype之后,还会存在一些与office有关的加载项配置没有删除掉,接下来将...
  • vueJs实现DOM加载完成之后,调用js

    万次阅读 2018-12-01 10:29:38
    参考 http://blog.csdn.net/u014445339/article/details/54929475  
  • [Unity-24] Unity的四种加载场景的方法

    万次阅读 2015-06-28 21:50:05
    Unity官方提供了4中加载场景(scene)的方法,分别是: 1. Application.LoadLevel():同步加载 2. Application.LoadLevelAsync():异步加载 3. Application.LoadLevelAddictive():同步附加式加载 4. ...
  • from selenium import webdriver options=webdriver.FirefoxProfile() options.set_preference(‘permissions.default.image’,2) b=webdriver.Firefox(options) b.get(‘http://image.baidu.com/’)
1 2 3 4 5 ... 20
收藏数 2,986,228
精华内容 1,194,491
关键字:

加载