汇总一下之前写的小知识点
主要知识点:对象创建过程中的半初始化状态
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 与对象建立关联。
主要知识点:指令重排序问题
1、相关知识点
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.jolgroupId>
<artifactId>jol-coreartifactId>
<version>0.9version>
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;. 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
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
主要知识点:直接、间接寻址
间接 (句柄方式)
直接指针
主要知识点:线上 -> 线程本地 -> Eden -> Old
分配过程
- String
- String 对象不可变,值改变只是创建了一个新的对象,值存在于常量池,不用不会被销毁
- String 类被 final 修饰,不可以被继承
- StringBuffer
- StringBuffer 对象可变,主要根据构造方法创建,对象存在栈区,不用会被销毁
- Integer 是 int 的包装类,int 是 java 的基本类型
- Integer 默认值是 null,int 默认值是 0
- Integer 是指向对象,int 是直接存储数据值(Java 会对 -128 ~ 127 的数值进行缓存)
- Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型
- Array 大小固定,ArrayList 大小可以动态变化
- ArrayList 提供更多的方法和特性
- 值传递:在方法调用时,实参将自己的一份拷贝传给形参,在方法内对该参数修改不影响实参
- 引用传递:在方法调用的时候,实参将自己的地址传递给形参,在方法内,对该参数的改变就是对实参实际操作
- 基本类型是值传递,引用类型是引用传递
- byte、short、int、long、double、float、boolean、char
- 自动拆箱:将一个包装类对象赋值给类基本类型的数据
- 自动装箱:将一个基本类型的数据赋值给了包装类对象
- 两者可以大大简化基本类型变量和包装类对象之间的转换过程
- 计算机使用的是二进制,但是浮点数没法使用二进制进行精确,计算机给我们展现十进制或者说我们输入十进制计算机去处理,这都需要不断进行二进制与十进制的转换
- Lambda 表达式
- 参数列表 -> 实现逻辑
- 函数式接口
- 只包含一个抽象方法的接口,匿名实现类都可以用 Lambda 表达式来写
- 方法引用/构造器引用
- 方法引用
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
- 构造器引用
- ClassName::new
- Stream API
- 处理集合的关键抽象概念,提供了一种高效且易用的处理数据的方式
- Collection 是一种静态的内存数据结构,主要面向内存,存储在内存中,Stream 是有关计算的,主要面向 CPU,通过 CPU 实现计算
- 操作步骤
- 创建 Stream:数据源获取流(数组、集合)
- 中间操作:对数据源的数据进行处理
- 终止操作:执行中间操作,并产生结果
- 注意点:Stream 不会存储元素、不会改变源对象,返回一个新的结果 Stream,懒操作,需要结果的时候才会执行
- Optional 类
- 容器类,可以保存类型 T 的值,代表这个值存在,或者只保存 null,表示这个值不存在,可以避免空指针
- == 是判断是不是指向同一个内存空间,equals 是判断指向的内存空间的值是不是相同
- == 是对内存地址进行比较,equals 是对字符串内容进行比较
- == 指引用是否相同,equals 指值是否相同
- 如果不重写,使用的是本地方法,返回的是当前对象的内存地址
- 因为必须保证重写后的 equals 方法认定相同的两个对象拥有相同的 hashcode
- hashcode 方法重写原则就是保证 equals 方法认定为相同的两个对象拥有相同的 hashcode
- 比较的是引用类型的变量所指向的对象地址,和 == 一样
- 修饰类、方法、局部变量、成员变量
- 类不可被继承、方法不可被重写、变量赋值后不可修改
- 保证可见性和禁止指令重排序的一个关键字
- 被 volatile 修饰的变量存放在主内存中,修改的时候会同时修改主内存,读取的时候直接从主内存中读
- synchronized 可以加在方法或代码块中,lock 需要显性指定起始和结束位置
- synchronized 托管在 jvm 执行,lock 是 Java 写的控制锁代码
- synchronized 是悲观锁,lock 是乐观锁
- synchronized 是关键字,lock 是接口
- 静态方法:给对象加锁
- 成员方法:给实例对象加锁
- 方法覆盖:覆盖掉之前的方法(Override)
- 方法重载:相同的方法名,但是传递的参数不一样
- getDeclaredField 获取字段,setAccessible(true) 设置访问权限
- 内部类访问规则
- 内部类可以直接访问外部类中的成员,包括私有,因为内部类中持有一个外部类的引用(外部类名.this)
- 外部类要访问内部类需要创建对象
- 静态内部类
- 用 static 修饰,在访问限制上它只能访问外部类中的 static 成员变量或方法
- 成员内部类
- 普通内部类可以无条件访问外部类的所有成员属性和成员方法(包括 private 和 static)
- 内外部类拥有同名的变量或方法的时候,会隐藏外部的,如果需要访问外部的需要 外部类.this.成员 访问
- 局部内部类
- 定义在外部类的方法中,可以直接访问外部类的所有成员,但是不能随便访问局部变量,局部变量被 final 修饰才能访问
- 匿名内部类
- 内部类必须是继承一个类或实现一个接口
- 是一种书写规范,编译时类型安全检测机制
- 类加载机制
- 把描述类的数据从 class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型
- 双亲委派模型
- 接到类加载请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器完成类加载任务,就成功返回,父类都无法完成加载时才自己去加载
- 好处
- 无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的 Bootstrap ClassLoader 进行加载,所以在不同的环境中都是同一个类
- static:static 关键字修饰的内容都是静态的
- 方法、变量:不需要创建对象就能访问
- 代码块:创建时首先执行的代码,进行一些复杂的初始化工作
- 不能覆盖 private 和 static 方法,方法覆盖是运行时绑定,static 是编译时静态绑定,private 其他类无法访问这个方法
- 类是对象的模板,对象是类的实例。类只有通过对象才可以使用,而在开发之中应该先产生类,之后再产生对象。类不能直接使用,对象是可以直接使用的。
- 进程是资源分配的最小单位,线程是程序执行的最小单位,是资源调度的最小单位
- 进程有独立地址空间,线程是共享进程中的数据
- 一个进程可以有多个线程,一个线程只有一个进程
- 不同进程之间需要通信实现同步,不同线程之间需要协作同步
- 通俗:线程是儿子,进程是父亲,一个父亲可以有多个儿子,一个儿子只有一个父亲