Java基础
yatbfm

注:大多数内容参考了JavaGuide

Java

线程池可以设置的参数

https://javabetter.cn/interview/java-34.html#_28-%E7%BA%BF%E7%A8%8B%E6%B1%A0%E6%9C%89%E5%93%AA%E4%BA%9B%E5%8F%82%E6%95%B0
七个参数:
1.corePoolSize核心线程数,线程池中始终存活的线程数。
2.maximumPoolSize: 最大线程数,线程池中允许的最大线程数。
3.keepAliveTime: 存活时间,线程没有任务执行时最多保持多久时间会终止。
4.unit: 单位,参数keepAliveTime的时间单位,7种可选。
5.workQueue: 一个阻塞队列,用来存储等待执行的任务,均为线程安全,7种可选。
6.threadFactory: 线程工厂,主要用来创建线程,默认正常优先级、非守护线程。
7.handler拒绝策略,拒绝处理任务时的策略,4种可选,默认为AbortPolicy。

synchronized和可重入锁的区别

  • 用法不同:synchronized 可以用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用于代码块。
  • 获取锁和释放锁的机制不同:synchronized 是自动加锁和释放锁的,而 ReentrantLock 需要手动加锁和释放锁。
  • 锁类型不同:synchronized 是非公平锁,而 ReentrantLock 默认为非公平锁,也可以手动指定为公平锁。
  • 响应中断不同:ReentrantLock 可以响应中断,解决死锁的问题,而 synchronized 不能响应中断。
  • 底层实现不同:synchronized 是 JVM 层面通过监视器实现的,而 ReentrantLock 是基于 AQS 实现的。

可重入锁怎么实现

线程安全的集合有哪些

  1. Vector、HashTable

使用synchronized修饰方法保证线程安全
效率低

  1. ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet

除了1.8的ConcurrentHashMap大多都是用Lock锁

ConcurrentHashMap怎么保证线程安全

https://javaguide.cn/java/collection/java-collection-questions-02.html#concurrenthashmap-%E5%92%8C-hashtable-%E7%9A%84%E5%8C%BA%E5%88%AB
JDK1.7 的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组+链表/红黑二叉树。
在 JDK1.7 的时候,ConcurrentHashMap 对整个桶数组进行了分割分段(Segment,分段锁),每一把锁只锁容器其中一部分数据(下面有示意图),多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。
image.png

到了 JDK1.8 的时候,ConcurrentHashMap 已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本。
image.png

CAS是什么

https://javaguide.cn/java/basis/unsafe.html#cas-%E6%93%8D%E4%BD%9C
CAS 即比较并替换(Compare And Swap),是实现并发算法时常用到的一种技术。CAS 操作包含三个操作数——内存位置、预期原值及新值。执行 CAS 操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。我们都知道,CAS 是一条 CPU 的原子指令(cmpxchg 指令),不会造成所谓的数据不一致问题,Unsafe 提供的 CAS 方法(如 compareAndSwapObject、compareAndSwapInt、compareAndSwapLong)底层实现即为 CPU 指令 cmpxchg 。

存在问题:如果在更新过程中,另一个线程也修改了这个变量,但是修改后的值和原值一样,这样就不会发现修改过,可以采用添加版本号或者时间戳的方式避免。

volatile

  • 1.保证内存可见性
    • 当一个被volatile关键字修饰的变量被一个线程修改的时候,其他线程可以立刻得到修改之后的结果。当一个线程向被volatile关键字修饰的变量写入数据的时候,虚拟机会强制它被值刷新到主内存中。当一个线程读取被volatile关键字修饰的值的时候,虚拟机会强制要求它从主内存中读取
  • 2.禁止指令重排序
    • 指令重排序是编译器和处理器为了高效对程序进行优化的手段,cpu 是与内存交互的,而 cpu 的效率想比内存高很多,所以 cpu 会在不影响最终结果的情况下,不等待返回结果直接进行后续的指令操作,而 volatile 就是给相应代码加了内存屏障,在屏障内的代码禁止指令重排序。

Java的内存区域

Java的gc逻辑

https://juejin.cn/post/7123853933801373733
https://javaguide.cn/java/jvm/jvm-garbage-collection.html

内存分配和回收原则

首先分配对象到新生代的Eden区,再次分配对象时仍旧是优先分配到Eden区,如果发现无法满足,则进行一次Minor gc(也叫Young gc),将Eden区的对象复制到S区,如果S区无法满足条件则根据空间分配担保将对象复制到老年代,一般老年代可以满足条件,如果不满足条件则进行Full gc,对新生代和老年代进行一次gc
较大的对象(大对象就是需要大量连续内存空间的对象(比如:字符串、数组))也会直接进入老年代,避免将大对象放入新生代产生频繁的gc操作。具体什么是大对象,不同垃圾回收器会有不同的设置:

  • G1垃圾回收器可以通过设置参数来确定大对象的大小。
  • Parallel Scavenge 垃圾回收器,默认情况没有阈值,是根据当前堆内存的情况和历史数据动态决定的。

jvm会给每个对象设置一个年龄,新生代的对象经历一次Minor gc存活下来时,会将年龄增加1,当年龄达到一定阈值时(默认为15,但也和具体的垃圾收集器或者参数设置有关),也会加入到老年代中。

死亡对象判断方法

  1. 引用计数法

给对象中添加一个引用计数器:

  • 有一个地方引用了这个对象时,计数器加1
  • 引用失效则减1
  • 计数器为0则表示对象不再被使用

该方法实现简单效率高,但是很难解决对象之间循环引用的问题(循环引用的情况下两个对象的计数器都不为0)。

  1. 可达性分析算法

通过一系列被称为GC Roots的对象作为起点,从这些节点开始向下搜索,节点所走过的路被称为引用链,当一个对象到GC Roots没有任何引用链的话,则证明此对象是不可用的,需要被回收。
哪些对象可以作为GC Roots呢:

  • 虚拟机栈(栈帧中的局部变量表)中引用的对象
  • 本地方法栈(native方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象
  • JNI(Java Native Interface)引用的对象
  1. 引用类型总结
    1. 强引用:大部分引用都是强引用,垃圾回收器绝不会回收,当内存空间不足时会抛出OOM异常也不会回收强引用的对象。
    2. 软引用:如果内存足够,则不会回收,如果内存不足则会回收软引用对象的内存。软引用可以用来实现内存敏感的高速缓存。
    3. 弱引用:只有短暂的生命周期,无论内存是否充足,只要发现了弱引用的对象就会进行回收。
    4. 虚引用:虚引用并不会决定任何对象的生命周期,对象如果只有虚引用,就和没有引用一样,任何时候都会被回收。

虚引用必须和引用队列联合使用,而软引用和弱引用不必须和引用队列联合使用。

  1. 如何判断废弃常量

假如在字符串常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量"abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池了。

  1. 如何判断无用类

类需要同时满足下面 3 个条件才能算是 “无用的类”

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。

垃圾收集算法

  1. 标记清除算法

分为标记和清除两个阶段:首先标记出所有不回收的对象,标记完成后统一回收掉所有没有被标记的对象。
是最基础的算法,后续算法都是对他的改进,这种算法会有两个明显的问题:

  • 效率问题:标记和清除两个过程效率都不高
  • 空间问题:标记清楚后会产生大量不连续的内存碎片
  1. 标记整理算法

标记过程和标记清除算法一样,后续步骤不是直接对可回收对象回收,而是让所有存活的对象向前一端移动,然后直接清理掉端边界以外的内存。由于多了整理的步,效率不高,适合老年代这种垃圾回收频率不是很高的场景。

  1. 复制算法

解决效率和内存碎片问题。将内存分为大小相同的两块,每次使用其中一块,当一块使用完后,将存活的对象复制到另一块,然后清理使用的空间。
存在两个问题:

  • 可用内存变小
  • 不适合老年代,如果存活对象数量较多,复制性能会变差。
  1. 分代收集算法

根据对象存活周期将内存分为几块。一般将Java堆分为新生代和老年代。新生代中每次收集都会有大量对象死去,所以可以选择标记复制算法,只需要少量对象的复制成本就可以完成垃圾收集;而老年代的对象存活几率比较高,而且没有额外的空间担保,所以可以使用标记清除或者标记整理算法。

垃圾收集器

垃圾收集器垃圾收集算法的具体实现。到目前为止还没有最好的垃圾收集器出现,只能根据具体的应用场景选择合适的垃圾收集器
JDK默认的垃圾收集器:

  • 1.8:Parallel Scavenge(新生代) + Parallel Old(老年代)
  • 9 - 20: G1
  1. Parallel Scavenge收集器

Parallel Scavenge收集器是使用标记复制算法的多线程收集器,该收集器的关注点在于吞吐量(高效率使用CPU),吞吐量指CPU中用于运行用户代码时间与CPU总消耗时间的比值。该收集器提供了很多参数供用户找到合适的停顿时间或最大吞吐量,也可以使用收集器的自适应调节策略。

  1. Parallel Old收集器

Parallel Scavenge的老年代版本,使用多线程标记整理算法,同样注重吞吐量。

  1. G1收集器

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器大容量内存的机器. 以极高概率满足GC 停顿时间 要求的同时,还具备高吞吐量性能特征。
有以下特征:

  • 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
  • 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
  • 空间整合:与 CMS 的“标记-清除”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
  • 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒。

G1收集器大致分为几个步骤:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收


G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

HashMap的原理

https://javaguide.cn/java/collection/hashmap-source-code.html
jdk1.8之前由 数组+链表 组成,链表主要为了解决哈希冲突(拉链法)。在jdk1.8之后解决哈希冲突首先使用链表,当链表长度大于等于阈值(默认为8)时,首先判断数组长度,如果数组长度小于64,会选择扩容,如果数组长度大于64,会将链表转成红黑树,减少搜索的时间。
刚刚创建HashMap对象时数组长度为0,当第一次插入数据时数组长度设置为16。
首先将对象的hashCodehashCode无符号右移16位的结果做异或(添加一些扰动)获得实际的哈希值(设为hash),然后将hash和数组长度-1做与运算(等同于取模,因为数组长度为2的n次幂)得到存放的数组索引位置。
当键值对的数量size超过阈值(数组长度*负载因子(默认是0.75))就会对数组进行扩容。

悲观锁和乐观锁的区别、场景

https://javaguide.cn/java/concurrent/optimistic-lock-and-pessimistic-lock.html

双亲委派机制

类加载过程

类加载器详解

由 Hexo 驱动 & 主题 Keep
访客数 访问量