从对象到类,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.不可变对象,顾名思义就是创建后的对象 ...
随机推荐
- JavaSE基础之数组
数组 一.静态初始化 格式一 数据类型[] 变量名 = {元素1,元素2,元素3...}; 格式二 数据类型[] 变量名 = new 数据类型{元素1,元素2,元素3...}; 或者: 数据类型[] ...
- Java并发编程(05):悲观锁和乐观锁机制
本文源码:GitHub·点这里 || GitEE·点这里 一.资源和加锁 1.场景描述 多线程并发访问同一个资源问题,假如线程A获取变量之后修改变量值,线程C在此时也获取变量值并且修改,两个线程同时并 ...
- WeChair项目Beta冲刺(5/10)
团队项目进行情况 1.昨日进展 Beta冲刺第五天 昨日进展: 前后端并行开发,项目按照计划有条不絮进行 2.今日安排 前端:扫码占座功能和预约功能并行开发 后端:扫码占座后端逻辑开发,预约用座 ...
- ant+jmeter+jenkins接口自动化测试一
[Jmeter篇]jmeter+Ant+Jenkins接口自动化测试集成(一) 橙子探索测试发表于橙子探索测试订阅 90 一.简介 1.什么是ant? ant是构建工具,把代码从某个地方拿来,编译,再 ...
- 多线程高并发编程(12) -- 阻塞算法实现ArrayBlockingQueue源码分析(1)
一.前言 前文探究了非阻塞算法的实现ConcurrentLinkedQueue安全队列,也说明了阻塞算法实现的两种方式,使用一把锁(出队和入队同一把锁ArrayBlockingQueue)和两把锁(出 ...
- Python 简明教程 --- 12,Python 字典
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 代码写的越急,程序跑得越慢. -- Roy Carlson 目录 Python 字典是另一种非常实用 ...
- NodeMCU手把手入门:配置NodeMCU ESP8266开发板环境及点亮LED灯
之前一直在玩树莓派,最近实验室买了些NodeMCU就想着玩一玩,没想到挺有意思的.其实树莓派能实现的功能,它大部分也可以,价格比派也便宜不少,舍不得买派的同学可以先买这个开发板玩一玩. 本文主要介绍了 ...
- Security 10:权限管理
SQL Server 用于管理权限的TSQL命令有:GRANT用于授予权限,REVOKE 用于移除授予的权限,而DENY用于防止安全主体通过GRANT获得权限.但是,SQL Server的权限管理不是 ...
- pdfjs优化,实现按需加载,节省流量和内存
1 问题 当使用pdfjs来实现预览功能的时候,遇到了2个问题: 一是带宽占用过大,会下载整个pdf文件,这对部署在公网的应用来说,成本压力很大,因为云服务带宽是很贵的. 二是内存占用过大,一个80M ...
- day01微信小程序
一.基本概要 1.一个程序接口,可以集成很多功能,也就是在程序上再次开发 腾讯:微信+小程序 阿里:支付宝 +小程序 小程序的使用量很多 2.为什么要微信小程序? 1.微信用户群体大 2.容易推广, ...