JAVA知识点总结
文章目录
JVM
- JVM是运行在操作系统之上的,他与硬件没有直接交互。
- 运行过程:Java 源文件->编译器->.Class文件->JVM->机器码
- java可以跨平台的原因是每一种平台的解释器是不同的,但是实现的虚拟机是相同的。
线程
Hotspot JVM 中的 Java 线程与原生操作系统线程有直接的映射关系。
一、JVM内存
JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA 堆、方法区】、直接内存。
4. 线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁。
5. 线程共享区域随虚拟机的启动/关闭而创建/销毁。
6. 直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用。
程序计数器(线程私有)
一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。
虚拟机栈(线程私有)
是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
本地方法区(线程私有)
本地方法栈则为Native 方法服务
堆(Heap-线程共享)-运行时数据区
是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。
方法区/永久代(线程共享)
用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据.
二、垃圾回收与算法
- 引用计数法:对象如果没有任何与之关联的引用,即他们的引用计数都不为 0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。
- 可达性分析:果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。
- 标记清除算法(Mark-Sweep):最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。
- 复制算法(copying):按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。
- 标记整理算法(Mark-Compact):标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
- 分代收集算法:分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
三、JAVA 四中引用类型
- 强引用:把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。
- 软引用:软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,根据内存决定是否回收。软引用通常用在对内存敏感的程序中。
- 弱引用:弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,只使用了弱引用的对象来说,不管JVM的内存,都会被回收。
- 虚引用:虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚
引用的主要作用是跟踪对象被垃圾回收的状态。
四、GC 分代收集算法 VS 分区收集算法
分代收集算法
当前主流 VM 垃圾收集都采用”分代收集”(Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为几块, 如 JVM 中的 新生代、老年代、永久代,这样就可以根据各年代特点分别采用最适当的 GC 算法。
分区收集算法
分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的好处是可以控制一次回收多少个小区间 , 根据目标停顿时间, 每次合理地回收若干个小区间(而不是整个堆), 从而减少一次 GC 所产生的停顿。
GC垃圾收集器
- Serial 垃圾收集器(单线程、复制算法)Serial(英文连续)是最基本垃圾收集器,使用复制算法
- ParNew 垃圾收集器(Serial+多线程)ParNew 垃圾收集器其实是 Serial 收集器的多线程版本
- Parallel Scavenge 收集器(多线程复制算法、高效)Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃圾收集器,它重点关注的是程序达到一个可控制的吞吐量
- Serial Old 收集 单线程标记整理算法 Serial Old 是 Serial 垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在 Client 默认的 java 虚拟机默认的年老代垃圾收集器。
- Parallel Old 收集器(多线程标记整理算法)Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,
- CMS 收集器(多线程标记清除算法)Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,它使用多线程的标记-清除算法。
- G1 收集器Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器,G1 收集器两个最突出的改进是:1. 基于标记-整理算法,不产生内存碎片。2. 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率。
五、JAVA IO/NIO
- 阻塞 IO 模型
- 非阻塞 IO 模型
- 多路复用 IO 模型
- 信号驱动 IO 模型
- 异步 IO 模型
JAVA NIO
NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector
六、死锁的必要条件
互斥条件:同一资源同时只能由一个线程读取
不可抢占条件:不能强行剥夺线程占有的资源
请求和保持条件:请求其他资源的同时对自己手中的资源保持不放
循环等待条件:在相互等待资源的过程中,形成一个闭环
七、什么是AQS锁?
AQS(AbstractQueuedSynchronizer)是Java中提供的一个用于构建锁和其他同步组件的框架。它是java.util.concurrent.locks包中的一个类,是实现同步器的基础。AQS使用一个int类型的变量来表示同步状态,并通过一个FIFO队列来管理那些获取同步状态失败的线程。
AQS的主要特点包括:
同步状态(State):AQS内部有一个volatile类型的int变量来表示同步状态,这个状态用来控制线程的获取和释放。
节点和队列:当线程尝试获取同步状态而失败时,AQS会将这些线程封装成节点(Node)并放入队列中。这个队列是一个FIFO的双向队列,用于管理等待获取锁的线程。
获取和释放方法:AQS定义了两种主要的方法来管理同步状态:acquire(获取)和release(释放)。子类通过重写这些方法,可以实现不同的同步机制。
独占和共享:AQS支持两种同步模式。独占模式(如ReentrantLock)意味着每次只有一个线程可以持有锁;共享模式(如Semaphore、ReadWriteLock)允许多个线程同时获取锁。
子类实现:通过继承AQS并实现它的方法,可以非常方便地实现各种同步组件。例如,ReentrantLock、CountDownLatch、Semaphore、ReadWriteLock等都是基于AQS的。
有线程想获取锁时,会以CAS的形式将state变为1,CAS成功后便将加锁线程设为自己。当其他线程来竞争锁时会判断state是不是0,不是0再判断加锁线程是不是自己,不是的话就把自己放入阻塞队列。这个阻塞队列是用双向链表实现的
八、有哪些常见的AQS锁
基于 Java 的 AbstractQueuedSynchronizer
(AQS) 框架,有几种常见的锁和同步器。这些锁和同步器通过继承和实现 AQS 提供的方法来实现它们的同步功能。以下是一些常见的基于 AQS 的锁和同步组件:
-
ReentrantLock(可重入锁):
- 它是一种排他的锁,允许锁的持有者多次获取它,而不会造成死锁。
- 提供了公平和非公平的锁定机制。非公平锁就是会通过两次CAS去抢占锁,公平锁会按队列顺序排队
-
ReadWriteLock(读写锁):
- ReadWriteLock 其中的
ReentrantReadWriteLock
是一种支持两种类型的锁:读锁和写锁。 - 读锁可以由多个读线程同时持有,而写锁是独占的。
- ReadWriteLock 其中的
-
CountDownLatch(倒计时门栓):
- 允许一个或多个线程等待直到一系列操作在其他线程中完成。
应用场景:启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行
- 允许一个或多个线程等待直到一系列操作在其他线程中完成。
-
Semaphore(信号量):
- 主要用于控制对有限资源的访问。
- 可以是公平的也可以是非公平的。
应用场景:允许多个线程访问某个临界资源时,如上下车,买卖票
-
CyclicBarrier(循环屏障):
- 它允许一组线程互相等待,直到所有线程都到达了一个公共屏障点,然后这些线程才会继续执行。
应用场景:多线程计算数据,最后合并计算结果的应用场景
- 它允许一组线程互相等待,直到所有线程都到达了一个公共屏障点,然后这些线程才会继续执行。
-
FutureTask:
- 用于表示异步计算的结果。
- 它提供了一个完成操作,并能获取计算结果的方法。
这些工具类大大简化了并发编程的复杂性,使开发人员能够更专注于业务逻辑的实现,而不是在并发控制上花费太多精力。每种类型的锁或同步器都有其特定的应用场景,因此在实际使用中应根据具体需求选择合适的类型。
九、yield()和join()
在 Java 的多线程编程中,yield()
和 join()
是两个常用的方法,它们用于控制线程的执行。它们的作用和使用场景各不相同。
yield()
yield()
是一个静态方法,定义在 Thread
类中。当一个线程调用 yield()
方法时,它给出了对当前执行的线程处理器资源的控制权,暗示调度器当前线程愿意放弃其当前的时间片,以便其他线程可以运行。使用 yield()
的目的是为了提高线程间的相互作用和提高效率。
- 特点:
- 它只是一个提示,并不保证会产生任何作用。
yield()
后,当前线程仍然可能会继续执行。- 它通常用于调试或测试目的,或者当希望线程更加平等地共享处理器资源时。
join()
join()
方法定义在 Thread
类中。当一个线程执行 join()
方法时,调用它的线程将被挂起,直到 join()
方法所属的线程结束运行。
-
作用:
join()
用于让其他线程等待,直到当前线程完成其运行。- 它允许一个线程在另一个线程结束后再继续执行。
-
使用场景:
- 当一个线程需要等待另一个线程完成某些准备工作后才能继续运行时,可以使用
join()
。 - 例如,在主线程中,如果需要等待一个子线程的初始化或结果计算完成后再继续执行,就可以在主线程中对该子线程调用
join()
。
- 当一个线程需要等待另一个线程完成某些准备工作后才能继续运行时,可以使用
总结一下,yield()
是一个线程提示,表示当前线程愿意放弃其当前时间片,但不保证会产生任何效果;而 join()
是一个线程控制方法,用于让一个线程等待另一个线程完成其运行。这两个方法在多线程编程中用于不同的目的和场景。
士、线程池七大参数
在 Java 中,使用 java.util.concurrent.ThreadPoolExecutor
类创建线程池时,需要指定七个核心参数,这些参数共同决定了线程池的行为和性能。以下是这七大参数的详细说明:
-
corePoolSize(核心线程数):
- 线程池中的核心线程数量。
- 即使这些线程处于空闲状态,它们也不会被销毁。
- 线程池初始化时,默认不会立即创建这些核心线程,它们通常在有新任务时才会创建。
-
maximumPoolSize(最大线程数):
- 线程池能够容纳的最大线程数量。
- 当工作队列满了之后,线程池会创建新的线程,直到线程数达到这个最大值。
-
keepAliveTime(线程保持活动时间):
- 当线程数量超过核心线程数时,这些多余的线程在空闲时会等待新任务的到来。
keepAliveTime
是这些线程在终止前等待新任务的最长时间。
-
unit(时间单位):
keepAliveTime
的时间单位。- 例如,可以是毫秒(
TimeUnit.MILLISECONDS
)、秒(TimeUnit.SECONDS
)等。
-
workQueue(工作队列):
- 用于存储等待执行的任务的阻塞队列。
- 可以选择不同类型的队列,如
LinkedBlockingQueue
、SynchronousQueue
、ArrayBlockingQueue
等。
-
threadFactory(线程工厂):
- 用于创建新线程的工厂。
- 可以通过它自定义线程的名称、优先级等属性。
-
handler(拒绝策略):
- 当线程池和工作队列都满了时,指定线程池的拒绝策略。
- 常见的拒绝策略包括
ThreadPoolExecutor.AbortPolicy
(抛出异常)、ThreadPoolExecutor.CallerRunsPolicy
(用调用者所在的线程来执行任务)、ThreadPoolExecutor.DiscardPolicy
(静默丢弃无法处理的任务)、ThreadPoolExecutor.DiscardOldestPolicy
(丢弃最早的未处理的任务请求)。
这些参数共同决定了线程池的工作方式和性能,因此在创建线程池时需要根据实际的应用场景仔细选择合适的参数。
士一、保证并发安全的三大特性?
原子性:一次或多次操作在执行期间不被其他线程影响
可见性:当一个线程在工作内存修改了变量,其他线程能立刻知道
有序性:JVM对指令的优化会让指令执行顺序改变,有序性是禁止指令重排
基础知识
一、接口和抽象类
接口
- 定义:接口是一种完全抽象的类,它允许声明方法,但不允许实现方法。
- 目的:主要用于定义一组方法规范,强制实现类遵守一定的行为模式。
- 特点:(1)所有方法默认是“public”和“abstract”的(除了默认方法和静态方法)。(2)可以包含常量(默认是public static final)。(3)不可以有构造器。(4)一个类可以实现多个接口。(5)接口支持多继承。
抽象类
- 定义:抽象类是一种不能被实例化的类,包含抽象方法(没有具体实现的方法)以及非抽象方法(有具体实现的方法)。
- 目的:为子类提供一个基础模板,子类可以在此基础上进行拓展和修改。
- 特点:(1)可以包含抽象方法和非抽象方法。(2)可以有构造器(3)抽象类可以有成员变量(4)一个类只能继承一个抽象类(Java不支持多重继承)。(5)如果一个子类继承抽象类,则子类需要实现所有的抽象方法,除非它本身也是一个抽象类。
二、重载和重写
重载
- 定义:重载发生在同一个类中,当两个或多个方法在同一个类中共享相同的名称,但具有不同的参数列表(参数的数量或类型不同)时,这些方法被称为重载。
- 目的:重载的主要目的是提高程序的可读性和代码的组织性,允许在同一类中使用相同的方法名称来执行类似但略有不同的任务。
- 规则:(1)必须改变参数的数量或类型。(2)可以改变返回类型。(3)可以改变访问修饰符。(4)重载是静态的,它基于编译时的方法绑定。
重载
- 定义:重写发生在具有继承关系的两个类之间。当子类有一个与父类相同名称、相同参数列表的方法时,子类的这个方法被视为对父类方法的重写。
- 目的:重写的主要目的是允许子类提供特定于自身的实现,覆盖父类中提供的方法。
- 规则:(1)必须有相同的方法名称和相同的参数列表。(2)返回类型可以是子类型(协变返回类型)。(3)访问修饰符不能更严格。(4)不能重写被final修饰的方法。(5)重写是基于运行时的动态方法绑定。
三、hashCode和equals
在Java中,hashCode()和equals()方法常用于处理对象的比较和哈希表操作。
hashCode():此方法返回对象的哈希码,用于确定对象在哈希表中的位置。不同的对象可以有相同的哈希码,但理想情况下,不同的对象应该有不同的哈希码。
equals():用于检查两个对象是否相等。默认实现检查对象的引用是否相同,但通常会被重写以提供更具体的等价性判断(例如,比较对象的内容而非引用)。
重写equals()时,通常建议也重写hashCode(),以保持hashCode的一致性:即等价的对象应该有相同的哈希码。这是因为在哈希表(如HashMap)中使用对象时,先通过hashCode()确定桶位置,然后用equals()检查是否真正相等。如果这两个方法不一致,可能导致哈希表的行为异常。