1. 对象的诞生

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

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

  1. 类加载:在第一次使用该对象的时候会进行类的加载工作,之后便不再加载。
  2. 分配空间:JVM给对象在堆上分配使用的空间,有两种方式。(题外话:对象不一定在堆上创建,感兴趣的话可以搜一下为什么)
    1. 指针碰撞:就是有序分配。维护一个指针,表示当前内存空闲的地址,下次分配空间的时候从这个位置开始,结束后更新指针位置。
    2. 空闲列表:就是随便分配。维护一个列表,记录哪些地址是空闲的,在进行空间分配的时候从列表中找到符合大小的地址进行分配,然后更新列表。
  3. 将分配到的空间都初始化为0值
  4. 设置对象头:设置对象头的信息,如GC年龄、"我是谁"。
  5. 调用init方法:init方法是在编译生成字节码的时候生成的,作用为初始化对象的成员变量(先初始化父类再初始化本身)。

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

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

2. 类的加载流程

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

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

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

  1. 加载:将类信息加载到JVM中,并且在内存中生成一个Class对象。
  2. 验证:验证类的字节码是否符合当前JVM。
  3. 准备:将类的静态变量初始化为默认值。(static修饰的)
  4. 解析:将符号引用(一串字母)转为直接引用(内存地址引用)。
  5. 初始化:静态变量初始化为代码中的值。例如static int a = 1,a=1是在这一步进行的,第3步执行为的时候a=0。clinit方法在此步骤执行,跟init方法类似,先初始化父类再初始化本身。
  6. 使用
  7. 卸载

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


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;
} }
// 启动,然后查看结果
public class main { public static void main(String[] args) {
Son son1 = new Son();
// 验证类只加载一次
Son son2 = new Son();
} }

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

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

3. 类加载器

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

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

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

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

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

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

破坏双亲委派模型

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

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

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

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

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

public class DriverManager {

	static {
// 加载下面的Driver
loadInitialDrivers();
println("JDBC DriverManager initialized");
} private static void loadInitialDrivers() {
// ...
// 这里就是加载子类Driver的方法,内部实现通过一个上下文加载器完成,方法体在下方
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> 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 <S> ServiceLoader<S> load(Class<S> service) {
// 简单来说就是将加载好的类信息放入上下文加载器中,然后这边从这个加载器拿
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
}

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

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

 

 

慢一点,再慢一点。

从对象到类,Java中需要知道的这些东西的更多相关文章

  1. 关于C++类定义中不能声明该类对象,而Java中可以的原因

    相信接触过C++的人,在学习Java的过程当中,会遇到这样一个问题:在Java中常常会在类定义中声明一个该类的对象(例如Person类定义中声明一些叫parents之类的Person对象),但是在C+ ...

  2. 【Java】Java中的Collections类——Java中升级版的数据结构【转】

    一般来说课本上的数据结构包括数组.单链表.堆栈.树.图.我这里所指的数据结构,是一个怎么表示一个对象的问题,有时候,单单一个变量声明不堪大用,比如int,String,double甚至一维数组.二维数 ...

  3. 不用static,巧用对象.方法调用java中的函数

    先生成一个对象,用"对象.方法()"的方式调用. java中的main方法是静态的,用于程序的入口,在静态方法中无法调用非静态方法,只能调用静态方法.想调用静态方法的话就要先生成该 ...

  4. 持有对象:总结JAVA中的常用容器和迭代器,随机数 速查

    JAVA使用术语“Collection”来指代那些表示集合的对象,JAVA提供的接口很多,首先我们先来记住他们的层次结构: java集合框架的基本接口/类层次结构 java.util.Collecti ...

  5. 在JavaScript 自定义对象来模拟Java中的Map

    直接看代码: //模拟一个Map对象 function Map(){ //声明一个容器 var container={}; //定义一个put方法,向容器中存值 this.put=function(k ...

  6. 【译】Java中的对象序列化

    前言 好久没翻译simple java了,睡前来一篇. 译文链接: http://www.programcreek.com/2014/01/java-serialization/ 什么是对象序列化 在 ...

  7. 判断java中两个对象是否相等

    java中的基本数据类型判断是否相等,直接使用"=="就行了,相等返回true,否则,返回false. 但是java中的引用类型的对象比较变态,假设有两个引用对象obj1,obj2 ...

  8. JAVA中的String类(详解)

    Java.lang.String类是final类型的,因此不可以继承这个类.不能修改这个类.String是一个类不属于基本数据类型. 可以从源码中看到,String是一个final类型. String ...

  9. java中String类为什么不可变?

    在面试中经常遇到这样的问题:1.什么是不可变对象.不可变对象有什么好处.在什么情景下使用它,或者更具体一点,java的String类为什么要设置成不可变类型? 1.不可变对象,顾名思义就是创建后的对象 ...

随机推荐

  1. C++ Primer Plus(一)

    完整阅读C++ Primer Plus 系统重新学习C++语言部分,记录重要但易被忽略的,关键但易被遗忘的. 预备 1.C++相对于C增加了最关键的两项,面向对象和范型编程. 处理数据 2.对于变量明 ...

  2. Arduino + RFID 读取 IC 卡 Arduino uno中获得RFID的UID 并通过串口转发RFID卡号

    RFID简介:射频识别即RFID(Radio Frequency IDentification)技术,又称无线射频识别,是一种通信技术,可通过无线电讯号识别特定目标并读写相关数据,而无需识别系统与特定 ...

  3. 力扣:二叉树着色游戏(DFS详解)

    有两位极客玩家参与了一场「二叉树着色」的游戏.游戏中,给出二叉树的根节点 root,树上总共有 n 个节点,且 n 为奇数,其中每个节点上的值从 1 到 n 各不相同. 游戏从「一号」玩家开始(「一号 ...

  4. c++_primer_第4版目录

    https://vdisk.weibo.com/s/BN_NALmbbBH01 第1章 快速入门1.1 编写简单的C++程序1.2 初窥输入/输出1.2.1 标准输入与输出对象1.2.2 一个使用IO ...

  5. 三角函数与缓入缓出动画及C#实现(图文讲解)

    日常经常能看到缓入缓出的动画效果,如: 1,带缓入缓出效果的滚动条: 2,带缓入缓出效果的呼吸灯: 像上面这种效果,就是用到了三角函数相关的知识,下面将从头开始一步步去讲解如何实现这种效果. 一.基础 ...

  6. 学习Linux必须掌握的一个知识-i节点

    linux文件系统是Linux系统的心脏部分,提供了层次结构的目录和文件.文件系统将磁盘空间划分为每1024个字节一组,称为块(也有用512字节为一块的,如:SCOXENIX).编号从0到整个磁盘的最 ...

  7. maven依赖冲突以及解决方法

    什么是依赖冲突 依赖冲突是指项目依赖的某一个jar包,有多个不同的版本,因而造成类包版本冲突 依赖冲突的原因 依赖冲突很经常是类包之间的间接依赖引起的.每个显式声明的类包都会依赖于一些其它的隐式类包, ...

  8. Flink1.10全文跟读翻译

    前言 突然的一个想法,我想把flink官网英语版全部看一遍翻译出来,并且带上自己的理解.自己不是什么大神,只是想这样做一遍,有人说不是有中文版,因为我自己想练习一下英语和对flink的理解吧!工作是一 ...

  9. 全宇宙首本 VS Code 中文书,来了!

    大家好!我是韩骏,VS Code 中文社区创始人,VS Code 的代码贡献者.2013 年,毕业于上海交通大学软件学院,现在是微软开发平台事业部的软件工程师.写过 20 多款 VS Code 插件, ...

  10. Python3笔记019 - 4.4 字典

    第4章 序列的应用 python的数据类型分为:空类型.布尔类型.数字类型.字节类型.字符串类型.元组类型.列表类型.字典类型.集合类型 在python中序列是一块用于存放多个值的连续内存空间. py ...