从对象到类,Java中需要知道的这些东西
1. 对象的诞生
在平时的开发中,我们使用对象的时候,都是直接new一个临时变量然后进行各种逻辑赋值然后返回,但是你有没有想过一个对象在创建的过程中经历了什么呢,为什么创建时静态变量就已经赋完值了?这些似乎理所当然的操作其实里边还是有点东西的。
先说下一个对象诞生时的整个过程,一个对象的诞生一定会经过加载类的信息—>为即将诞生的对象分配内存空间—>将对象的成员变量赋上一个默认值—>捏脸(在头部设置对象的类信息和GC年龄)—>将对象的成员变量初始化为代码中写的值这五个流程,各个流程都有其重要的作用。
- 类加载:在第一次使用该对象的时候会进行类的加载工作,之后便不再加载。
- 分配空间:JVM给对象在堆上分配使用的空间,有两种方式。(题外话:对象不一定在堆上创建,感兴趣的话可以搜一下为什么)
- 指针碰撞:就是有序分配。维护一个指针,表示当前内存空闲的地址,下次分配空间的时候从这个位置开始,结束后更新指针位置。
- 空闲列表:就是随便分配。维护一个列表,记录哪些地址是空闲的,在进行空间分配的时候从列表中找到符合大小的地址进行分配,然后更新列表。
- 将分配到的空间都初始化为0值
- 设置对象头:设置对象头的信息,如GC年龄、"我是谁"。
- 调用init方法:init方法是在编译生成字节码的时候生成的,作用为初始化对象的成员变量(先初始化父类再初始化本身)。
没想到一个new操作竟然经历了这么多,想想确实有点任重而道远的味道。可能有人说了,"我用spring很多bean都不用new也能正常使用的哦,你是不是在骗我哦?"对于这样的提问,我只能说:

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

这个时候千万别慌,先深吸一口气,然后缓缓地说:"其实我面的是产品岗!"
在创建对象时第一步就是进行类的加载,但是类加载并不是一步操作,而是有相当多的流程的(不然你以为静态变量是用爱赋值的吗?),流程如下:
- 加载:将类信息加载到JVM中,并且在内存中生成一个Class对象。
- 验证:验证类的字节码是否符合当前JVM。
- 准备:将类的静态变量初始化为默认值。(static修饰的)
- 解析:将符号引用(一串字母)转为直接引用(内存地址引用)。
- 初始化:静态变量初始化为代码中的值。例如
static int a = 1,a=1是在这一步进行的,第3步执行为的时候a=0。clinit方法在此步骤执行,跟init方法类似,先初始化父类再初始化本身。- 使用
- 卸载
上面就是类的整个加载流程,你可能一点没记,没有关系,来个例子体会体会。写两个类,一个父类,一个子类,设置日志信息,然后调用查看结果。
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中需要知道的这些东西的更多相关文章
- 关于C++类定义中不能声明该类对象,而Java中可以的原因
相信接触过C++的人,在学习Java的过程当中,会遇到这样一个问题:在Java中常常会在类定义中声明一个该类的对象(例如Person类定义中声明一些叫parents之类的Person对象),但是在C+ ...
- 【Java】Java中的Collections类——Java中升级版的数据结构【转】
一般来说课本上的数据结构包括数组.单链表.堆栈.树.图.我这里所指的数据结构,是一个怎么表示一个对象的问题,有时候,单单一个变量声明不堪大用,比如int,String,double甚至一维数组.二维数 ...
- 不用static,巧用对象.方法调用java中的函数
先生成一个对象,用"对象.方法()"的方式调用. java中的main方法是静态的,用于程序的入口,在静态方法中无法调用非静态方法,只能调用静态方法.想调用静态方法的话就要先生成该 ...
- 持有对象:总结JAVA中的常用容器和迭代器,随机数 速查
JAVA使用术语“Collection”来指代那些表示集合的对象,JAVA提供的接口很多,首先我们先来记住他们的层次结构: java集合框架的基本接口/类层次结构 java.util.Collecti ...
- 在JavaScript 自定义对象来模拟Java中的Map
直接看代码: //模拟一个Map对象 function Map(){ //声明一个容器 var container={}; //定义一个put方法,向容器中存值 this.put=function(k ...
- 【译】Java中的对象序列化
前言 好久没翻译simple java了,睡前来一篇. 译文链接: http://www.programcreek.com/2014/01/java-serialization/ 什么是对象序列化 在 ...
- 判断java中两个对象是否相等
java中的基本数据类型判断是否相等,直接使用"=="就行了,相等返回true,否则,返回false. 但是java中的引用类型的对象比较变态,假设有两个引用对象obj1,obj2 ...
- JAVA中的String类(详解)
Java.lang.String类是final类型的,因此不可以继承这个类.不能修改这个类.String是一个类不属于基本数据类型. 可以从源码中看到,String是一个final类型. String ...
- java中String类为什么不可变?
在面试中经常遇到这样的问题:1.什么是不可变对象.不可变对象有什么好处.在什么情景下使用它,或者更具体一点,java的String类为什么要设置成不可变类型? 1.不可变对象,顾名思义就是创建后的对象 ...
随机推荐
- 使用numpy生成二维正态分布
参考资料: https://www.zhihu.com/question/39823283?sort=created https://www.zhihu.com/question/288946037/ ...
- SpringCloud(一)版本选择
Springboot版本 官网:https://spring.io/projects/spring-boot 在官网上 springboot已经更新到最新2.2.6 Spingcloud版本 官网:h ...
- MySQL的分页存储过程
-- 创建分页存储过程-- 1 判断存在即删除DROP PROCEDURE IF EXISTS popp;-- 2 创建万能分页CREATE PROCEDURE popp(_fls VARCHAR( ...
- 【漏洞二】Apache HTTP Server "httpOnly" Cookie信息泄露漏洞
[漏洞] Apache HTTP Server "httpOnly" Cookie信息泄露漏洞 [原因] 服务器问题 Apache HTTP Server在对状态代码400的默认错 ...
- JavaWeb网上图书商城完整项目--day03-1.图书模块功能介绍及相关类创建
1 前两天我们学习了user用户模块和图书的分类模块,接下来我们学习图书模块 图书模块的功能主要是下面的功能: 2 接下来我们创建对应的包 我们来看看对应的数据库表t_book CREATE TABL ...
- Redis:rdb和aof
由于redis的数据都直接存储在内存里,在服务器发生宕机时内存的数据会瞬间清空,那么必须要有重启时恢复数据的方法. redis通过持久化机制将数据存储到磁盘中从而在服务器重启时恢复数据,这篇文章主要简 ...
- Python3-datetime模块-日期与时间
官方文档 http://python.usyiyi.cn/translate/python_352/library/datetime.html 代码示例 from datetime import da ...
- SSM中保存数据出现415错误
服务器415错误 ssm框架的controller jsp页面 问题:页面出现415错误 原因:请求和响应类型不一致 分析: 先排除以下基本的环境配置 1.URL路径对应好,视图解析器配置好,cont ...
- 客官,来看看AspNetCore的身份验证吧
开篇 这段时间潜水了太久,终于有时间可以更新一篇文章了. 通过本篇文章您将Get: Http的一些身份验证概念 在AspNetCore中实现身份验证方案 JWT等概念的基础知识 使用Bearer To ...
- SpringBoot2.x入门:快速创建一个SpringBoot应用
前提 这篇文章是<SpringBoot2.x入门>专辑的第2篇文章,使用的SpringBoot版本为2.3.1.RELEASE,JDK版本为1.8. 常规的套路会建议使用Spring官方提 ...