汇总一下之前写的小知识点
目录
-
- 关于 Object o = new Object() 的几个问题
- 小知识点
-
- 1、String 和 StringBuffer 区别
- 2、int 和 Integer 区别
- 3、数组 Array 和 列表 ArrayList 的区别
- 4、什么是值传递和引用传递
- 5、Java 支持的数据类型有哪些,什么是自动拆箱、自动装箱
- 6、为什么会出现 4.0-3.6=0.4000000001 这种情况
- 7、Java8 新特性简单介绍
- 8、== 和 equals 区别
- 9、Object 如果不重写 hashCode,hashCode 如何计算出来
- 10、为什么重写 equals 还要重写 hashcode
- 11、如果对一个类不重写,它的 equals 方法是如何比较的
- 12、Java 里面的 final 关键字是怎么用的
- 13、介绍一下 volatile
- 14、关于 Synchronized 和 Lock
- 15、Synchronized 修饰静态方法和修饰成员方法锁的是什么
- 16、方法覆盖和方法重载的意思
- 17、如何通过反射获取对象私有字段值
- 18、内部类可以引用他包含类的成员吗,有什么限制
- 19、什么是范型
- 20、解释一下类加载机制,双亲委派模型,好处是什么
- 21、static 关键字意思,是否可以 override private 或者 static 方法
- 22、类和对象的区别
- 23、线程和进程
关于 Object o = new Object() 的几个问题
一、对象创建的过程
主要知识点:对象创建过程中的半初始化状态
1、以下面这段代码为例,看一下 Object 对象创建的过程
Object o = new Object();
2、通过 idea 里面的 jclasslib 插件可以获取对应的字节码
2.1、根据 jclasslib 获取到上面代码运行的字节码信息如下
3、通过字节码信息来分析对象的创建过程
- 首先根据第 1 行字节码大概能分析出这是 new 一个对象,对应的是 java.lang.Object 对象,可以大概总结出这一行是申请一块内存存储 new 出来的对象,这时候对象里面的成员变量就是一个默认初始值。
- 接着看第 3 行字节码,字面意思就是调用 java.lang.Object 的 init 方法,这里就是调用对象的构造方法进行初始化,进行初始化之后对象里面的成员变量就是正常的赋值。
- 第 4 行字节码的意思就是将变量 o 与对象建立关联。
二、DCL 单例到底需不需要加 volatile 修饰
主要知识点:指令重排序问题
1、相关知识点
- volatile:修饰代表线程间可见,同时禁止指令重排序
- 指令重排序:是 CPU 获取内存进行等待的时候同时执行指令,期间执行的顺序就变成乱序,也是因为 CPU 提高效率而乱的,就好像煮饭的同时可以做其他的
- 单例:某一个类只能在内存里 new 出一个对象
- DCL:Double check lock 双重检查锁
2、DCL 单例代码
public class DCL {
private static volatile DCL INSTANCE;
private DCL() {
}
public static DCL getInstance() {
if (INSTANCE == null) {
synchronized (DCL.class) {
if (INSTANCE == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
INSTANCE = new DCL();
}
}
}
return INSTANCE;
}
}
3、分析过程
- 从 对象创建的过程 问题中可以知道,在第 1 行指令执行半初始化的时候,如果第 3、4 行指令发生了指令重排序!!!
- 这时候 INSTANCE 才 new 到一半,还没有执行构造方法进行初始化,第二个线程拿到的就是一个半初始化对象。
- 这时候对象里面的成员变量还没有赋上对应的值,无法保证数据一致性。
- 结论:如果要保证数据一致性就需要加,不需要保证数据一致性可以不加 (但是一般单例都是有成员变量,所以一般都要加)
三、对象在内存中的存储布局
主要知识点:对象、数组在内存中的布局
1、查看布局需要引入对应的依赖包,这里引入的是 jol-cord
JOL:Java Object Layer
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
2、普通对象的内存布局
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/** 运行结果
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/
3、数组对象的内存布局
Object[] os = new Object[1];
System.out.println(ClassLayout.parseInstance(os).toPrintable());
/** 运行结果
[Ljava.lang.Object; object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) f5 22 00 f8 (11110101 00100010 00000000 11111000) (-134208779)
12 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
16 4 java.lang.Object Object;.<elements> N/A
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/
4、布局分析
普通对象
- 前面三部分 12 个字节都是 object header,分别是 markword、class pointer、instance data
- 第 12 个字节往后 4 个字节是下一个对象的补齐 padding
- 所以 Object o = new Object() 在内存中占用 16 个字节
数组对象
- 和普通对象相比 object header 多一个 4 字节的数组长度和 4 字节的 elements
四、对象头具体包括什么
主要知识点:markword、klasspointer
MarkWord
- 主要存储对象自身运行时数据
- 锁信息、GC 信息、Identity hashcode
- 测试方式:给对象加上锁,可以看到内存布局 object header 里面的 VALUE 发生了变化
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
/** 运行结果
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 88 a9 20 09 (10001000 10101001 00100000 00001001) (153135496)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/
KlassPointer
- 类型指针是对象指向类元数据的指针,通过这个指针知道这个对象是哪个类的实例
- 对象指针有时候是 4 字节、有时候是 8 字节,主要是 jvm 默认启动了压缩 +UserCompressedClassPointers,将 64 位压缩成了 4 字节
- 扩展:当内存超过 32G 的时候会膨胀成 8 字节,压缩不再起作用
- KlassPointer 寻址极限是 4byte * 8 byte = 32bit,即 2 的 32 次方个内存单元地址,每个对象长度也必须是 8 的整数倍,当大于 32G 内存地址的时候,无法进行寻找,所以压缩失败
五、对象怎么定位
主要知识点:直接、间接寻址
间接 (句柄方式)
- 指向一组指针,一个实例数据指针指向堆里面真正的对象,一个类型指针指向方法区的 class
- 优点:方便 GC,GC 复制的时候指向的变量不需要变
- 缺点:效率不高
直接指针
- 直接指向堆里面的类型数据指针,类型数据指针指向方法区的 class
- 优点:直接找效率高
- 缺点:每一次变化,指向的变量都需要改变
六、对象怎么分配
主要知识点:线上 -> 线程本地 -> Eden -> Old
分配过程
- new 对象的时候,首先判断是否能够放到栈上,栈一弹出 (pop) 就结束了
- 栈大小够、且不存在逃逸就可以分配到栈上,回收的时候不需要 GC 介入,效率高
- 不能放在栈上,再判断是否很大,很大直接放到老年代上,经过 Full GC、Major GC 进行对象回收结束
- 如果不大,放在线程本地缓冲区(TLAB),放不下就放到 Eden 区
- TLAB:Thread local allocation buffer,每个线程在 Eden 区都独占有一个额外的小空间,优先会放到这个地方,放不下就放到 Eden 区
- 在 Eden 区被一次 GC 之后清除了就结束,没被清除就进入 Survive 区,再经过 GC,如果年龄够大的话就进入 Eden 区,不够就进入另一个 Survive 区循环
小知识点
1、String 和 StringBuffer 区别
- String
- String 对象不可变,值改变只是创建了一个新的对象,值存在于常量池,不用不会被销毁
- String 类被 final 修饰,不可以被继承
- StringBuffer
- StringBuffer 对象可变,主要根据构造方法创建,对象存在栈区,不用会被销毁
2、int 和 Integer 区别
- Integer 是 int 的包装类,int 是 java 的基本类型
- Integer 默认值是 null,int 默认值是 0
- Integer 是指向对象,int 是直接存储数据值(Java 会对 -128 ~ 127 的数值进行缓存)
3、数组 Array 和 列表 ArrayList 的区别
- Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型
- Array 大小固定,ArrayList 大小可以动态变化
- ArrayList 提供更多的方法和特性
4、什么是值传递和引用传递
- 值传递:在方法调用时,实参将自己的一份拷贝传给形参,在方法内对该参数修改不影响实参
- 引用传递:在方法调用的时候,实参将自己的地址传递给形参,在方法内,对该参数的改变就是对实参实际操作
- 基本类型是值传递,引用类型是引用传递
5、Java 支持的数据类型有哪些,什么是自动拆箱、自动装箱
- byte、short、int、long、double、float、boolean、char
- 自动拆箱:将一个包装类对象赋值给类基本类型的数据
- 自动装箱:将一个基本类型的数据赋值给了包装类对象
- 两者可以大大简化基本类型变量和包装类对象之间的转换过程
6、为什么会出现 4.0-3.6=0.4000000001 这种情况
- 计算机使用的是二进制,但是浮点数没法使用二进制进行精确,计算机给我们展现十进制或者说我们输入十进制计算机去处理,这都需要不断进行二进制与十进制的转换
7、Java8 新特性简单介绍
- Lambda 表达式
- 参数列表 -> 实现逻辑
- 函数式接口
- 只包含一个抽象方法的接口,匿名实现类都可以用 Lambda 表达式来写
- 方法引用/构造器引用
- 方法引用
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
- 构造器引用
- ClassName::new
- Stream API
- 处理集合的关键抽象概念,提供了一种高效且易用的处理数据的方式
- Collection 是一种静态的内存数据结构,主要面向内存,存储在内存中,Stream 是有关计算的,主要面向 CPU,通过 CPU 实现计算
- 操作步骤
- 创建 Stream:数据源获取流(数组、集合)
- 中间操作:对数据源的数据进行处理
- 终止操作:执行中间操作,并产生结果
- 注意点:Stream 不会存储元素、不会改变源对象,返回一个新的结果 Stream,懒操作,需要结果的时候才会执行
- Optional 类
- 容器类,可以保存类型 T 的值,代表这个值存在,或者只保存 null,表示这个值不存在,可以避免空指针
8、== 和 equals 区别
- == 是判断是不是指向同一个内存空间,equals 是判断指向的内存空间的值是不是相同
- == 是对内存地址进行比较,equals 是对字符串内容进行比较
- == 指引用是否相同,equals 指值是否相同
9、Object 如果不重写 hashCode,hashCode 如何计算出来
- 如果不重写,使用的是本地方法,返回的是当前对象的内存地址
10、为什么重写 equals 还要重写 hashcode
- 因为必须保证重写后的 equals 方法认定相同的两个对象拥有相同的 hashcode
- hashcode 方法重写原则就是保证 equals 方法认定为相同的两个对象拥有相同的 hashcode
11、如果对一个类不重写,它的 equals 方法是如何比较的
- 比较的是引用类型的变量所指向的对象地址,和 == 一样
12、Java 里面的 final 关键字是怎么用的
- 修饰类、方法、局部变量、成员变量
- 类不可被继承、方法不可被重写、变量赋值后不可修改
13、介绍一下 volatile
- 保证可见性和禁止指令重排序的一个关键字
- 被 volatile 修饰的变量存放在主内存中,修改的时候会同时修改主内存,读取的时候直接从主内存中读
14、关于 Synchronized 和 Lock
- synchronized 可以加在方法或代码块中,lock 需要显性指定起始和结束位置
- synchronized 托管在 jvm 执行,lock 是 Java 写的控制锁代码
- synchronized 是悲观锁,lock 是乐观锁
- synchronized 是关键字,lock 是接口
15、Synchronized 修饰静态方法和修饰成员方法锁的是什么
- 静态方法:给对象加锁
- 成员方法:给实例对象加锁
16、方法覆盖和方法重载的意思
- 方法覆盖:覆盖掉之前的方法(Override)
- 方法重载:相同的方法名,但是传递的参数不一样
17、如何通过反射获取对象私有字段值
- getDeclaredField 获取字段,setAccessible(true) 设置访问权限
18、内部类可以引用他包含类的成员吗,有什么限制
- 内部类访问规则
- 内部类可以直接访问外部类中的成员,包括私有,因为内部类中持有一个外部类的引用(外部类名.this)
- 外部类要访问内部类需要创建对象
- 静态内部类
- 用 static 修饰,在访问限制上它只能访问外部类中的 static 成员变量或方法
- 成员内部类
- 普通内部类可以无条件访问外部类的所有成员属性和成员方法(包括 private 和 static)
- 内外部类拥有同名的变量或方法的时候,会隐藏外部的,如果需要访问外部的需要 外部类.this.成员 访问
- 局部内部类
- 定义在外部类的方法中,可以直接访问外部类的所有成员,但是不能随便访问局部变量,局部变量被 final 修饰才能访问
- 匿名内部类
- 内部类必须是继承一个类或实现一个接口
19、什么是范型
- 是一种书写规范,编译时类型安全检测机制
20、解释一下类加载机制,双亲委派模型,好处是什么
- 类加载机制
- 把描述类的数据从 class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型
- 双亲委派模型
- 接到类加载请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器完成类加载任务,就成功返回,父类都无法完成加载时才自己去加载
- 好处
- 无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的 Bootstrap ClassLoader 进行加载,所以在不同的环境中都是同一个类
21、static 关键字意思,是否可以 override private 或者 static 方法
- static:static 关键字修饰的内容都是静态的
- 方法、变量:不需要创建对象就能访问
- 代码块:创建时首先执行的代码,进行一些复杂的初始化工作
- 不能覆盖 private 和 static 方法,方法覆盖是运行时绑定,static 是编译时静态绑定,private 其他类无法访问这个方法
22、类和对象的区别
- 类是对象的模板,对象是类的实例。类只有通过对象才可以使用,而在开发之中应该先产生类,之后再产生对象。类不能直接使用,对象是可以直接使用的。
23、线程和进程
- 进程是资源分配的最小单位,线程是程序执行的最小单位,是资源调度的最小单位
- 进程有独立地址空间,线程是共享进程中的数据
- 一个进程可以有多个线程,一个线程只有一个进程
- 不同进程之间需要通信实现同步,不同线程之间需要协作同步
- 通俗:线程是儿子,进程是父亲,一个父亲可以有多个儿子,一个儿子只有一个父亲