一篇文章弄懂JVM类加载机制过程以及原理
作者:哪?吒 发布时间:2022-01-19 10:32:57
一、做一个小测试
通过注释,标注出下面两个类中每个方法的执行顺序,并写出studentId的最终值。
package com.nezha.javase;
public class Person1 {
private int personId;
public Person1() {
setId(100);
}
public void setId(int id) {
personId = id;
}
}
package com.nezha.javase;
public class Student1 extends Person1 {
private int studentId = 1;
public Student1() {
}
@Override
public void setId(int id) {
super.setId(id);
studentId = id;
}
public void getStudentId() {
System.out.println("studentId = " + studentId);
}
}
package com.nezha.javase;
public class Test1 {
public static void main(String[] args) {
Student1 student = new Student1();
System.out.println("new Student() 完毕,开始调用getStudentId()方法");
student.getStudentId();
}
}
有兴趣的小伙伴试一下,相信我,用System.out.println
标记一下每个函数执行的先后顺序,如果你全对了,下面的不用看了,大佬。
二、类的初始化步骤:
初始化父类中的静态成员变量和静态代码块 ;
初始化子类中的静态成员变量和静态代码块 ;
初始化父类的普通成员变量和代码块,再执行父类的构造方法;
初始化子类的普通成员变量和代码块,再执行子类的构造方法;
三、看看你写对了没?
package com.nezha.javase;
public class Person {
private int personId;
/**
* 第一步,走父类无参构造函数
*/
public Person() {
// 1、第一步,走父类无参构造函数
System.out.println("第一步,走父类无参构造函数");
System.out.println("");
setId(100);
}
/**
* 第三步,通过super.setId(id);走父类发方法
* @param id
*/
public void setId(int id) {
System.out.println("第三步,通过super.setId(id);走父类发方法~~~id="+id);
personId = id;
System.out.println("在父类:studentId 被赋值为 " + personId);
System.out.println("");
}
}
package com.nezha.javase;
public class Student extends Person {
private int studentId = 1;
/**
* 在走子类无参构造函数前,会先执行子类的普通成员变量初始化
* 第五步,走子类无参构造函数
*/
public Student() {
System.out.println("第五步,在走子类无参构造函数前,会先执行子类的普通成员变量初始化");
System.out.println("第六步,走子类无参构造函数");
System.out.println("");
}
/**
* 第二步,走子类方法
*
* 走完super.setId(id);,第四步,再回此方法
* @param id
*/
@Override
public void setId(int id) {
System.out.println("第二步,走子类方法~~id="+id);
// 3、第三步,走子类方法
super.setId(id);
studentId = id;
System.out.println("第四步,再回此方法,在子类:studentId 被赋值为 " + studentId);
System.out.println("");
}
/**
* 第六步,走getStudentId()
*/
public void getStudentId() {
// 4、打印出来的值是100
System.out.println("第七步,走getStudentId()");
System.out.println("studentId = " + studentId);
System.out.println("");
}
}
package com.nezha.javase;
public class Test1 {
public static void main(String[] args) {
Student1 student = new Student1();
System.out.println("new Student() 完毕,开始调用getStudentId()方法");
// 打印出来的值是100
System.out.println("#推测~~打印出来的值是100");
student.getStudentId();
}
}
下面通过图解JVM的方式,分析一下。
四、类的加载过程
1、加载
通过一个类的全限定名获取定义此类的二进制字节流;
将这个字节流代表的静态存储结构转化为方法区的运行时数据结构;
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
2、链接
(1)验证(Verify)
目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全;
主要包括四种验证:文件格式验证、元数据验证、字节码验证、符号引用验证;
(2)准备(Prepare)
为类变量分配内存并且设置该类变量的默认初始值;
这里不包含final修饰的static,因为final在编译的时候就会分配了,准备阶段会显示初始化;
这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到堆中;
(3)解析
将常量池内的符号引用转换为直接引用的过程
例如静态代码块、静态变量的显示赋值
事实上,解析操作往往会伴随着JVM在执行完初始化之后在执行
符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《Java虚拟机规范》的Class文件格式中。直接引用就是指- 向目标的指针、相对偏移量或一个间接定位到目标的句柄
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对常量池中的CONSTANT_Filedref_info、CONSTANT_Class_info、CONSTANT_Methodref_info等。
3、初始化
初始化阶段就是执行类构造器方法的过程;
此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来;
构造器方法中指令按语句在源文件中出现的顺序执行;
类构造器方法不同于类的构造器。构造器是虚拟机视角下的类构造器;
若该类具有父类,JVM会保证子类的类构造器执行前,父类的类构造器已经执行完毕;
虚拟机必须保证一个类的类构造器方法在多线程下被同步加锁;
五、类加载器的分类
JVM类加载器包括两种,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
所有派生于抽象类ClassLoader的类加载器划分为自定义类加载器。
1、启动类加载器(引导类加载器)
启动类加载器是使用C/C++语言实现的,嵌套在JVM内部;
Java的核心类库都是使用引导类加载器加载的,比如String;
没有父加载器;
是扩展类加载器和应用程序类加载器的父类加载器 ;
出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类 ;
2、扩展类加载器
java语言编写
派生于ClassLoader类
父类加载器为启动类加载器
从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的jar放在此目录下,也会自动由扩展类加载器加载
3、应用程序类加载器(系统类加载器)
java语言编写
派生于ClassLoader类
父类加载器为扩展类加载器
它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
该类加载器是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载的
通过ClassLoader.getSystemClassLoader()方法可以获得该类加载器
六、类加载器子系统的作用
类加载器子系统负责从文件系统或网络中加载class文件,class文件在文件开头有特定的文件标识。
ClassLoader只负责class文件的加载,至于它是否可以运行,则有执行引擎决定。
加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池的信息,可能还包括字符串字面量和数字常量(这部分常量信息是class文件中常量池部分的内存映射)。
七、总结
类的初始化步骤,这看似非常基础的话题,却实打实的难住了很多人,还总结了更为深入JVM的类的加载过程、类加载器的分类、类加载器的作用。
来源:https://blog.csdn.net/guorui_java/article/details/128710485


猜你喜欢
- 首先,我们需要增加用户对该脚本的执行权限,即 String cmdstring = "chmod a+x test.sh
- 前言最近测试反馈一个问题,某个查询全量信息的接口,有时候返回全量数据,符合预期,但是偶尔又只返回1条数据,简直就是“见鬼
- 在Android开发中,通过以下三种方法定时执行任务:一、采用Handler与线程的sleep(long)方法(不建议使用,java的实现方
- Word中设置水印时,可加载图片设置为水印效果,但通常添加水印效果时,会对所有页面都设置成统一效果,如果需要对每一页或者某个页面设置不同的水
- 目录一、图示二、链表的概念及结构三、单链表的实现四、完整代码的展示一、图示二、链表的概念及结构 链表是一种物理存储结构上非连续存储结构,数据
- 本文实例讲述了Android编程实现二维码的生成与解析。分享给大家供大家参考,具体如下:直接上代码,代码上面有具体的解析,并且提供jar供下
- android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件,有必要对它进行深入的了解。 一个最简单的屏幕触
- 简介springmvc对json的前后台传输做了很好封装,避免了重复编码的过程,下面来看看常用的@ResponseBody和@Request
- 通过eclipse修改web的url访问路径今天做SpringMVC 基础跳转网页的时候发现了一个问题,就是eclipse访问url路径的问
- Spring Boot+Vue 前后端分离项目架构项目流程:1. SpringBoot 后端项目1、新建一个 SpringBoot 工程,并
- 微信平台开放后倒是挺火的,许多第三方应用都想试下,毕竟可以利用微信建立起来的关系链来拓展自己的应用还是挺不错的,可以节约很多在社交方面的开销
- IEnumerable这个接口在MSDN上是这么说的,它是一个公开枚举数,该枚举数支持在非泛型集合上进行简单的迭代。换句话说,对于所有数组的
- 一、ArrayListArrayList是一个可以处理变长数组的类型,这里不局限于“数”组,ArrayList是一个泛型类,可以存放任意类型
- 在程序开发中通常有推送消息的需求,通常为短信服务,邮件,电话提醒。短信及电话提醒通常需要向运营商购买服务调用接口,比较麻烦。邮件信息推送也是
- java与JSON数据的转换实例详解JSON与JAVA数据的转换(JSON 即 JavaScript Object Natation,它是一
- @Profiles和@PropertySource根据环境切换配置文件使用@PropertySource注解加载配置文件,并制定解析配置文件
- 我相信现在绝大部分App几乎避免不了消息推送,其实原理还是使用了长连接,通过服务端将消息推给客户端。市面上也有不少三方库,例如极光、友盟、个
- 什么是分布式锁?在回答这个问题之前,我们先回答一下什么是锁。普通的锁,即在单机多线程环境下,当多个线程需要访问同一个变量或代码片段时,被访问
- app中肯定是少不了与用户交互的各种dialog,下面给大家介绍几种提示框的提示。一般创建一个对话框需要经过以下几步:1、创建AlertDi
- Android 中下拉菜单,即如html中的<select>,关键在于调用setDropDownViewResource方法,以