常量池
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
aa == bb; // true
// String.equals()被重写过,比较的是对象的值,如果equals方法未被重写,就等价于"==",比较的是引用
// 重写equals时,必须重写hashCode,因为如果hashcode值相同,就会调用equals()方法来检查hashcode相等的对象是否真的相同。
// 如果两者相同,就不会让其加入操作成功
// StringBuffer和StringBuilder都继承自接口AbstractStringBuilder
java中的常量池分为两种: 静态常量池和运行时常量池
静态常量池:字面量和符号引用量
- 字面量 :java语言层面常量的概念,如文本字符串,声明为final的常量值等
- 符号引用量:属于编译原理方面的概念,包括类和接口的全限定名、字段名称和描述符、方法名称和描述符
运行时常量池:JVM在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池
JVM虚拟内存分布:
- 程序计数器:JVM执行程序的流水线,存放一些跳转指令
- 本地方法栈:JVM调用操作系统方法所使用的栈
- 虚拟机栈:是JVM执行java代码所使用的栈
- 方法区:存放一些常量、静态变量、类信息等,可以理解成class文件在内存中的存放位置
- 虚拟机堆:JVM执行java代码所使用的堆,存放对象实例和数组
JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
线程共享的区域有: - 堆
- 方法区
- 直接内存(非运行时数据区的一部分)
线程私有的区域有: - 程序计数器
- 虚拟机栈
- 本地方法栈
HashCode和equals的相关规定:
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,对两个对象分别调用equals方法都返回true
- 两个对象有相同的hashcode值,它们不一定相等
- 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
- hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
java序列化
序列化:将对象写入到IO流中
步骤:
- 创建一个ObjectOutputStream输出流;
- 调用ObjectOutputStream对象的writeObject输出可序列化对象。
反序列化:从IO流中恢复对象(不会调用构造方法,反序列的对象是由JVM自己生成的对象,不通过构造方法生成)
步骤:
- 创建一个ObjectInputStream输入流;
- 调用ObjectInputStream对象的readObject()得到序列化的对象。
意义:序列化机制允许将实现序列化的Java对象转换为字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
实现方式:如果需要将某个对象保存到磁盘上或者通过网络传输,那么这个类应该实现Serializable接口或者Externalizable接口之一。
集合
List(对付顺序的好帮手):List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象
Set(注重独一无二的性质):不允许重复的集合,不会有多个元素引用相同的对象
Map(用Key来搜索的专家):使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象
Vector:类的所有方法是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间
ArrayList:不是同步的,所以在不需要保证线程安全时建议使用ArrayList,否则使用Vector
底层实现
- ArrayList:Object数组
- Vector:Object数组
- LinkedList:双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环)
- HashSet(无序,唯一):基于HashMap实现的,底层采用HashMap来保存元素
- LinkeHashSet:LinkedHashSet继承于HashSet,并且其内部是通过LinkedHashMap来实现的
- TreeSet(有序,唯一):红黑树
- HashMap:JDK1.8之前由数组+链表组成,数组是HashMap的主体,链表则是为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8),将链表转换为红黑树,以减少搜索时间
- LinkedHashMap:继承自HashMap
- Hashtable:数组+链表组成的
- TreeMap:红黑树(自平衡的排序二叉树)
红黑树:
二分查找树的特点:
- 左子树的值小于或等于它的根节点的值
- 右子树的值大于或等于它的根节点的值
- 左、右子树也分别是二叉排序树
二分查找树存在的问题:当有多个连续插入的值都小于(或大于)根节点时,会出现下述情况
红黑树的特点:
- 节点是黑色或红色
- 根节点是黑色
- 每个叶子节点都是黑色的空节点(NIL)
- 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
native
- 凡是带了
native
关键字的,说明java的作用范围达不到了,会去调用底层语言的库,例如Thread
中的start0
- 流程:代码运行到
native
那一行,会去调本地方法栈,然后通过本地方法栈找到本地方法接口(JNI
),然后再找到本地方法库 - JNI的作用:扩展Java的使用,融合不同的编程语言为Java所用
Tips:
- 如果有些字段(类成员变量)不想序列化时,可以使用关键字transient修饰,transient只能修饰变量,不能修饰类和方法
- java 中的 length 属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.
- java 中的 length() 方法是针对字符串说的,如果想看这个字符串的长度则用到 length() 这个方法.
- java 中的 size() 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看
- HashSet的底层是基于HashMap实现的
- 死锁的四个必须条件:
- 互斥条件:该资源任意一个时刻只由一个线程占用
- 请求与保持条件:一个进程因请求资源而阻塞时,对方已获得的资源保持不放
- 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
- 为什么调用start()方法时会执行run()方法,而不能直接调用run()方法:调用start方法方可启动线程并使线程进入就绪状态(真正的多线程),而run方法只是thread的一个普通方法调用,还是在主线程里执行的
- excute()和submit()的区别:
- excute()用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否
- submit()用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功
- 通过工厂类Excutors创建的三种线程池类型:(这三个方法实际上都是调用的ThreadPoolExecutor的构造方法)
- FixedThreadPool:返回一个固定线程数量的线程池。线程池中的线程数量始终不变
- SingleThreadExecutor:只有一个线程的线程池
- CachedThreadPool:可根据实际情况调整线程数量的线程池
- AQS的两种资源共享方式:Exclusive(独占)–>公平锁和非公平锁,Share(共享)–>多个线程可同时执行
- Java创建对象的过程:
-
类加载检查
-
分配内存
-
初始化零值
-
设置对象头:初始化零值后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象头中
-
执行init方法
- 类加载的过程
类加载器有:
- BootstrapClassLoader(启动类加载器,最顶层的加载类,负责加载%JAVA_HOME%/lib目录下的jar包和类或者被-Xbootclasspath参数指定的路径中的所有类)
- ExtensionClassLoader(扩展类加载器):主要负责加载目录%JAVA_HOME%/lib/ext目录下的jar包和类,或被java.ext.dirs系统变量指定的路径下的jar包
- AppClassLoader(应用程序类加载器):面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类