Java 知道这些东西_不会写,从对象到类,Java中需要知道的这些东西

目录

1. 对象的诞生

2. 类的加载流程

3. 类加载器

破坏双亲委派模型

Top

1. 对象的诞生#

在平时的开发中,我们使用对象的时候,都是直接new一个临时变量然后进行各种逻辑赋值然后返回,但是你有没有想过一个对象在创建的过程中经历了什么呢,为什么创建时静态变量就已经赋完值了?这些似乎理所当然的操作其实里边还是有点东西的。

先说下一个对象诞生时的整个过程,一个对象的诞生一定会经过加载类的信息—>为即将诞生的对象分配内存空间—>将对象的成员变量赋上一个默认值—>捏脸(在头部设置对象的类信息和GC年龄)—>将对象的成员变量初始化为代码中写的值这五个流程,各个流程都有其重要的作用。

类加载:在第一次使用该对象的时候会进行类的加载工作,之后便不再加载。

分配空间:JVM给对象在堆上分配使用的空间,有两种方式。(题外话:对象不一定在堆上创建,感兴趣的话可以搜一下为什么)

指针碰撞:就是有序分配。维护一个指针,表示当前内存空闲的地址,下次分配空间的时候从这个位置开始,结束后更新指针位置。

空闲列表:就是随便分配。维护一个列表,记录哪些地址是空闲的,在进行空间分配的时候从列表中找到符合大小的地址进行分配,然后更新列表。

将分配到的空间都初始化为0值

设置对象头:设置对象头的信息,如GC年龄、”我是谁“。

调用init方法:init方法是在编译生成字节码的时候生成的,作用为初始化对象的成员变量(先初始化父类再初始化本身)。

没想到一个new操作竟然经历了这么多,想想确实有点任重而道远的味道。可能有人说了,”我用spring很多bean都不用new也能正常使用的哦,你是不是在骗我哦?”对于这样的提问,我只能说:

830fdde6404ea69f26ba600419ecd91a.png

大哥,开个玩笑,你又何必当真呢,来,你先把手上的砖头放下,我再给你扯一会儿。其实对于spring,其在项目启动的时候就已经进行了初始化,并且放在一个容器(IOC)中了,所以不是不需要只是工作提前做了(当然指的是单例模式);另外springBean的生命周期比我们手动new出来的要更复杂一些,但本质上只不过是加了一些流程,让其更具备扩展性,当然这都是题外话了(但是很重要)。

2. 类的加载流程#

一个对象创建的流程清楚了,但是某天面试官可能会说:”讲下类的加载过程“,这时候的你可能是这样的:

69cac206ee79e4139b84a919321aa487.png

这个时候千万别慌,先深吸一口气,然后缓缓地说:”其实我面的是产品岗!”

在创建对象时第一步就是进行类的加载,但是类加载并不是一步操作,而是有相当多的流程的(不然你以为静态变量是用爱赋值的吗?),流程如下:31256d2e3953983b51abc2a37bc26200.png

加载:将类信息加载到JVM中,并且在内存中生成一个Class对象。

验证:验证类的字节码是否符合当前JVM。

准备:将类的静态变量初始化为默认值。(static修饰的)

解析:将符号引用(一串字母)转为直接引用(内存地址引用)。

初始化:静态变量初始化为代码中的值。例如static int a = 1,a=1是在这一步进行的,第3步执行为的时候a=0。clinit方法在此步骤执行,跟init方法类似,先初始化父类再初始化本身。

使用

卸载

上面就是类的整个加载流程,你可能一点没记,没有关系,来个例子体会体会。写两个类,一个父类,一个子类,设置日志信息,然后调用查看结果。

Copy

import lombok.Data;

/**

* 父类

*/

@Data

public class Father{

private int age;

private String name;

public static String FATHER_STATIC = "FATHER_NAME";

static {

System.err.println("Father类的静态块:" + FATHER_STATIC);

}

public Father(){

System.err.println("Father的构造方法");

}

public Father(int age, String name){

this.age = age;

this.name = name;

}

}

import lombok.Data;

/**

* 子类(当前类)

*/

@Data

public class Son extends Father{

private int sex;

public static String SON_STATIC = "SON_NAME";

static {

System.err.println("Son类的静态块:" + SON_STATIC);

}

public Son(){

System.err.println("Son的构造方法");

}

{

// 验证方法块在构造方法前执行,无论位置在哪

System.err.println("Son的构造方法块");

}

public Son(int age, String name, int sex){

super(age, name);

this.sex = sex;

}

}

Copy

// 启动,然后查看结果

public class main{

public static void main(String[] args){

Son son1 = new Son();

// 验证类只加载一次

Son son2 = new Son();

}

}

可以想下这个小demo然后想下结果应该是什么,然后对比下方的结果图。

4c5eac4dd4b294ce92f4d4e1b3879f15.png

理解了这个demo,对象和类的流程应该就没啥问题了。其他的如使用Class.forName调用、只用到父类变量会初始化当前类吗之类的问题可以自己动手验证下,印象更加深刻哦。

3. 类加载器#

类加载流程理解了,类加载器还会远吗?不远了,就在下方了,不然就不起这个标题了。

那么类加载到底是干嘛的呢?废话,肯定是加载类的。

38b4770f3ceb970cc374225ef1d0326d.png

类加载器默认提供三种——BootStrap ClassLoader、ExtClassLoader和AppClassLoader,你也可以自己定义ClassLoader(只要继承ClassLoader类,然后重写loadClass方法就okay了)。

d5c1ef3dc7537cf40fb0f3215c4e6bcf.png

BootStrap ClassLoader:最顶层的类加载器,主要加载的是jre下lib目录下的rt.jar包,由于用的是C++编写,所以在Java中表现的形式为null;另外为了安全考虑Java在加载jar包的时候用的文件名,并且只加载java、sun等开头的类。

ExtClassLoader:第二层类加载器,范围为lib/ext目录下的包。

AppClassLoader:应用类加载器,范围为classpath下的jar包。

正常类加载器加载类的过程是这样的:

8602859e607ab73886f26844fb90ad5f.png

​ 这就是传说中的双亲委派模型了,大概意思就是类要先从父类加载器加载,如果父类加载器加载了,那么当前加载器就不再加载,这样可以保证用户在用的时候不会用到其他人写的重名或者恶意搞破坏的类;另外其实跟双亲没什么关系,只是名字这么叫(那你说parent不翻译成双亲应该怎么翻译嘛)。

破坏双亲委派模型#

双亲委派模型确实保证了Java库类的安全性,但是还会带来一些问题。

思考一个问题:如果我在ExtClassLoader甚至BootStrap ClassLoader加载的类里边需要引用下层的类,那我要怎么办呢,按照双亲委派模型,我能拿到的类是从上面流下来的,但是我要的下面的类。

所以,这个时候需要对这个模型进行一点改动,就是对于一些特定的类,其需要的一些类信息可以从子类加载器中获取,注意这里是特定的类,不是你想破坏就破坏的,只能是官方提供口子你才能破坏这个模型。

拿经典的DriverManager来说,DriverManager是rt.jar下的类,由BootStrap ClassLoader加载,但是其需要管理各个数据库厂商的Driver。e1cf1e66a97b1f98137afac47a47a230.png

从上图可以看出,如果严格遵照双亲委派模型是行不通的,这时候官方就在DriverManager中加了一个静态块来加载这些Driver类,这就是所谓的口子,来看下大概的代码。

Copy

public class DriverManager{

static {

// 加载下面的Driver

loadInitialDrivers();

println("JDBC DriverManager initialized");

}

private static void loadInitialDrivers(){

// ...

// 这里就是加载子类Driver的方法,内部实现通过一个上下文加载器完成,方法体在下方

ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);

Iterator driversIterator = loadedDrivers.iterator();

// ...

for (String aDriver : driversList) {

try {

println("DriverManager.Initialize: loading " + aDriver);

Class.forName(aDriver, true,

ClassLoader.getSystemClassLoader());

} catch (Exception ex) {

println("DriverManager.Initialize: load failed: " + ex);

}

}

}

public static ServiceLoader load(Class service){

// 简单来说就是将加载好的类信息放入上下文加载器中,然后这边从这个加载器拿

ClassLoader cl = Thread.currentThread().getContextClassLoader();

return ServiceLoader.load(service, cl);

}

}

大概的逻辑就是:使用静态块先加载Driver的实现类,这些Driver实现类的信息被放入一个上下文加载器中,只要从这个上下文加载器拿出来就okay了。

一开始可能都会被破坏双亲委派模型听起来这么牛的词汇给吓到,但是了解过后想来也不过如此,所以只要不停下来,道路就会不断延伸。

慢一点,再慢一点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值