Java虚拟机之对象创建过程与类加载机制及双亲委派模型

目录
  • 一、对象的创建过程:
    • 1、对象的创建过程:
    • 2、对象的访问方式:
  • 二、类加载机制:
    • 2.1、加载阶段:
    • 2.2、验证阶段:
    • 2.3、准备阶段:
    • 2.4、解析阶段:
    • 2.5、初始化:
      • 2.5.1、类的主动引用:
      • 2.5.2、类的被动引用:
      • 2.5.3、()方法的特点:
  • 三、类加载器与双亲委派模型:
    • 3.1、JVM 的类加载器:
    • 3.2、双亲委派模型:
      • 3.2.1、双亲委派模型的工作原理:
      • 3.2.2、双亲委派模型的优点:
    • 3.3、类加载器源码:loadClass()
    • 3.4、如何破坏双亲委派模型:
  • 总结

一、对象的创建过程:

1、对象的创建过程:

对象的创建过程一般是从 new 指令(JVM层面)开始的,整个创建过程如下:

(1)首先检查 new 指令的参数是否能在常量池中定位到一个类的符号引用;

(2)如果没有,说明类还没有被加载,则须先执行相应的类加载、解析和初始化等类加载过程,该过程的具体步骤详见下文

(3)如果有,虚拟机将在堆中为新生对象分配内存。分配内存方式有:指针碰撞和空闲列表,具体选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

指针碰撞:如果Java堆是绝对规整的,所有用过的内存都放在一边,所有没用过的内存存放在另一边,中间存放一个指针作为分界点指示器。分配内存时,将指针从用过的内存区域向空闲内存区域移动等距离区域。
空闲列表:如果Java不是规整的,这时,虚拟机就必须维护一张列表,列表上记录了可用的内存块,在分配内存时,从列表上找到一个足够大的连续内存块分配给对象,并更新列表上的记录。

在分配对象内存空间的过程中,需要考虑在并发情况下,线程是否安全的问题。因为创建对象在虚拟机中是非常频繁的行为,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。因此必须要保证线程安全,解决这个问题有两种方案:

  • CAS以及失败重试(比较和交换机制):对分配内存空间的操作进行同步处理,实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。CAS操作需要输入两个数值,一个旧值(操作前期望的值)和一个新值,在操作期间先比较旧值有没有发送变化,如果没有变化,才交换成新值,否则不进行交换。
  • TLAB(Thread Local Allocation Buffer,本地线程分配缓存):把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块私有内存,也就是本地线程分配缓冲。TLAB的目的是在为新对象分配内存空间时,让每个Java应用线程能在使用自己专属的分配指针来分配空间,减少同步开销。

(4)内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值,保证了对象实例的字段在 Java 代码中可以不赋初始值就可以直接使用;

(5)对对象进行必要的设置,例如是哪个对象的实例、如何才能找到类元信息、对象的哈希码、GC 分代年龄等信息,这些信息存放在对象头中。

(6)执行 init 方法,把对象按照程序员意愿进行初始化。

至此,一个对象就被创建完毕,同时会在Java栈中分配一个引用指向这个对象,通过栈上面的引用可以访问堆中的具体对象,访问对象主要有两种方式:通过句柄访问对象和直接指针访问对象。

2、对象的访问方式:

(1)通过句柄访问对象:

在Java堆中划出一块内存专门作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的地址地址信息。

(2)通过直接指针访问对象:

(3)优劣对比:

① 使用句柄,reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。

② 直接指针,速度快,节省一次指定定位的开销。

二、类加载机制:

Java 文件中的代码在编译后,就会生成JVM能够识别的二进制字节流 class 文件,class 文件中描述的各种信息,都需要加载到虚拟机中才能被运行和使用。

类加载机制,就是虚拟机把类的数据从class文件加载到内存,并对数据进行校检,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程。类从加载到虚拟机内存开始,到卸载出内存结束,整个生命周期包括七个阶段,如下图(每个过程作用见文章第四部分):

2.1、加载阶段:

这阶段的虚拟机需要完成三件事情:

(1)通过一个类的全限定名来获取定义此类的二进制字节流。
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
(3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

2.2、验证阶段:

这阶段是为了确保class文件的字节流包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全。分为4个校检动作:

(1)文件格式验证:验证字节流是否符合class文件格式的规范,并且能被当前版本的虚拟机处理,通过该阶段后,字节流会进入内存的方法区中进行储存。
(2)元数据验证:对字节码描述的信息进行语言分析,对类的元数据信息进行语义校验,确保其描述的信息符合java语言规范要求。
(3)字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段对类的方法进行校验分析,保证类的方法在运行时不会做出危害虚拟机安全的事件。
(4)符号引用验证:对类自身以外的信息(常量池中各种符号引用)的信息进行校检,确保解析动作能正常执行(该动作发生在解析阶段中)

2.3、准备阶段:

正式为类变量分配内存空间并设置数据类型零值。这个阶段进行内存分配的仅包括类变量(static修饰),不包括实例变量,实例变量会在对象实例化时随对象一起分配在java堆。

public static int value= 123 ; //变量value在准备阶段过后的初始值是0,不是123.
public static final int value = 123 ; //特殊情况:会生成ConstantValue属性,在准备阶段初始值是123.

final、static、static final修饰的字段赋值的区别:

(1)static修饰的字段在准备阶段被初始化为0或null等默认值,然后在初始化阶段(触发类构造器)才会被赋予代码中设定的值,如果没有设定值,那么它的值就为默认值。

(2)static final修饰的字段在Javac时生成ConstantValue属性,在类加载的准备阶段根据ConstantValue的值为该字段赋值,它没有默认值,必须显式地赋值,否则Javac时会报错。可以理解为在编译期即把结果放入了常量池中。

(3)final修饰的字段在运行时被初始化(可以直接赋值,也可以在实例构造器中()赋值),一旦赋值便不可更改。

2.4、解析阶段:

将常量池的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄、和调用限定符7类符号引用。

对同一符号引用进行多次解析请求是很常见的事情,除invokedynamic指令以外,虚拟机可以对第一次解析的结果进行缓存,从而避免解析动作重复进行。invokedynamic对应的引用称为“动态调用限定符”,必须等到程序实际运行到这条指定的时候,解析动作才能进行。因此,当碰到由前面的invokedynamic指令触发过的解析的符号引用时,并不意味着这个解析结果对其他的invokedynamic指令也同样生效。

(1)符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何字面量,只要使用时无歧义定位到目标即可。符号引用与虚拟机的内存布局无关,引用的目标并不一定已经加载到内存中。各种虚拟机实现的内存布局可以不相同,但是他们能接受的符号引用必须都是一致的,符号引用的字面量形式明确地定义在java虚拟机规范的calss文件格式中。

(2)直接引用:直接引用是可以直接定位到目标的指针、相对偏移量或是一个能间接定位目标的句柄。直接引用是与虚拟机的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。

2.5、初始化:

初始化阶段才真正执行类中定义的java代码。执行类构造器()方法的过程,并按程序的设置去初始类变量和其他资源。

2.5.1、类的主动引用:

在初始化阶段,有且只有5种场景必须立即对类进行“初始化”,称为主动引用:

(1)遇到new、getstatic、putstatic、invokestatic这4条指定时。对应的场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已经在编译期把结果放入常量池的静态字段除外),以及调用一个类的静态方法的时候。

(2)使用java.lang.reflect包的方法对类进行发射调用的时候。

(3)当初始化一个类的时候,如果发现其父类还没进行初始化,则必须对父类进行初始化。(与接口的区别:接口在初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候,才会初始化)

(4)当虚拟机启动时,用户指定的要执行的主类(包含main方法的类)。

(5)java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个句柄所对应的类没有进行过初始化,则需要触发其初始化。

2.5.2、类的被动引用:

除了主动引用,其他引用类的方式都不会触发初始化,称为被动引用:

(1)对于静态字段,只有直接定义这个字段的类才会被初始化,通过其子类来引用父类中定义的静态字段,只会触发其父类的初始化而不会触发子类的初始化。

//父类
public class SuperClass {
    //静态变量value
    public static int value =123456;
    //静态块,父类初始化时会调用
    static{
        System.out.println("父类初始化!");
    }
}  

//子类
public class SubClass extends SuperClass{
    //静态块,子类初始化时会调用
    static{
        System.out.println("子类初始化!");
    }
}  

//主类、测试类
public class InitTest {
    public static void main(String[] args){
        System.out.println(SubClass.value);  //输出结果:父类初始化! 123456
    }
}

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

//父类
public class SuperClass {
    //静态变量value
    public static int value = 123456;
    //静态块,父类初始化时会调用
    static{
        System.out.println("父类初始化!");
    }
}  

//主类、测试类
public class InitTest {
    public static void main(String[] args){
        SuperClass[] test = new SuperClass[10]; //输出结果:没有任何输出结果
    }
}

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

//定义常量的类
public class ConstClass {
    static{
        System.out.println("常量类初始化!");
    }  

    public static final String HELLOWORLD = "hello world!";
}  

//主类、测试类
public class InitTest {
    public static void main(String[] args){
      System.out.println(ConstClass.HELLOWORLD);  //输出结果:hello world
    }
}  

2.5.3、()方法的特点:

(1)()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,收集的顺序是由语句在源文件中出现的顺序决定的。静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但是不可以访问。

public class Test {

	static{
		i=0; //给变量赋值可以正常编译通过
		System.out.println(i); //编译器会提示非法向前引用
	}
	static int i=1;
}

(2)()方法与实例构造器()方法不同,它不需要显示调用父类构造器,虚拟机会保证子类的()方法执行之前,父类的()方法已经执行完毕,所以父类中定义的静态语句块要优先于子类的变量赋值操作,虚拟机中第一个被执行的()方法的类是java.lang.Object。

(3)()方法对于类或接口并不是必需的,如果一个类中没有静态语句块,也就没有对变量的赋值操作,那么编译器可以不为这个类生成()方法。

(4)接口中不能使用静态语句块,仍然有变量初始化操作,因此仍然会生成()方法,与类不同的是,执行接口中的()方法不需要先执行父接口的()方法。只有父接口中定义的变量被使用是,才需要初始化父接口,同时,接口实现类在初始化时也不会执行接口的()方法。

(5)()方法在多线程环境中被正确的加锁、同步,多个线程同时去初始化一个类,只会有一个线程执行()方法,其他线程则需要阻塞等待,直到活动线程执行()方法完毕,活动线程执行完毕后,其他线程唤醒后被不会再次进入()方法,因为同一个类加载器下,一个类型只会被初始化一次。

三、类加载器与双亲委派模型:

3.1、JVM 的类加载器:

类加载机制生命周期的第一阶段,即加载阶段需要由类加载器来完成的,类加载器根据一个类的全限定名读取类的二进制字节流到JVM中,然后生成对应的java.lang.Class对象实例,

在虚拟机默认提供了3种类加载器,启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用类加载器(Application ClassLoader),如果有必要还可以加入自己定义的类加载器。

(1)启动类加载器(Bootstrap ClassLoader):负责加载 在\lib目录 和 被-Xbootclasspath参数所指定的路径中的类库

(2)扩展类加载器(Extension ClassLoader):负责加载 \lib\ext目录 和 被java.ext.dirs系统变量所指定的路径中的所有类库

(3)应用程序类加载器(Application ClassLoader):负责加载用户类路径classPath所指定的类库,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

(4)自定义加载器(CustomClassLoader):由应用程序根据自身需要自定义,如 Tomcat、Jboss 都会根据 j2ee 规范自行实现。

任意一个类在 JVM 中的唯一性,是由加载它的类加载器和类的全限定名一共同确定的。因此,比较两个类是否“相等”的前提是这两个类是由同一个类加载器加载的,否则,即使两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。JVM 的类加载机制,规定一个类有且只有一个类加载器对它进行加载。而如何保证这个只有一个类加载器对它进行加载呢?则是由双亲委派模型来实现的。

3.2、双亲委派模型:

双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父类加载器。(类加载器之间的父子关系不是以继承的关系实现,而是使用组合关系来复用父加载器的代码)

3.2.1、双亲委派模型的工作原理:

如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层级的类加载器都是如此,因此所有请求最终都会被传到最顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。因此,加载过程可以看成自底向上检查类是否已经加载,然后自顶向下加载类。

3.2.2、双亲委派模型的优点:

(1)使用双亲委派模型来组织类加载器之间的关系,Java类随着它的类加载器一起具备了一种带有优先级的层次关系。

(2)避免类的重复加载,当父类加载器已经加载了该类时,子类加载器就没必要再加载一次。

(3)解决各个类加载器的基础类的统一问题,越基础的类由越上层的加载器进行加载。避免Java核心API中的类被随意替换,规避风险,防止核心API库被随意篡改。

例如类 java.lang.Object,它存在在 rt.jar 中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的 Bootstrap ClassLoader 进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个 java.lang.Object 的同名类并放在 ClassPath 中,那系统中将会出现多个不同的 Object 类,程序将混乱。因此,如果开发者尝试编写一个与 rt.jar 类库中重名的 Java 类,可以正常编译,但是永远无法被加载运行。

3.3、类加载器源码:loadClass()

实现双亲委派的代码都集中在 java.lang.ClassLoader 的 loadClass() 方法之中,下面我们简单看下 loadClass() 的源码是怎么样的:

public abstract class ClassLoader {
  // 每个类加载器都有一个父加载器
  private final ClassLoader parent;

  public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
            // 首先,检查该类是否已经加载过了
            Class<?> c = findLoadedClass(name);
            // 如果没有加载过
            if (c == null) {
                if (parent != null) {
                 // 先委托给父加载器去加载,注意这是个递归调用
                 c = parent.loadClass(name, false);
                } else {
                   // 如果父加载器为空,查找 Bootstrap 加载器是不是加载过了
                   c = findBootstrapClassOrNull(name);
                }

                // 如果父加载器没加载成功,调用自己的 findClass 去加载
                if (c == null) {
                    c = findClass(name);
                }
            } 

            return c;
        }
    }

    // ClassLoader 中 findClass 方式需要被子类覆盖,下面这段代码就是对应代码
    protected Class<?> findClass(String name){
       //1. 根据传入的类名 name,到在特定目录下去寻找类文件,把 .class 文件读入内存
          ...
       //2. 调用 defineClass 将字节数组转成 Class 对象
       return defineClass(buf, off, len);
    }

    // 将字节码数组解析成一个 Class 对象,用 native 方法实现
    protected final Class<?> defineClass(byte[] b, int off, int len){
    }
}

3.4、如何破坏双亲委派模型:

双亲委派过程是在 loadClass() 方法中实现的,如果想要破坏这种机制,那么就自定义一个类加载器(继承自 ClassLoader),重写其中的 loadClass() 方法,使其不进行双亲委派即可。ClassLoader 中和类加载有关的方法有很多,除了前面提到了 loadClass(),除此之外,还有 findClass() 和 defineClass() 等,那么这几个方法有什么区别呢:

  • loadClass() 就是主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中
  • findClass() 根据名称或位置加载 .class 字节码
  • definclass() 把字节码转化为 Class 对象

findClass() 是 JDK1.2 之后 ClassLoader 新添加的方法,在 JDK1.2 之后已不提倡用户直接覆盖 loadClass() 方法,而是建议把自己的类加载逻辑实现到 findClass() 方法中,因为在 loadClass() 方法的逻辑里,如果父类加载器加载失败,则会调用自己的 findClass() 方法来完成加载。而同样如果你想定义一个自己的类加载器,并且要遵守双亲委派模型,那么可以继承 ClassLoader,并且在 findClass() 中实现你自己的加载逻辑即可

对于双亲委派模型的破坏,最经典例子就是 Tomcat 容器的类加载机制了,它实现了自己的类加载器 WebApp ClassLoader,并且打破了双亲委派模型,在每个应用在部署后,都会创建一个唯一的类加载器,对此感兴趣的读者可以阅读后面这篇文章:Tomcat 的类加载机制

总结

到此这篇关于Java虚拟机之对象创建过程与类加载机制及双亲委派模型的文章就介绍到这了,更多相关Java虚拟机对象创建过程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入理解Java虚拟机体系结构

    1概述 众所周知,Java支持平台无关性.安全性和网络移动性.而Java平台由Java虚拟机和Java核心类所构成,它为纯Java程序提供了统一的编程接口,而不管下层操作系统是什么.正是得益于Java虚拟机,它号称的"一次编译,到处运行"才能有所保障. 1.1Java程序执行流程 Java程序的执行依赖于编译环境和运行环境.源码代码转变成可执行的机器代码,由下面的流程完成: Java技术的核心就是Java虚拟机,因为所有的Java程序都在虚拟机上运行.Java程序的运行需要Java虚拟

  • Java JVM虚拟机运行机制

    一:JVM基础概念 JVM(Java虚拟机)一种用于计算设备的规范,可用不同的方式(软件或硬件)加以实现.编译虚拟机的指令集与编译微处理器的指令集非常类似.Java虚拟机包括一套字节码指令集.一组寄存器.一个栈.一个垃圾回收堆和一个存储方法域. Java虚拟机(JVM)是可运行Java代码的假想计算机.只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行. 下面看下Jvm的体系结构图: 二:解释型语言和编译型语言的联系与区别. 编译型语言是通过

  • java虚拟机

    众所周知,jvm的内存是受限的,一为机器的体系架构,二为操作系统本身.x86,x86-64,SPARC,.....的内存映射是不同,而各操作系统的内存管理机制也有区别.以下是来自http://fengyouhua.javaeye.com/blog/58170 1. Heap设定与垃圾回收Java Heap分为3个区,Young,Old和Permanent.Young保存刚实例化的对象.当该区被填满时,GC会将对象移到Old区.Permanent区则负责保存反射对象,本文不讨论该区.JVM的Hea

  • java虚拟机指令dup详解

    本文实例为大家介绍了java虚拟机指令dup,供大家参考,具体内容如下 举个例子: public class ExceptionTest{ void cantBeZero(int i) throws Exception{ throw new Exception(); } } 上面代码编译后的字节码指令如下: void cantBeZero(int) throws java.lang.Exception; descriptor: (I)V flags: Code: stack=2, locals=

  • 深入解析java虚拟机

    java虚拟机是什么 "java虚拟机"可能指以下三种东西:1).抽象规范:2).一个具体的实现:3).一个运行中的虚拟机实例: java虚拟机生命周期 启动当启动一个java程序时,一个虚拟机实例诞生.虚拟机实例通过调用某个初始类的public static void main(String[] args)方法来运行一个java程序.任何拥有这样一个main方法的类都可以作为java程序运行的起点,所以必须要告诉虚拟机初始类的名称,整个程序将从它的main方法开始运行.消亡初始类的m

  • Java虚拟机之对象创建过程与类加载机制及双亲委派模型

    目录 一.对象的创建过程: 1.对象的创建过程: 2.对象的访问方式: 二.类加载机制: 2.1.加载阶段: 2.2.验证阶段: 2.3.准备阶段: 2.4.解析阶段: 2.5.初始化: 2.5.1.类的主动引用: 2.5.2.类的被动引用: 2.5.3.()方法的特点: 三.类加载器与双亲委派模型: 3.1.JVM 的类加载器: 3.2.双亲委派模型: 3.2.1.双亲委派模型的工作原理: 3.2.2.双亲委派模型的优点: 3.3.类加载器源码:loadClass() 3.4.如何破坏双亲委派

  • java类的加载过程以及类加载器的分析

    我们知道,我们写的java代码保存的格式是 .java, java文件被编译后会转换为字节码,字节码可以在任何平台通过java虚拟机来运行,这也是java能够跨平台的原因. 那JVM是如何来让我们写的java文件运行的呢? 这个问题通常的问法好像是:类是如何被加载的. 记得第一次遇见这个问题的时候,同学给我的回答是: 1.虚拟机会加载JDK里类的核心包 2.虚拟机会加载JDK里类的扩展包 3.虚拟机会加载JDK里类的系统包 4.虚拟机再会加载我们写好的java类. 初学的时候,大家都这么说,好像

  • Java类加载器与双亲委派机制和线程上下文类加载器专项解读分析

    目录 一.类加载器 1.启动类加载器 2.拓展类加载器 3.应用类加载器 4.类的命名空间 二.双亲委派机制 1.类加载机制流程 2.类加载器加载顺序 3.双亲委派机制流程 4.源码分析 5.双亲委派机制优缺点 三.线程上下文类加载器 1.线程上下文类加载器(Context Classloader) 2.ServiceLoader 四.自定义类加载器 一.类加载器 类加载器就是根据类的二进制名(binary name)读取java编译器编译好的字节码文件(.class文件),并且转化生成一个ja

  • 浅谈Java内存区域与对象创建过程

    一.java内存区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则依赖用户线程的启动和结束而建立和销毁.根据<Java虚拟机规范(JavaSE7版)>的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域. 1.程序计数器(线程私有) 程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码

  • java 虚拟机中对象访问详解

    java 虚拟机中对象访问详解 对象访问会涉及到Java栈.Java堆.方法区这三个内存区域. 如下面这句代码: Object objectRef = new Object(); 假设这句代码出现在方法体中,"Object objectRef" 这部分将会反映到Java栈的本地变量中,作为一个reference类型数据出现.而"new Object()"这部分将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的

  • JVM的类加载过程以及双亲委派模型详解

    jvm 的主要组成部分 类加载器(ClassLoader) 运行时数据区(Runtime Data Area) 执行引擎(Execution Engine) 本地库接口(Native Interface) jvm 运行时数据区的组成 方法区: ①方法区主要用来存储已被虚拟机加载的类信息(构造器,接口定义).常量.静态变量和运行时常量池等数据. ②该区域是被线程共享的. ③方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用.该常量池具有动态性,也就是说常量并不一定是编译时确定,运行

  • 详解Java类加载器与双亲委派机制

    目录 引子 了解.class文件 类加载的过程 类加载器 与 双亲委派机制 ClassLoader 自定义类加载器 编写一个自定义的类加载器 为什么我们这边要打破双亲委派机制 自定义类加载器时,如何打破双亲委派机制 SPI机制 与 线程上下文类加载器 JDBC Tomcat SpringBoot Starter 尾语 引子 大家想必都有过平时开发springboot 项目的时候稍微改动一点代码,就得重启,就很烦 网上一般介绍 2种方式 spring-boot-devtools,或者通过JRebe

  • java 详解类加载器的双亲委派及打破双亲委派

    java 详解类加载器的双亲委派及打破双亲委派 一般的场景中使用Java默认的类加载器即可,但有时为了达到某种目的又不得不实现自己的类加载器,例如为了达到类库的互相隔离,例如为了达到热部署重加载功能.这时就需要自己定义类加载器,每个类加载器加载各自的类库资源,以此达到资源隔离效果.在对资源的加载上可以沿用双亲委派机制,也可以打破双亲委派机制. 一.沿用双亲委派机制自定义类加载器很简单,只需继承ClassLoader类并重写findClass方法即可.如下例子: ①先定义一个待加载的类Test,它

  • JVM的类加载器和双亲委派模式你了解吗

    目录 类加载器 1.启动类加载器 2.拓展类加载器 3.应用程序类加载器 4.双亲委派模式 5.自定义类加载器 5.1.使用场景 5.2.步骤 总结 类加载器 Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类.实现这个动作的代码被称为“类加载器”(ClassLoader). 对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个

  • 新手入门Jvm-- JVM对象创建与内存分配机制

    1. 对象的创建 对象创建的主要流程: 1.类加载检查 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析和初始化过.如果没有,那必须先执行相应的类加载过程. new指令对应到语言层面上讲是,new关键词.对象克隆.对象序列化等. 2.分配内存 在类加载检查通过后,接下来虚拟机将为新生对象分配内存.对象所需内存的大小在类 加载完成后便可完全确定,为对象分配空间的任务等同于把 一块确定大小的内存从Java堆中

随机推荐