JavaSE知识考点
JAVA中的几种基本数据类型是什么,各自占用多少字节?
- 4种整形 ( byte 1 short 2 int 4 long 8 )
- 2种浮点型(float 4 double 8)
- char型(char 2)
- boolean型(boolean 1)
String 类能被继承吗,为什么?
- String类是被fianl修饰的,而被final修饰的属性不可变,类不可被继承,方法不可以被复写
String,StringBuffer,StringBuilder 的区别
String是字符串常量
StringBuffer是字符串变量,同步处理线程安全的,效率低
- StringBuilder是字符串变量,异步处理线程不安全的,效率高
.ArrayList (扩容策略1.5倍)和 LinkedList 有什么区别
- 共同点:都是实现了List接口的容器类,都支持增删查改的操作,都是采用异步处理放式
- 底层实现:基于动态数组的数据结构与基于链表结构
- 随机访问的get与set方法,ArrayList效率高
- 对于删除与插入,LinkedList效率高
ArrayList与Vector的区别
出现版本:1.2 1.0
初始化策略:
- ArrayList:采用懒加载方式,在构造方法时并不初始化对象数组,只有在添加元素时才数组对象初始化为10
- Vector:在无参构造时就将对象数组初始化为10
扩容策略
- ArrayList:新数组扩容为原数组的1.5倍
- Vector:新数组寇蓉为原数组的2倍
遍历方式
ArrayList则则不支持
Vector支持较老的Enumeration
讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当 new 的时候,他们的执行顺序
- 父类静态变量>父类静态代码块>子类静态变量>子类静态代码块>父类构造块>父类构造方法>父类字段>子类构造块>子类构造方法>子类字段
- 父类静态数据 ,子类静态数据,父类构造函数 ,父类字段,子类构造函数,子类字段
用过哪些 Map 类,都有什么区别,HashMap 是线程安全的吗,并发下使用Map 是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
- HashMap
- 线程不安全,并且采用懒加载策略
- 存储方式:链表+数组(JDK8增添了红黑树)
- 树化逻辑:当桶中链表个数>=8,并且所有元素个数加起来超过64,结构转化为红黑树(解决问题:链表太长导致查找太慢问题,O(n)->O(logn),减少哈希碰撞)
- 默认容量(桶的数量):16
- 扩容策略:当超过容量最大值,不在扩充,没超过最大值扩充为原来的2倍,扩容后对元素进行rehash,要么保留在原桶中,要么在新桶中,HashMap
ConcurrentHashMap
线程安全:
JDK7中采用ReentrantLock保证Segment的线程安全
JDK8之后使用内建锁+CAS锁每个桶的头结点,使得锁进一步细粒度化
存储方式:
- JDK7中将16个桶设计改为16个Segment,每个Segment都有独立的一把锁,拆分后每个Segment相当于一个HashMap,
- JDK8使用与HashMap相同的存储结构:链表+数组+红黑树,并且引入懒加载模式(锁的力度更细:原来取得锁Segment一片区域到锁桶的头结点,内存占用更小)
JAVA8 的 ConcurrentHashMap 为什么放弃了分段锁,有什么问题吗?
- 锁力度更细
- 内存占有更小
有没有有顺序的 Map 实现类,如果有,他们是怎么保证有序
LinkedHashMap(记录插入的顺序):保留插入的顺序,输出顺序与输入顺序一致
TreeHashMap:默认升序的,按照key的内容排序,处理排序是按照Comparable接口完成,依靠comparaTo()方法完成,当然也可以自己复写Comparable接口中的comparaTo()方法,去自己规定排序方式
抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口么
反射的原理,反射创建类实例的三种方式是什么?
三种实例化对象方式
- 任何类的实例化对象可以通过Object类中的getClass()方法取得Class类对象
- “类.class”:直接根据某个具体的类来取得Class类的实例化对象。
- 使用Class类提供的方法:public static Class<?> forName(String className) throws ClassNotFoundException
反射中,Class.forName 和 ClassLoader 区别
final 的用途
- final变量是只读的,final变量一般与static关键字使用,作为常量使用
- final方法不可重写
- final类不能被继承
final好处:
- final关键字可以提高性能。JVM和Java应用都会缓存final变量
- final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销
- 使用final关键字,JVM会对方法,变量及类进行优化,更好地实现封装。
juc包下提供的四大工具类,
JVM
什么情况下会发生栈内存溢出
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverFlow异常
- 如果虚拟机在拓展栈时无法申请到足够的内存空间,则会抛出OOM异常
JVM 的内存结构,Eden 和 Survivor 比例
- Eden和Survivor(一个称为From,另外称为To区域)的比例是按照8 : 1分配的
JVM 内存为什么要分成新生代,老年代,永久代。新生代中为什么要分为 Eden和 Survivor。
都是属于堆
“标记-清除”算法是最基础的收集算法,分为“标记”和”清除”两个阶段:在标记后在完成统一回收所有被标记的对象。(效率问题:标记与清除效率不高 空间问题会:产生大量不连续的内存碎片)
MinorGC 新生代(复制算法):当对象进入初次进入Survivor区时就会将对象的年龄+1,当对象每熬过一次GC年龄就会+1,当到达默认值15时,就会将其移到老年代中。
- 为啥有两个Survivor区:通过两个Survivor区可以达到新生代无碎片的目的
MajorGC 老年代(标记-整理算法):存活时间比较久,或者需要占用大量连续内存空间的对象
- 针对老年代的特点,提出了一种称之为”标记-整理算法”。标记过程仍与”标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
FullGC 持久代
- 当前JVM垃圾回收机制采用“分代收集算法”,就是将Java堆分为新生代和老年代。
关于深浅拷贝
浅拷贝:拷贝出来的对象仍然保留原有对象的所有引用
- 问题:牵一发而动全身,只要任意一个原对象(或拷贝对象)中的引用发生改变,所有对象均会收到影响
深拷贝:拷贝出来的对象产生了所有引用新的对象
特点:修改任意一个对象,不会对其他对象产生影响
强引用,软引用,弱引用,虚引用
JVM 中一次完整的 GC 流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的 JVM参数
过程:
- 向Eden申请空间
- 空间足够
- 空间不足够
- 触发第一次Minor GC将所有存活对象放入Survivor from区,将对象年龄置位1,然后一次清理Eden区
- 再次发生Minor GC,扫描Eden和From区,将存活对象拷贝到To区,然后一次清理掉Eden区和From区
- 再次发生Minor GC时,会对Eden区域To区进行回收,存活对象移动到From区。每次移动都会将存活对象年龄+1
- 当存活对象年龄到达默认15时,存入老年代
- 当对象的大小过大时,直接进入老年代
- 当老年代空间不足时,会进行标记-整理算法 Full GC(老年代 GC或者Major GC)
- 当完全垃圾收集后,若Survivor和old区仍然无法存放Eden复制过来的部分对象时会导致JVM无法在Eden区为新对象创建内存区域,则出现”Out of memory错误”;
JVM主要参数:
-Xms:最小堆大小
-Xmx:最大堆大小
-Xss:设置每个线程的堆栈大小
-XX:NewSize=n:设置年轻代最小空间大小 MaxNewSize最大空间大小
-XX:PermSize=n:设置持久代最小空间大小 MaxPermSize最大空间大小
-XX:PretenureSizeThreshold:大于设定值的对象直接在老年代分配
-XX:MaxTenurngThreshold:设置垃圾最大年龄
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
- 向Eden申请空间
你知道哪几种垃圾收集器,各自的优缺
- 串行收集器:暂停所有应用的线程来工作;单线程
- 并行收集器:默认的垃圾收集器。暂停所有应用;多线程
- G1收集器:用于大堆区域。堆内存分隔,并发回收
- CMS收集器:多线程扫描,标记需要回收的实例,清除
垃圾回收算法的实现原理
- 标记-清除算法
- 复制算法
- 标记-整理算法
如何判断对象已死?
- 引用计数法
- 可达性分析算法
JVM 内存模型的相关知识了解多少,比如重排序,内存屏障,happens-before,主内存,工作内存等。
工作内存与主内存
:black_flag:
Happens-before原则(指令重排序)
:black_flag:
JMM内存三大特性(AVO)
原子性:基本数据类型的访问读写是具备原子性的,如若需要更大范围
的原子性,需要内建锁或lock体系的支持(i++,i– 等操作)
可见性:当一个线程修改了共享线程的值,其他线程立即得知值的修改。volatile,final,synchronized可以实现可见性。
有序性:若在本线程内观察,所有操作都是有序的;若在线程之外观察另外一个线程,所有操作都是无序的。
Volatile关键字(可见性,禁止指令重排(懒汉式单例的double-check))
保证此变量对所有线程的可见性(一个线程将修改变量值,其余线程可得知值的修改)
- 线程对变量进行修改之后,要立刻回写到主内存。
- 线程对变量读取的时候,要从主内存中读,而不是缓存。
禁止指令重排:为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则将程序编写顺序打乱。如果变量没有volatile修饰,程序执行的顺序可能会进行重排序。
不能保证原子性(举例:i++操作)
假如说线程A在做了i+1,但未赋值的时候,线程B就开始读取i,那么当
线程A赋值i=1,并回写到主内存,而此时线程B已经不再需要i的值了,
而是直接交给处理器去做+1的操作,于是当线程B执行完并回写到主
内存,i的值仍然是1,而不是预期的2。也就是说,volatile缩短了普通
变量在不同线程之间执行的时间差,但仍然存有漏洞,依然不能保证原
子性。
- 处理方法:加锁,使用原子类
关于double-check
:black_flag:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton{
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null)
instance=new Singleton();
}
}
retuen instance;
}
}
怎么打出线程堆栈信息
- JDK:jps
- Linux:ps -ef | grep | java
请解释如下 jvm 参数的含义
- -Xms:最小堆大小
- -Xmx:最大堆大小
- -Xss:设置每个线程的堆栈大小
- -XX:PermSize=n:设置持久代最小空间大小 MaxPermSize最大空间大小
- -XX:MaxTenurngThreshold:设置垃圾最大年龄
数据库
数据库隔离级别有哪些,各自的含义是什么,MYSQL 默认的隔离级别是什么
- 未提交读
- 已提交读(不可重复读)
- 可重复读
- 可串行读
Mysql默认是可重复读
什么是幻读与不可重读
- 脏读:当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
- 幻读:
- 不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。(即不能读到相同的数据内容)
MSQL 的索引原理,索引的类型有哪些,如何创建合理的索引,索引如何优化
聚集索引和非聚集索引的区别
聚簇索引:
- 表排列顺序:表记录的排列顺序与索引的排列顺序一致。
非聚簇索引:
表排列顺序:物理顺序和索引的顺序不一致
索引文件和数据文件分开,索引文件的叶子页只保存了主键值,要定位记录还要去查找相应的数据块
某个表有近千万数据,CRUD 比较慢,如何优化。
数据库中的ACID是什么?
A:atomic,原子性
C:consistent,一致性
事务开始及结束后,处于一致状态,是通过原子性来保证的
I:Isolation,隔离性
各个事物之间互不干扰
D:Durability,持久性
一个事物一旦提交,对数据库所做的改变都要记录到永久存储中去(例如:磁盘)
- 如何写 sql 能够有效的使用到复合索引。
- mysql 中 in 和 exists 区别
算法与数据结构
常用的排序算法,快排,归并、冒泡。 快排的最优时间复杂度,最差复杂度。
冒泡排序的优化方案。| 排序方式 | 最优情况 | 平均情况 | 最坏情况 | 空间复杂度 | 稳定性 |
| ——– | ——– | ——– | ————— | ———- | —— |
| 插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
| 希尔排序 | O(n) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
| 选择排序 | O(n^2) | O(m^2) | O(n^2) | O(1) | 不稳定 |
| 堆排序 | O() | O() | O() | O(1) | 不稳定 |
| 冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
| 快速排序 | O(nlogn) | O(nlogn) | 极端情况下O(n2) | O(logn) | 不稳定 |
| 归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
10 亿个数字里里面找最小的 10个
topk问题:建立一个数量为10的大堆,然后遍历数字,当有数字小于对顶元素时,替换堆顶元素,然后调整大堆
优化:可以把所有10亿个数据分组存放,比如分别放在1000个文件中。这样处理就可以分别在每个文件的10^6个数据中找出最大的10个数,合并到一起在再找出最终的结果
有 1 亿个数字,其中有 2 个是重复的,快速找到它,时间和空间要最优
2 亿个随机生成的无序整数,找出中间大小的值
多线程
多线程的几种实现方式,什么是线程安全
- 继承Thread类实现多线程
- Runnable接口实现多线程
- Callable接口实现多线程
线程安全(线程同步)就是多个线程在访问一个内存资源时,只有一个线程可以访问成功,
volatile(保证不了原子操作) 的原理,作用,能代替锁吗
- 保持线程之间的内存可见性
- 禁止指令重排
由于Java中的运算不是原子性操作,所以volatile在多线程模式下不能保证安全性,并不能替代锁
线程的生命周期状态图
创建 就绪 运行 阻塞 结束
- start()
- 就绪到运行通过系统调度,运行到就绪调用yield()方法(不会释放锁对象)
- 运行到阻塞 sleep()方法不会释放对象锁 join()方法会释放锁,
- 当sleep()结束后会,阻塞解除,回到就绪太
sleep 和 wait 的区别
- sleep是线程休眠,不会释放掉锁,是thread的静态方法
- wait是线程等待,会释放掉锁,是object中的方法
sleep 和 sleep(0)的区别
与线程的优先级有关
线程创建后,会进入线程调度队列。 线程调度队列通常按线程的优先级分成多个队列。 每个优先级都会有一个队列。
:ballot_box_with_check: sleep会使线程进入等待状态
调用sleep(0)的时候,如果调度器中不存在优先级>=该线程优先级时,该线程会继续运行。否则,线程会被放入到其优先级相应的队列尾部。就是相当调用sleep(0)时,通常会让该程序中优先级更高或者与其相等的线程获取更多的运行时间。
Lock 与 Synchronized 的区别
Synchronized
- 遇到异常或者执行完毕会自动释放锁
Lock(可重入锁,读写锁)
- 需要人为的unLock()释放锁资源
性能方面:在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但在资源竞争激烈的情况下ReetrantLock的性能更优
Atomic只能同步一个值,而且一段代码中只能出现一个Atomic变量,多一个同步无效
synchronized 的原理是什么,一般用在什么地方(比如加在静态方法和非静态方法的区别,静态方法和非静态方法同时执行的时候会有影响吗)
- synchronized底层采用对象锁(mointor)机制(监视器):在执行同步代码块前需要先执行monitorenter指令,退出时执行monitorexit指令,而且有多条monitorexit指令,要保证所获得的锁在正常路径,以及异常执行路径上都能够被解锁
- 静态方法中锁的是实例对象,非静态方法中锁的是类对象,同事执行不会有影响
- 静态方法
- 非静态方法中
用过哪些原子类,他们的原理是什么(CAS 如 AtomicInteger)