main()方法的执行 和 类加载

Java类加载与实例化
本文探讨了Java中类的加载过程与实例化流程,详细解释了类加载时静态成员和静态块的初始化顺序,以及实例化过程中非静态成员的初始化。同时介绍了Class.forName()与newInstance()的使用,并通过实例演示了静态与非静态变量的区别。

存在这么一个类:

public class test {

	public int noStaticNum = printNoStaticNum();
	public static int staticNum = printStaticNum();
	public static final int aaa = 111;
	public final int bbb = 222;

	static {
		System.out.println("执行静态块!");
	}

	public static int printStaticNum() {
		System.out.println("execute printStaticNum() method !");
		return 1;
	}

	public int printNoStaticNum() {
		System.out.println("execute printNoStaticNum() method !");
		return 1;
	}

	public static void main(String[] args) {

	}
}

1、类的实例化 test2 t1 = new test2() ;

实例化的结果是打印:

execute printStaticNum() method !
执行静态块!
execute printNoStaticNum() method !

 

实例化过程其实分开了说有两个过程:类加载和实例化的过程

类加载的过程大概包括以下过程(详情可以参考深入JVM这本书):

           * 1、把字节码文件转换为二进制文件
           * 2、在方法区中形成内部数据结构.(类中局部变量、方法、父类引用等)
           * 3、验证类文件中数据合法性
           * 4、初始化的过程(主要给静态变量和静态块初始化)

实例化的过程包括:

          * 1、实例化主要的工作是在内存堆中生成一个对象实例
          * 2、生成的实例会进行内部局部变量的初始化等

 

进行运行结果的验证:

直接执行这个类的结果是:

execute printStaticNum() method !
执行静态块!

可以发现运行main()并没有对局部变量进行初始化,那么是否运行main()没有实例化呢?内存中是否存在test2的对象呢?如果存在,又是一个什么样的形态呢?今天没时间了,以后验证吧!

 

2、Class.forName()和newInstance()

Class.forName()方法是加载的过程

newInstance()是一个实例化的过程

 

//利用默认的构造器生成对象
 test2 t2 = (test2)Class.forName("com.sudongyuan.test2").newInstance();

//如果类中有多个构造器可以选择相应的构造器生成对象
test2 t3 = (test2)Class.forName("com.sudongyuan.test2").getConstructors()[0].newInstance("");

 

3、反射机制,利用方法名调用对象方法

         Class a = Class.forName("com.sudongyuan.test2") ;
          Method b = a.getMethod("printNoStaticNum",null);
          b.invoke(a.newInstance(), null);

 

4、验证静态变量和非静态变量的区别(还有final变量)

         /* 验证静态变量和非静态变量的区别
           * 1、静态变量是所有的类实例共享,其中一个实例对其进行了修改会在别的类实例中反映出来
           * 2、非静态变量是实例独享,对其的改动不会反映到别的对象中
           * 3、final变量不能进行重新附值,所以在使用中final int a = 0 和final static int a = 0
           * 应该是一样的。区别是
           * */
          test2 f = (test2)a.newInstance() ;
          f.staticNum = 99000 ;
          f.noStaticNum = 29999 ;        

System.out.println(((test2)a.newInstance()).staticNum + " " + ((test2)a.newInstance()).noStaticNum );

打印的结果是:99000   1

 

类中有这两个final变量

   public static final int aaa  ;  
   public final int bbb  ;

 

对static final aaa,根据有的书中介绍说在编译时候就会把aaa转换为常量定义形式,但是在编译后的Class文件中,aaa并没有转换(这个还有待以后验证)。

那么aaa和bbb有什么区别呢?根据static的区别,是不是aaa是所有实例共享呢?

但是既然是常量,那么就无所谓共享不共享了。因为所有aaa都是相同的值。所以,aaa和bbb在使用上是完全一样的了。

只不过bbb是对象实例私有的,存放在堆空间的对象中,而aaa则是和一般的static一样存放在方法区中,以待对象的调用,加了一层限制,不允许调用的对象改变值罢了。


<think>嗯,用户这次问的是如何在main方法中直观展示Java的加载过程。从用户提供的站内引用来看,ta应该已经对JVM类加载机制有一定了解,特别是引用[3]明确提到了加载、链接、初始化的三个阶段,而引用[1]则强调了main方法执行前初始的加载顺序。 用户可能是个正在学习JVM底层机制的开发者,想通过代码实践加深理解。ta的真实需求可能不只是看文档描述,而是希望有可运行的代码来可视化这个过程。考虑到类加载本身是JVM隐式操作,直接"显示"比较困难,但通过静态代码块构造方法执行顺序可以间接观察。 我注意到引用[2]提到loadClass方法,这启发我可以通过自定义类加载器来拦截加载过程。不过用户明确要求"在main方法中"实现,所以更合适的方案是: 1. 利用静态变量赋值静态代码块作为观测点 2. 通过主动引用触发子类加载(如用户引用[1]提到的MethodHandle) 3. 结合-verbose:class参数补充日志 需要特别注意引用[4]提到的解析阶段发生在初始化之后,这在示例代码中通过ConstantClass.DATA的访问时机可以体现。另外用户引用[3]的测试题形式很有趣,或许可以建议ta自己写似测试来验证。 用户似乎混淆了"显示""触发"的概念,真正的加载过程是由JVM控制的,我们只能通过hook点间接观察。所以回复里要区分清楚主动触发被动观测的区别。 </think>在`main`方法中观察的加载过程,可通过以下方法实现: --- ### 方法一:通过静态代码块构造代码块 静态代码块在**类加载的初始化阶段**执行,构造代码块在对象实例化时执行: ```java class LoadTracer { // 静态代码块(类加载执行) static { System.out.println("[初始化] LoadTracer被加载"); } // 构造代码块(实例化时执行) { System.out.println("[实例化] LoadTracer对象被创建"); } } public class Main { public static void main(String[] args) { System.out.println("-- main方法开始 --"); new LoadTracer(); // 触发类加载对象实例化 System.out.println("-- main方法结束 --"); } } ``` **输出**: ``` -- main方法开始 -- [初始化] LoadTracer被加载 [实例化] LoadTracer对象被创建 -- main方法结束 -- ``` --- ### 方法二:添加JVM参数输出类加载日志 在启动时添加 `-verbose:class` 参数,控制台将打印所有加载的: ```bash java -verbose:class Main ``` 输出示例(片段): ``` [Loaded Main from file:/path/] [Loaded LoadTracer from file:/path/] -- main方法开始 -- [初始化] LoadTracer被加载 [实例化] LoadTracer对象被创建 -- main方法结束 -- ``` --- ### 原理说明 1. **触发时机**: - 类加载在首次**主动引用**时发生: - 创建实例(`new`) - 访问静态变量/方法(`Class.staticField`) - 反射(`Class.forName()`)[^1][^4] - 被动引用(如子引用父静态字段)不会触发子类加载[^3]。 2. **类加载阶段**: 1. **加载**:查找字节码并创建`Class`对象 2. **链接**:验证、准备(分配内存)、解析(符号引用转直接引用)[^3][^4] 3. **初始化**:执行静态代码块静态变量赋值(`<clinit>()`方法) > 通过静态代码块的输出位置,可观察到类加载完成并进入初始化阶段[^1]。 --- ### 进阶:自定义类加载器 重写`findClass()`方法可拦截加载过程: ```java public class CustomClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { System.out.println("[加载] 尝试加载: " + name); byte[] data = loadClassData(name); // 自定义加载字节码 return defineClass(name, data, 0, data.length); } } // 使用示例 public static void main(String[] args) throws Exception { CustomClassLoader loader = new CustomClassLoader(); Class<?> clazz = loader.loadClass("com.example.Demo"); clazz.newInstance(); // 触发初始化 } ``` --- ### 注意事项 - **延迟加载**:JVM按需加载,静态代码块在首次主动引用时执行[^3]。 - **避免被动引用**:如`SubClass.value`(父静态字段)不会触发子类加载。 - **类加载器层级**:启动类加载器 → 扩展类加载器 → 应用类加载器 → 自定义加载器[^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值