• Java虚拟机(三) —— 类加载


    概述

    类加载:将类从class文件加载到内存中,并且对字节码进行校验、准备、解析和初始化,最终转换为可以被JVM直接使用的Java类型。

    类加载的过程

    加载——验证——准备——解析——初始化——使用——卸载

    准备

    虚拟机在准备阶段为类变量(static修饰)分配内存,并设置类变量的初始值。这些内存是在哪里分配的?堆里?答案是方法区。
    而实例变量则是在初始化阶段随对象一起分配在堆内存。而且这里要注意private static int a = 1;在准备阶段,a为0而不为1,在初始化阶段,a才被赋为1

    解析

    解析阶段是将 常量池中的符号引用替换为直接引用

    初始化

    遇到下列4种情况,必须对类进行初始化
    (1)有new、getstatic、putstatic、invokestatic
    (2)反射调用时,如果类没有初始化,就必须首先对类初始化
    (3)初始化一个类时,如果父类没有被初始化,就首先对父类进行初始化。
    (4)虚拟机启动时需要指定一个主类(main方法所在的类),虚拟机必须首先对其初始化

    监控类的加载

    -XX:+TraceClassLoading
    

    被动引用不会触发类的初始化

    (1)子类引用父类的静态字段,不会引起子类的初始化

    
    class Father{
    
        public static int a = 1;
    
        static{
            System.out.println("father");
        }
    
    }
    
    class Son extends Father{
    
        public static int b = 2;
    
        static {
            System.out.println("son");
        }
    
    }
    
    
    public class ClassLoaderDemo {
    
        public static void main(String[] args) {
            System.out.println(Son.a);
        }
    
    }
    
    

    原因是,该静态变量虽然是子类引用,但底层还是属于父类的。

    (2)通过数组定义引用类,不会触发类的初始化

    
    public class ClassLoaderDemo {
    
        public static void main(String[] args) {
            Father[] fathers = new Father[10];
        }
    
    }
    
    

    只加载,并未初始化。

    (3)常量在编译的阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量类的初始化

    
    class Father{
    
        public static final int a = 1;
    
        static{
            System.out.println("father");
        }
    
    }
    
    public class ClassLoaderDemo {
    
        public static void main(String[] args) {
            System.out.println(Father.a);
        }
    
    }
    

    对象占用空间计算

    dir

    类加载器

    运行期间动态加载

    JVM对class文件并不是全部加载,而是按需加载,需要的时候才会加载进来。

    启动时加参数-verbose:class,打印类加载顺序。

    
    class A{
        static {
            System.out.println("A");
        }
    }
    public class ClassLoaderDemo2 {
    
        public static void main(String[] args) {
            System.out.println(String.valueOf("test"));
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            A a = new A();
        }
    
    }
    
    

    运行结果,会发现只有当执行到new A()是,A类才会被加载进来。

    双亲委派机制

    先委托父类加载器寻找目标类,在找不到的情况下,在自己的路径中查找目标类。

    是否可以创建一个全限定名相同的String类

    不可以。当AppClassLoader加载该String时,判断java.lang.String已经加载,便不会再次加载。所以执行的依旧是jdk中的String,但是系统的java.lang.String中没有main()方法,所以会报错。这是一种安全机制。

    
    package java.lang;
    
    public class String {
    
        public static void main(String[] args) {
            System.out.println("test");
        }
    
    }
    
    

    异常信息如下

    那么String是由哪个加载器加载的?

    打印为null,说明是启动类加载器加载的,由于该加载器是C++实现,所以返回null。

    
    public class ClassLoaderDemo2 {
    
        @Test
        public void testStringClassLoader(){
            // 打印为null,说明是启动类加载器加载的,由于该加载器是C++实现,所以返回null
            System.out.println(String.class.getClass().getClassLoader());
        }
    
    }
    
    

    启动类加载器(Bootstrap CLassLoader)

    无法被Java程序直接引用。它属于JVM内部使用的类加载器,既没有上级类加载器,也没有下级类加载器,因此不遵守ClassLoader的上级委托机制,只能算是JVM的一个类加载工具。

    扩展类加载器(Extension CLassLoader)

    应用程序类加载器(Application CLassLoader)

    1、为什么Java可以跨平台
    因为有java虚拟机,跨平台是因为字节码即class文件具有平台无关性,java代码会经过java虚拟机转换为字节码

    2、class文件的结构
    class文件主要是以8位字节码为基本单位的二进制文件,主要的存储方式是以类似于c语言的结构体来存储,其中的两种基本类型为无符号数和表,其中表中又会有其他的表和一些无符号数。就好比结构体中有一些变量,还有一些其他的结构体
    class文件的不同位置是固定的,第一个字节码是魔数,紧接着是次版本、主版本、常量池变量数,常量池变量表.......

    
    <dependency>
                <groupId>org.apache.lucene</groupId>
                <artifactId>lucene-core</artifactId>
                <version>4.0.0</version>
    </dependency>
    
    
    

    //计算指定对象及其引用树上的所有对象的综合大小,单位字节
    long RamUsageEstimator.sizeOf(Object obj)

    //计算指定对象本身在堆空间的大小,单位字节
    long RamUsageEstimator.shallowSizeOf(Object obj)

    //计算指定对象及其引用树上的所有对象的综合大小,返回可读的结果,如:2KB
    String RamUsageEstimator.humanSizeOf(Object obj)

    
    public class A {
    
    
        static{
            System.out.println("A");
        }
    
    
    }
    
    public class B extends A{
    
        static{
            System.out.println("B");
        }
    
    }
    
    
    
    
  • 相关阅读:
    模板实参演绎
    模板实例化
    模板中的名称
    友元函数在类中的声明在外围是不可见的
    C++ 宽字符(wchar_t)与窄字符(char)的转换
    ImageButton如何让图片按比例缩放不被拉伸
    C++模板实例化(1)
    android开发之GenyMotion与intelliJ的配置
    jacoco报告表头注释
    Spring源码工具类之StringUtils
  • 原文地址:https://www.cnblogs.com/fonxian/p/5683154.html
Copyright © 2020-2023  润新知