NEW时,如果发现符号引用代表的类没有进行类加载,会先执行对应的类加载。 类加载执行完后给对象分配内存,内存的分配有两种方式分别是指针碰撞和空闲列表, 用哪种取决于使用的垃圾回收器是否具备空间整理能力,有Compact能力的回收器用的是指针碰撞没有的则是空闲列表。 为了保证多线程环境下对象分配内存的原子性,默认情况下虚拟机采用CAS算法加失败重试来保证原子性,还有一种方法是给每一个线程都分配一块TLAB内存空间。 内存分配完成后,虚拟机会对堆内存空间内实例对象的成员变量进行零值初始化,如果之前用的是TLAB的话, 零值初始化也可以在TLAB给对象分配内存时进行。这样在Java代码中可以不给字段赋与初始值就使用字段。 然后虚拟机会给对象设置一个对象头,里面存放有一些信息。 最后会执行对象的构造方法进行初始化。是否正确
时间: 2024-03-30 19:34:14 浏览: 67
您的理解是正确的。当执行`new`关键字时,如果虚拟机发现符号引用代表的类没有进行类加载,会先执行对应的类加载,然后再进行内存分配。内存分配有两种方式,分别是指针碰撞和空闲列表。使用哪种方式取决于使用的垃圾回收器是否具备空间整理能力。如果垃圾回收器具备空间整理能力,就使用指针碰撞。否则,就使用空闲列表。
为了保证多线程环境下对象分配内存的原子性,默认情况下虚拟机采用CAS算法加失败重试来保证原子性。另外,还有一种方法是给每个线程分配一块TLAB内存空间。内存分配完成后,虚拟机会对堆内存空间内实例对象的成员变量进行零值初始化。如果之前使用的是TLAB,零值初始化也可以在TLAB给对象分配内存时进行。这样,在Java代码中可以不给字段赋与初始值就使用字段。最后,虚拟机会给对象设置一个对象头,里面存放有一些信息。最后执行构造方法进行初始化。
相关问题
符号引用即用用字符串符号的形式来表示引用,其实被引用的类、方法或者变量还没有被加载到内存中。而直接引用则是有具体引用地址的指针,被引用的类、方法或者变量已经被加载到内存中。以变量举个例子:
符号引用(Symbolic Reference)和直接引用(Direct Reference)的区别主要体现在程序的动态链接阶段。在编程中,当我们使用符号引用,比如通过类名、方法名或变量名来创建一个引用,这个引用只是一个名字标记,并不会立即引发类的加载或实例化。只有当程序真正尝试访问这个引用时,如果对应的类还未加载,系统会进行动态查找并加载。
例如,在Java中,我们可以这样声明一个未初始化的静态成员引用:
```java
Class<?> clazz = MyClass.class;
```
在这个例子中,`MyClass.class`是一个符号引用,`MyClass`实际的类对象尚未加载到内存。然而,如果我们试图调用该类的一个方法,如`clazz.getDeclaredMethod("myMethod")`,这时才会触发加载`MyClass`并找到对应的方法。
相比之下,直接引用通常是硬编码的指针或者引用类型,一旦分配就指向了特定的内存位置,例如:
```java
MyClass obj = new MyClass();
obj.myMethod();
```
这里,`obj`就是对`MyClass`对象的直接引用,`myMethod()`是直接在`obj`上执行的,因为`MyClass`已经完全加载到内存中。
加载(第二次再 new 同一个类时,不需再加载)。3、加载子类会先加载父类。
### Java 类加载机制详解
#### 加载阶段
在 Java 中,类的加载是指将类的 `.class` 文件中的二进制数据读入到内存中,并将其放在运行时数据区的方法区内,然后在堆区创建一个 `java.lang.Class` 对象,用来封装类的相关信息[^1]。当 JVM 启动时,会预先加载一些核心库所需的类,之后按需动态加载其他类。
#### 验证阶段
验证的主要目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机自身的安全[^2]。这一阶段主要包括文件格式验证、元数据验证、字节码验证以及符号引用验证。
#### 准备阶段
准备阶段主要是为类变量分配内存并设置默认初始值,这些内存是在方法区中进行分配的。需要注意的是,在此阶段并不会执行任何 Java 代码片段[^3]。
#### 解析阶段
解析阶段是把常量池内的符号引用替换为直接引用的过程。这一步骤包括了对类或接口、字段、类方法及接口方法的解析。
#### 初始化阶段
这是类加载最后的一个阶段,也是执行类中定义的 Java 程序代码的第一个阶段。在此阶段,JVM 执行类的初始化语句,完成静态变量赋值和静态代码块的执行操作。
---
#### 关于类加载不重复加载相同类
Java 的类加载器遵循 **双亲委派模型** (Parent Delegation Model),这意味着每个类加载器都会优先让其父类加载器去尝试加载某个类。只有当父类加载器无法找到或者加载该类时,才会由自己负责加载。这种设计可以有效防止多个版本的同一类被多次加载的情况发生,从而保证类的一致性和安全性。
#### 子类加载前先加载父类
按照 Java 虚拟机规范的规定,在初始化一个类之前,必须先完成对其父类的初始化工作。这是因为子类可能会继承父类的一些属性和方法,而这些成员可能依赖于已经正确初始化过的父类状态。
#### 类加载过程中涉及的关键概念
- **类加载器**: 主要分为启动类加载器(Bootstrap)、扩展类加载器(Extension) 和应用程序类加载器(Application/ System)。
- **类相等性**: 即使两个类来自同一个 .class 文件,但如果它们是由不同的类加载器实例所加载,则会被视为完全不同的两种类型。
以下是展示如何通过反射获取特定类加载器及其层次结构的小例子:
```java
public class ClassLoaderDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 获取当前线程上下文类加载器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
// 输出应用类加载器
System.out.println("Application Class Loader: " + contextClassLoader);
// 查看父级关系直到 Bootstrap loader
while(contextClassLoader != null){
contextClassLoader = contextClassLoader.getParent();
System.out.println("Next Parent Class Loader: "+contextClassLoader);
}
// 使用 Class.forName 方法显式加载某类并打印其对应的 CLassLoader
Class<?> clazz = Class.forName("java.util.ArrayList");
System.out.println(clazz.getName() +" loaded by :"+clazz.getClassLoader());
}
}
```
阅读全文
相关推荐


















