JVM 心得分享(加载 链接 初始化)
作者:Michael_Chong 发布时间:2023-09-01 19:17:50
基本概念:类加载的过程大致分为三个阶段
1、加载阶段:本阶段主要把class的二进制代码加载进入JVM,并且进行常量池(类名,方法名,字段名),方法区(二进制字节码),栈(本地方法栈结构),堆(java.lang.class对象)的设置。
有三个加载类:Bootstrap ClassLoader,加载jre/lib/下的类;
Extension ClassLoader:加载jre/lib/ext下的类;
ApplicationClassLoader:加载classpath下的类(应用程序自己开发的类,如 工程目录/bin/下的.class文件)
还有一个扩展的加载类,满足应用程序的特殊需求。类的加载时,父亲loader优先执行load动作,父亲load不了时,子类运作。
2、链接阶段:又分为三个小阶段 校验,准备,解析。
校验:实施字节码文件的格式,语法等的校验。
准备:对静态变量申请存储空间,并设置默认的初始值。如:private static int a =2;那么在准备阶段a被设置为0;
解析:把方法区中的符号指针替换为直接引用。
3、初始化阶段:对静态变量进行初始化,执行静态块,创建类的实例。上述的a变量在初始化阶段会被设置为2。
第一步:验证静态变量和静态块的加载+链接(校验,准备,解析)+初始化过程中值的变化。
package com.chong.studyparalell.clazz.loader;
public class ClassLoaderDemo {
public static void main(String []args){
Test test2 = new Test();
System.out.println("Test2实例化结束"+test2.toString());
}
}
package com.chong.studyparalell.clazz.loader;
public class Test{
private static Test test1 = new Test();
private static int a = 2;
private static int b = 2;
static {
System.out.println("【Test类静态块】a=" + a);
}
public Test(){
System.out.println("【Test类构造方法】a=" + a);
System.out.println("【Test类构造方法】b=" + b);
System.out.println("【Test类实例】" + this.toString());
}
public static Test newInstance(){
return test1;
}
}
log输出如下:
1 【Test类构造方法】a=0
2 【Test类构造方法】b=0
3 【Test类实例】com.chong.studyparalell.clazz.loader.Test@16c1857
4 【Test类静态块】a=2
5 【Test类构造方法】a=2
6 【Test类构造方法】b=2
7 【Test类实例】com.chong.studyparalell.clazz.loader.Test@1b1fd9c
8 Test2实例化结束com.chong.studyparalell.clazz.loader.Test@1b1fd9c
首先Test类在链接阶段(准备阶段),a,b分别被设置默认值0。
当new Test()执行后,
1)首先初始化Test类的三个静态变量 test1,a,b。
初始化test1时,第一次调用构造方法,此时a,b为0。对应日志1,2行。
实例化test1,日志第3行。
test1初始化完成后,继续初始化a,b,设为2。
接着初始化静态块 ,对应日志第4行。
2)执行Test类的构造方法
因为a,b已经被初始化为2,所以执行类的构造方法时,会输出a,b 为2。日志第5,6行。
实例化后输出地址信息,日志第7行。
3)最终main方法里打出实例工作完成,日志第8行。
第二步,加入父类后,进行确认。
package com.chong.studyparalell.clazz.loader;
public class TestBase {
private static int base_a = 2;
private static int base_b = 2;
static {
System.out.println("【父类静态块】 base_a="+base_a);
}
public TestBase(){
System.out.println("【父类 构造方法】base_a=" + base_a);
System.out.println("【父类 构造方法】base_b=" + base_b);
System.out.println("【父类 实例】"+ this.toString());
}
}
package com.chong.studyparalell.clazz.loader;
public class Test extends TestBase{
内容同第一步;
}
log输出如下:
【父类静态块】 base_a=2
【父类 构造方法】base_a=2
【父类 构造方法】base_b=2
【父类 实例】com.chong.studyparalell.clazz.loader.Test@19ab8d
【Test类构造方法】a=0
【Test类构造方法】b=0
【Test类实例】com.chong.studyparalell.clazz.loader.Test@19ab8d
【Test类静态块】a=2
【父类 构造方法】base_a=2
【父类 构造方法】base_b=2
【父类 实例】com.chong.studyparalell.clazz.loader.Test@14dcfad
【Test类构造方法】a=2
【Test类构造方法】b=2
【Test类实例】com.chong.studyparalell.clazz.loader.Test@14dcfad
Test2实例化结束com.chong.studyparalell.clazz.loader.Test@14dcfad
可以发现父类的静态变量,静态块,构造方法首先被初始化。然后子类的静态变量,静态块和构造方法被初始化。
第三步:写一个自定义的类加载器
package com.chong.studyparalell.clazz.loader;
public class MyClassLoaderPilot {
public static void main(String[] args) {
try {
MyClassLoader classLoader = new MyClassLoader();
String filename = "com.chong.studyparalell.demon.DemonThreadDemo";
Object clazz = classLoader.loadCustomizeClass(filename);
System.out.println(clazz);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.chong.studyparalell.clazz.loader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class MyClassLoader extends ClassLoader {
private String demoPath = "D:\\work\\temp\\";
public Class<?> loadCustomizeClass(String filename) throws ClassNotFoundException {
FileInputStream fis = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
// 1.获取class文件的字节码(二进制数据)
String[] fileNames = filename.split("\\.");
fis = new FileInputStream(demoPath + fileNames[fileNames.length-1] +".class");
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
baos.write(bytes, 0, len);
}
} catch (Exception e) {
throw new ClassNotFoundException();
} finally {
try {
fis.close();
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
byte[] paramArrayOfByte = baos.toByteArray();
// 2。把二进制文件定义为class对象返回
return defineClass(filename, paramArrayOfByte, 0, paramArrayOfByte.length);
}
}
日志输出如下:
class com.chong.studyparalell.demon.DemonThreadDemo
实际的跟着代码走一遍,看看控制台的输出,用自己的思路虚拟着跟一跟,对于类的加载过程能够认识的更加清晰一些。
来源:http://www.cnblogs.com/chongpf/p/7656593.html


猜你喜欢
- (注意:本文基于JDK1.8) 前言包括迭代器中的remove()方法,以及删除单个元素、删除多个元素、删除所有元素、删除不包含的
- 一、缩略图在浏览相册的时候,可能需要生成相应的缩略图。直接上代码:public class ImageUtil { private Logg
- 这段时间想到一个有趣的功能,就是在Android的代码编译期间进行一些骚操作,来达到一些日常情境下难以实现的功能,比如监听应用中的所有onC
- Springboot自带定时任务实现动态配置Cron参数同学们,我今天分享一下SpringBoot动态配置Cron参数。场景是这样子的:后台
- 代码如果不进行格式化的处理,那么在查阅上会浪费不少的时间。今天我们要说的是字符串的格式化处理,作为基础编程内容,相信大家都字符串都不陌生。我
- mybatis 传入null值解决前端传入两个值,如果其中一个为null时,很多时候我们都很困惑,明明传入的是null,为啥mybatis
- 在实际应用中,大家使用的密码可以说多种多样,但是无论有多少,其组成不遑是有可打印字符组成的,我们可以认为class CreateDic{ p
- 传感器简单的介绍一下传感器: 就是设备用来感知周边环境变化的硬件。Android中的传感器包含在传感器框架中,属于android.hardw
- 本文实例分析了C# SQlite操作方法。分享给大家供大家参考,具体如下:最近项目需求用C#保存一些数据,如此先总结一下。需要下载Sqlit
- 本期文章源码:GitHub一文彻底搞懂《并查集》!概念并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。
- 一:需求详情:OpenOffice.org 是一套跨平台的办公室软件套件,能在 Windows、Linux、MacOS X (X11)、和
- 实现效果:奔溃的线程侠:(单线程)主线程正在处理刷新图片的请求时,无法再接受其他请求,从而陷入阻塞的死循环状态。绘制图片import jav
- Bluetooth结构1、JAVA层frameworks/base/core/java/android/bluetooth/包含了bluet
- 我们通常在使用Java 调用脚本的时候,会使用 Runtime 类如:// 打开浏览器并访问 http://localh
- 主要区别在于是否延迟加载。load方法不会立即访问数据库,当试图加载的记录不存在时,load方法返回一个未初始化的代理对象。get方法总是立
- 本文研究的主要是Java虚拟机中gc日志的理解问题,具体如下。一、日志分析 理解GC日志是处理Java虚拟机内存问题的基本技能。通过在jav
- 一、问题定义:问下有一个数组,这些数组中的值都有自己的权重,怎样设计才能高效的优先取出权重高的数??例如:权重: 8 2&nbs
- Java注解(annotation)简单上手反射reflect:https://www.jb51.net/article/221282.ht
- 一、带时区的时间1.获取当前时间对象(带时区)import java.time.ZonedDateTime;public class dem
- 在Android Studio中,你可以很快速的使用Parcelable插件进行实体类的序列化的实现,使用该插件后,你的实体类可以快速的实现