从对象到类,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.不可变对象,顾名思义就是创建后的对象 ...
随机推荐
- Homebrew命令总结
brew又叫homebrew,是macos上的一个包管理工具,能够在mac中方便的进行包管理,类似于ubuntu系统下的apt-get,记得自己第一次接触brew是为了在mac上安装一个独立绿色的视频 ...
- Anaconda 安装tensorflow出现错误
C:\ProgramData\Anaconda3\envs\python36tfgpu\lib\site-packages\tensorflow\python\framework\dtypes.py: ...
- 多语言工作者の十日冲刺<10/10>
这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 团队作业第五次--Alpha冲刺 这个作业的目标 团队进行Alpha冲刺--第十天(05.09) 作业正文 ...
- Linux 集群安装zookeeper
系统:CentOs 7 环境:jdk 8 Zookeeper 下载地址: http://www-eu.apache.org/dist/zookeeper/stable/ 上传至服务器并解压,本人放在 ...
- trollcave解题
这是第一次完整地进行模拟渗透,前前后后一共花了一天时间,花了点时间写了个writeup. 博主是个菜鸡,如果有大神看到,请轻喷...... writeup下载:https://hrbeueducn-m ...
- Windows安装 PyCharm
PyCharm是一种Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试.语法高亮.Project管理.代码跳转.智能提示.自动完成.单元测试.版本控制. ...
- web 基础(一) HTML
web 基础(一) HTML 与 XHTML 一.HTML介绍 HTML( Hyper Text Markup Language)指的是超文本标记语言,是用来描述网页的一种语言.它包括一系列标签.通过 ...
- SpringBoot--使用Spring Cache整合redis
一.简介 Spring Cache是Spring对缓存的封装,适用于 EHCache.Redis.Guava等缓存技术. 二.作用 主要是可以使用注解的方式来处理缓存,例如,我们使用redis缓存时, ...
- socketserver模块使用与源码分析
socketserver模块使用与源码分析 前言 在前面的学习中我们其实已经可以通过socket模块来建立我们的服务端,并且还介绍了关于TCP协议的粘包问题.但是还有一个非常大的问题就是我们所编写的S ...
- TreeMap实现