JVM架构入门之类加载器详解,助你深入JVM开发实战

2.2、链接

1. 验证

  • 确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。

  • 验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。

2. 准备

  • 类变量(静态变量)分配内存并设置类变量默认值–>0/false/null(不包含final修饰的static,final修饰的变量会显示初始化

  • 在初始化之前,若使用了类变量,用到的是默认值,并非代码中赋值的值

  • 不会实例变量分配初始化、类变量分配在方法区中,实例变量会随对象分配到java堆中

3. 解析

​ 虚拟机常量池内的符号引用替换为直接引用 ,类名、字段名、方法名—>具体内存地址或偏移量

2.3、初始化

1. 主动/被动使用

2. 初始化注意点

  • 类变量被赋值、实例变量被初始化

  • 每个类/接口被Java程序首次主动使用的时候才会被java虚拟机初始化

  • 从上到下初始化

  • 初始化一个类时,要求它的父类都已经被初始化了(除接口)

  • 当初始化一个类的时候并不会先初始化它实现的接口

  • 当初始化一个接口的时候,并不会初始化它的父接口

一个父接口并不会因为它的子接口或实现类的初始化而初始化,只有当首次使用其特定的静态变量时(即运行时常量,如接口中引用类型的变量)时才会初始化

3. 深入理解举例1

  • 对于静态字段来说,只有直接定义了该字段的类才会被初始化

  • 每个类在初始化前,必须先初始化其父类(除接口)

  • 追踪类的加载情况:-XX:+TraceClassLoading(+表示开启,-表示关闭)

  • 对于常量(这里指编译器确定的常量)来说,常量值在编译阶段会存入到调用它的方法所在的类的常量池中,本质上调用类没有直接引用到定义常量的类

  • 对于引用类型数组来说,其类型是由JVM在运行期间动态生成的,表示为[L+自定义类全类名(一维)这种形式

  • 准备阶段只是分配内存、赋默认值,初始化阶段才是真正的赋值(自己设定的值)

  • 初始化阶段是从上到小初始化赋值

public class ClassLoaderTest {

public static void main(String[] args) {

//单独测试下列语句

//1.

System.out.println(Child.str1);

/*输出

  • Parent static block

  • hello I’m Parent

*/

//2.

System.out.println(Child.str2);

/*输出

  • Parent static block

  • Child static block

  • hello I’m Child

*/

//3.

System.out.println(Parent.str3);

/*输出

  • hello I’m Parent2

  • */

//4.

System.out.println(Parent.str4);

/*输出

  • Parent static block

  • 78f59c0d-b91c-4e32-8109-dec5cb23aa13

  • */

//5.

Parent[] parents1=new Parent[1];

System.out.println(parents1.getClass());

Parent[][] parents2=new Parent[2][2];

System.out.println(parents2.getClass());

/*输出

  • class [Lcom.lx.Parent;

  • class [[Lcom.lx.Parent;

  • */

//6.

System.out.println(Singleton1.count1);

System.out.println(Singleton1.count2);

System.out.println(Singleton2.count1);

System.out.println(Singleton2.count2);

/*输出

  • 1,1,1,0

  • */

}

}

class Parent{

public static String str1 = “hello I’m Parent”;

public static final String str3 = “hello I’m Parent2”;

public static final String str4 = UUID.randomUUID().toString();

static {

System.out.println(“Parent static block”);

}

}

class Child extends Parent{

public static String str2 = “hello I’m Child”;

static {

System.out.println(“Child static block”);

}

}

class Singleton1 {

public static int count1;

public static int count2=0;

public static Singleton1 singleton1=new Singleton1();

public Singleton1() {

count1++;

count2++;

}

public Singleton1 getInstance(){

return singleton1 ;

}

}

class Singleton2 {

public static int count1;

public static Singleton2 singleton2=new Singleton2();

public Singleton2() {

count1++;

count2++;

}

public static int count2=0;

public Singleton2 getInstance(){

return singleton2 ;

}

}

4. 结果分析

  1. Child属于被动使用,Parent是主动使用,所以只会初始化Parent

  2. Child属于主动使用,所以会初始化Child,由于初始化的类具有父类所以先初始化父类

  3. Parent并没有被使用到,str3的值在编译期间就被存入CLassLoaderTest这个调用它的方法所在的类的常量池中,与Parent无关

  4. str4不是编译期间就能确定的常量,就不会放到调用方法类的常量池中,在运行时主动使用Parent类进而需要初始化该类

  5. 没有对Parent类初始化,引用数组类型并非Parent类,而是jvm动态生成的class [Lcom.lx.Parent

  6. 首先访问Singleton的静态方法–》Singleton是主动使用–》先初始化

  7. 第一种:准备阶段给count1,2分配空间默认值已经为0了,此时给类变量singleton初始化,调用构造方法,分别加一

  8. 第二种:同上,但是在给singleton初始化时,count2并未初始化,自增只是暂时的,随后就要对它初始化,所以在count2初始化前对他进行的操作时无效的。

类加载情况

情况1:

  • 加载object…类

  • 加载启动类

  • 加载父类

  • 加载子类

类的加载并非一定要该类被主动使用化

1588845311178

情况2:同上

情况3:

​ 自定义的类只加载了启动类(调用常量的方法所在的类)

​ 1588917876432

情况4:加载启动类以及Parent类

反编译结果

情况1:

1588918065497

情况2:类似1

情况3:没有引用到Parent类(定义常量的类)

1588917553379

1588917592415

情况4:类似1

5. 深入理解举例2

接口中定义的变量都是常量

常量又分为编译期常量和运行期常量,编译期常量的值在编译期间就可以确定,直接存储在了调用类的常量池中,所以访问接口中的编译期常量并不会导致接口的初始化,只有访问接口中的运行期常量才会引起接口的初始化。

父接口并不会因为子接口或是实现类的初始化而初始化,当访问到了其特定的静态变量时(即运行时常量,如接口中引用类型的变量)才会初始化

public class ClassLoaderTest2 {

public static void main(String[] args) {

System.out.println(new demo2().a);

System.out.println(“=====”);

System.out.println(son1.a);

new demo1().show();

System.out.println(demo1.str);

System.out.println(son1.b);

System.out.println(demo1.s);//System.out.println(son1.s);

/*输出

  • father2 singleton

  • 1

  • =====

  • 1

  • show method

  • string

  • father1 singleton

  • com.lx.father1$1@1b6d3586

  • */

}

}

interface father1{

int a=1;

void show();

String str=“string”;

Singleton1 s=new Singleton1(){

{

System.out.println(“father1 singleton”);

}

};

}

interface son1 extends father1 {

int b=0;

Singleton1 s1=new Singleton1(){

{

System.out.println(“son1 singleton”);

}

};

}

class demo1 implements father1{

@Override

public void show() {

System.out.println(“show method”);

}

}

class father2{

int a=1;

void show(){}

String str=“string”;

Singleton1 s=new Singleton1(){

{

System.out.println(“father2 singleton”);

}

};

}

class demo2 extends father2{

}

6. 结果分析

​ 第3行:子类初始化前必须初始化父类

​ 第5-8行:访问到编译时常量(已经存入了调用方法类的常量池中),不会导致初始化

​ 第9行: 访问了运行时常量,需要初始化定义该运行时常量的类

3、类加载器分类

img

一、java虚拟机自带的类加载器

  1. 启动类加载器(Bootstrap) ,C++所写,不是ClassLoader子类

1587996894484

  1. 扩展类加载器(Extension) ,Java所写

1587996931132

  1. 应用程序类加载器(AppClassLoader)。
  • 自定义类一般为系统(应用)类加载器加载

二、用户自定义的类加载器

import com.gmail.fxding2019.T;

public class Test{

//Test:查看类加载器

public static void main(String[] args) {

Object object = new Object();

//查看是那个“ClassLoader”(快递员把Object加载进来的)

System.out.println(object.getClass().getClassLoader());

//查看Object的加载器的上一层

// error Exception in thread “main” java.lang.NullPointerException(已经是祖先了)

//System.out.println(object.getClass().getClassLoader().getParent());

System.out.println();

Test t = new Test();

System.out.println(t.getClass().getClassLoader().getParent().getParent());

System.out.println(t.getClass().getClassLoader().getParent());

System.out.println(t.getClass().getClassLoader());

}

}

/*

*output:

  • null

  • null

  • sun.misc.Launcher$ExtClassLoader@4554617c

  • sun.misc.Launcher$AppClassLoader@18b4aac2

  • */

  • 如果是JDK自带的类(Object、String、ArrayList等),其使用的加载器是Bootstrap加载器;如果自己写的类,使用的是AppClassLoader加载器;Extension加载器是负责将把java更新的程序包的类加载进行
  • 输出中,sun.misc.Launcher是JVM相关调用的入口程序
  • Java加载器个数为3+1。前三个是系统自带的,用户可以定制类的加载方式,通过继承Java. lang. ClassLoader

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • Java加载器个数为3+1。前三个是系统自带的,用户可以定制类的加载方式,通过继承Java. lang. ClassLoader

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-kRFOGOZm-1714862527805)]

[外链图片转存中…(img-TTxS8JAr-1714862527806)]

[外链图片转存中…(img-bnRuoGko-1714862527806)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值