java文档整理

基础

基础数据类型:byte、short、int、long、float、double、char、boolean(基本类型在栈内存中,引用在堆内存中)
面向对象的三个基本特征是:封装、继承、多态。
面向对象六大原则:单一职责原则,开放封闭原则,替换原则,依赖倒置原则,接口隔离原则,良性依赖原则
== equals,hashCode,区别
==:基本数据类型,值是否相等。引用数据类型,内存地址是否相同。
equals:equals是Object的方法,比较的是所指向的对象的地址值, 一般情况下,重写之后比较的是对象的值
hashCode:java就采用了hash表,利用哈希算法(也叫散列算法),就是将对象数据根据该对象的特征使用特定的算法将其定义到一个地址上
重载,重写
重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。
父类与子类之间的多态性,对父类的函数进行重新定义。
int与Integer的基本使用对比
Integer是int的包装类;int是基本数据类型
Integer变量必须实例化后才能使用;int变量不需要;
Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
Integer的默认值是null;int的默认值是0。
反射:反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
calss的使用:Class.forName(类的全称)
方法的反射
构造函数的反射
成员变量的反射
String、StringBuffer与StringBuilder的区别?分别在哪些场景下使⽤
1. 三者都是final, 不允许被继承
2. 在本质都是char[] 字符数组实现
3. String、StringBuffer与StringBuilder中,String是不可变对象,另外两个是可变的
4. StringBuilder 效率更快,因为它不需要加锁,不具备多线程安全
5. StringBuffer ⾥⾯操作⽅法⽤synchronized ,效率相对更低,是线程安全的;
抽象类和接口的区别:
abstract 关键字定义的类就是抽象类,采用 abstract 关键字定义的方法就是抽象方法(单继承)(静态方法和构造方法 可以有)
implements 关键字,接口是特殊的抽象类,类与类是继承 extends,类与接口是实现implements,其实都是继承(多实现)(静态方法和构造方法 不可以有)一个类可以实现多个接口

public class AbstractTest01 {
    public static void main(String[] args) {
        new a();//错误,不可创建对象
    }
}
//定义一个抽象类
abstract  class a{

}
//不是抽象类可以继承抽象类并且实例化
class b extends a{
}

//抽象子类可以继承抽象类
abstract class c extends a{
}

List & map

ArrayList全解析

ArrayList是内部是以动态数组的形式来存储数据,动态数组不是意味着去改变原有内部生成的数组的长度、而是保留原有数组的引用、将其指向新生成的数组对象、这样会造成数组的长度可变的假象。
随机访问ArrayList中的元素效率非常高、但是执行插入、删除时效率比较地下、具体原因后面有分析。
ArrayList的扩容机制是怎样的?
未指定集合容量,默认是0, 若已经指定⼤⼩则集合⼤⼩为指定的;
当集合第⼀次添加元素的时候,集合⼤⼩扩容为10
ArrayList的元素个数⼤于其容量,扩容的⼤⼩= 原始⼤⼩+原始⼤⼩/2
CopyOnWriteArrayList
CopyOnWriteArrayList是Java集合框架中的一种并发容器。
它是ArrayList的线程安全版本,采用了一种特殊的“写时复制”策略
执⾏修改操作时,会拷⻉⼀份新的数组进⾏操作(add、set、remove),在执⾏完修改后将原来集合指向 新的集合来完成修改操作
场景:读⾼性能,适⽤读操作远远⼤于写操作的场景中使⽤(读的时候是不需要加锁的,直接获取,删除和增加是需要加锁的, 读多写少)
设计思想:读写分离+最终⼀致。缺点:内存占⽤问题,写时复制机制,内存⾥会同时驻扎两个对象的内存,旧的对象和新写⼊的对象,

LinkedList全解析


arrayList 和 linkedList 区别
1.底层实现:ArrayList内部是数组实现,而LinkedList内部实现是双向链表结构
2.性能:新增、删除元素时ArrayList需要使用到拷贝原数组,而LinkedList只需移动指针。新增删除LinkedList 比较快,遍历Array 比较快
java集合框架List了解多少?Vector 和 ArrayList、LinkedList 联系和区别?

HashMap

hashMap 遍历
entrySet()
keySet(获取所有key集合)
hasNext(Iterator<Map.Entry> entries = map.entrySet().iterator(); )
Lambda(map.forEach((k, v) -> System.out.println("key: " + k + " value:" + v));)
是基于哈希表的 Map 接口的实现,以 Key-Value 的形式存在,即存储的对象是 Entry (同时包含了 Key 和 Value)
在HashMap中,其会根据hash算法来计算key-value的存储位置并进行快速存取。HashMap最多只允许一条Entry的键为Null(多条会覆盖),但允许多条Entry的值为Null。
HashMap:底层是基于数组+链表,⾮线程安全的,默认容量是16、允许有空的健和值
HashMap的底层实现
1. HashMap底层(数组+链表+红⿊树 jdk8才有红⿊树)
2. 数组中每⼀项是⼀个链表,即数组和链表的结合体
3. Node<K,V>[] table 是数组,数组的元素是Entry(Node继承Entry),Entry元素是⼀个key-value的键值对,它持有⼀个指向下个Entry的引⽤,
4. Entry链表的⾸节点,也指向了该链表的下个Entry元素
5. 在JDK1.8中,链表的⻓度⼤于8,链表会转换成红⿊树

ConcurrentHashMap和 hashtable 都是线程安全,之间区别?
hashtable类基本上所有的⽅法都是采⽤ synchronized,
ConcurrentHashMap是采⽤了分段锁的思想提⾼性能,锁粒度更细化JDK8之前,ConcurrentHashMap使⽤锁分段技术,将数据分成⼀段段存储,每个数据段配置⼀把锁,即segment类,技术点:Segment+HashEntry,
JKD8之后,取消Segment这个分段锁数据结构,底层也是使⽤Node数组+链表+红⿊树,从⽽实现对每⼀段数据就⾏加锁,也减少了并发冲突的概率,CAS(读)+Synchronized(写)
技术点:Node+Cas+Synchronized

HashMap 的 put 流程
1、判断键值对数组table是否为空或为null,否则执行resize()进行扩容(初始化)
2、根据键值key计算hash值得到数组索引
3、判断table[i]==null,条件成立,直接新建节点添加
4、如果table[i] != null,则如下
4.1 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value
4.2 判断table[i]是否为treeNode,即table[i]是否是红黑树,如果是红黑树,则直接在树中插入键值对
4.3 遍历table[i],链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话把链表转化成红黑树,在红黑树中执行插入操作,遍历过程中若发现key已经存在直接覆盖value
5、插入成功后,判断实际存在的键值对数据size是否超过了最大容量threshold(数据长度*0.75),如果超过,进行扩容。
————————————————

Hashmap的死循环及Java8的修复?
HashSet详解
⽤过哪些Map的实现
答:HashMap、Hashtable、ConcurrentHashMap?
HashMap:底层是基于数组+链表,⾮线程安全的,允许有空的健和值
Hashtable:虽然是线程安全的,但是它只是简单地用synchronized修饰了一些关键方法,相当于是对this加锁,即对整个Hashtable对象进行加锁

ConcurrentHashMap

相较于Hashtable做了许多的优化,核心思路就是降低锁冲突概率~,锁的是每个链表的头结点,降低了锁冲突的概率;充分利用CAS机制;优化了扩容方式;key值不可以为null,适用于多线程环境

多线程&线程池

多线程

并发&并行:多车道一个收费站出口,多车道对应多个收费站出口
线程常用方法
currentThread(),当前线程
getId(),获取线程唯一编号
setName()/getName(),线程名称

start(),开启新的线程
run(),在当前线程中执行

isAlive(),线程活动状态
sleep(),属于Thread 的方法,休眠毫秒,不释放锁
yield(),属于Thread 的方法,交出cpu使用权,不释放锁
join(),属于Thread 的方法,让调用join方法线程先执行完成,不释放锁, join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。

wait(),属于Object 方法,进入等待状态,释放锁。
notify(),属于Object 方法,唤醒(对象监视器)单个线程,释放锁。
notifAll(),属于Object 方法,唤醒(对象监视器)等待全部线程,释放锁。

setPriority(num),设置线程优先级(1-10)
interrupt(),中断线程,只是打一个中断标记,并不是真正的停止
isInterrupted(),线程中断标记。循环体中break;return;进行中断

setDaemon(),守护线程,垃圾回收器就是守护线程,守护线程不能单独执行,只剩守护线程会自动销毁,监控别的线程,main线程结束,守护线程也结束了
sleep/yield/join wait/notify/notifyAll, 分别解释下
sleep:属于线程Thread的⽅法,让线程暂缓执⾏,等待预计时间之后再恢复,交出CPU使⽤权,不会释放锁
yield: 属于线程Thread的⽅法,暂停当前线程的对象,去执⾏其他线程 交出CPU使⽤权,不会释放锁
join: 属于线程Thread的⽅法,让调⽤ join⽅法的线程先执⾏完毕 交出CPU使⽤权,不会释放锁

wait, 属于Object的⽅法,当前线程调⽤对象的wait⽅法,会释放锁,进⼊线程的等待队列,需要依靠notify或者notifyAll唤醒,或者wait(timeout)时间⾃动唤醒
notfy, 属于Object的⽅法,唤醒在对象监视器上等待的单个线程,选择是任意的
nofifAll:属于Object的⽅法,唤醒在对象监视器上等待的全部线程

线程生命周期介绍
NEW:新建状态,调用start()启动之前
RUNNABLE:可运行状态,复合状态包括(READY,RUNNING)READY可以被线程调度器进行调度,RUNNING 标识该线程正在执行。Thread.yield(),可以把线程从RUNNING:状态修改成READY状态
BLOCKED:阻塞状态,线程发起阻塞IO请求,申请独占资源(申请锁),进入阻塞状态,不会占用CPU资源,线程获得申请的资源,转换成RUNNABLE状态。
WATTIING:等待状态,想成执行了wait,join方法,会把线程转换成 等待状态,执行了notify,方法或者加入的线程执行完毕,当前线程转换为 RUNNABLE状态
TIMED_WAITING:超时等待,调用Thread.sleep(long),或者wait(long),进入超时等待状态。不会无限等待,没有在指定时间完成期望操作,自动转换为RUNNABLE
TERMINATED:终止状态

java线程常⻅的基本状态有哪些
1. 创建(NEW): ⽣成线程对象,但是并没有调⽤该对象start(),
2. 就绪(Runnable):当调⽤线程对象的start()⽅法,线程就进⼊就绪状态,就是没获得CPU使⽤权。
3. 运⾏(Running):即获得CPU使⽤权,
4. 阻塞(Blocked)
同步阻塞:线程在获取synchronized同步锁失败,即锁被其他线程占⽤,它就会进⼊同步阻塞状态
等待(WAITING)
超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后⾃⾏返回
5. 死亡(TERMINATED):⼀个线程run⽅法执⾏结束,该线程就死亡了

上下文切换
当前任务在执⾏完 CPU 时间⽚切换到另⼀个任务之前会先保存⾃⼰的状态,以便下次 再切换回这个任务时,可以再加载这个任务的状态
sleep() ⽅法和 wait() ⽅法区别和共同点
sleep(休眠)。wait(等待)
属于Thread类。属于Object类
超过休眠时间自动苏醒。⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或者 notifyAll() ⽅法

要优雅的去中断一个线程,在线程中提供了一个 interrupt 方法

public class InterruptDemo {

    private static int i;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            //默认情况下 isInterrupted 返回 false、通过 thread.interrupt 变成了 true
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("------"+Thread.currentThread().isInterrupted());
                i++;
            }
            System.out.println("Num:" + i);
        }, "interruptDemo");
        thread.start();
        TimeUnit.SECONDS.sleep(100);
        thread.interrupt(); //加和不加的效果
    }
}

原子性,可见性,有序性

原子性(Atomic)不可分割,操作要么成功,要么失败。多个线程对同一个变量修改,要么成功,要么失败。
java有两种方式实现原子性:一种是锁,一种是CAS(Compare and Swap)

可见性(visibility)线程A修改变量的值,其他线程无法立即读到更新的值,如果一个线程A对共享变量更新后,后续其他线程访问该变量,可以读到更新后的结果,说明线程A对共享变量的修改对其他线程具备可见性,一个CPU修改数据,另一个CPU无法立即读取到共享变量的值,(在多线程环境下,读和写发生在不同的线程中的时候,可能会出现:读线程不能及时的读取到其他线程写入的最新的值。这就是所谓的可见性。)

有序性(Ordering)指令重排序,多核处理器的环境下,编写的顺序结构,可能是没有保障的。(重排序,对内存访问有序操作的优化)

  • 指令重排序:

内存的重排序和处理器微架构有关。内存重排序可能会导致线程安全问题。
数据依赖关系,不允许重排序
x=1; y=x+1;
后一条语句,包含前一天语句执行结果。存在数据依赖关系。存在数据依赖的语句,不会被重排序。

x=1; y=2; z = x+ y ;
这三条语句中, 前两句不存在关系,可能会出现指令重排序。

存在控制依赖关系,语句允许重排
if 判断,多线程情况下,先执行了 if 代码块。

保证内存访问的顺序性
使用 volatile synchronized 关键字实现有序性
java虚拟机内存模型
工作内存,主内存,之间的数据问题,
存储子系统重排序:(内存)
高速缓存:(Cache)与主内存处理速度不匹配,而设计的一个高速缓存
写缓冲器:用来提高写高速缓存操作的效率



volatile

volatile:关键字volatile 可以使得在多处理器环境下保证了共享变量的可见性
在Java内存模型那一章我们介绍了JMM有一个主内存,每个线程有自己私有的工作内存,工作内存中保存了一些变量在主内存的拷贝。
内存可见性,指的是线程之间的可见性,当一个线程修改了共享变量时,另一个线程可以读取到这个修改后的值。
volatile可以保证内存可见性且禁止重排序
关键字volatile的主要使用场合是在多个线程中可以感知到共享的实例变量被修改了,并且可以获得该变量最新的值。
注意,仅仅是读取哦,修改变量操作是非线程安全的,需要使用synchronized关键字进行同步操作。
volatile 可以避免指令重排,能否解释下什么是指令重排
指令重排序分两类 编译器重排序和运⾏时重排序
JVM在编译java代码或者CPU执⾏JVM字节码时,对现有的指令进⾏重新排序,主要⽬的是优化运⾏
volatile修饰变量,每次读取前必须从主内存属性最新的值,每次写⼊需要⽴刻写到主内存中
volatile作用:使变量在多个线程之前可见。
volatile,强制线程从公共内存中读取变量的值,而不是从工作内存中读取。

synchronized

synchronized,内部锁,监视器(Monitor) 排他锁
可以修饰,代码块,修饰方法(实例方法,静态方法)
修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
对synchronized了解不,能否介绍下你对synchronized的理解
代码块:加了 synchronized 关键字的代码段,⽣成的字节码⽂件会多出 monitorenter 和monitorexit 两条指令
⽅法:⽣成的字节码⽂件中会多⼀个 ACC_SYNCHRONIZED 标志位,
volatile关键字和synchronized有什么⼤的区别?
volatile关键字,只能修饰变量,标志的变量将会从内存缓冲区撤回内存中,保证了线程访问这个变量是只能从公共堆栈中取值。增加了实例变量在多个线程之间的可见性
synchronized关键字,修饰变量,代码块。synchronized 在保证原子性 的同时间接保证了可见性。
volatile保证了变量在各个线程之间的可见性,而synchronized主要保证了各个线程访问资源的同步性。
线程之间的共享变量存在主内存中,每个线程都有一个私有的本地内存,存储了该线程以读、写共享变量的副本。本地内存是Java内存模型的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器等。
所以,线程A无法直接访问线程B的工作内存,线程间通信必须经过主内存。
根据JMM的规定,线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内存中读取。
线程A无法直接访问线程B的工作内存,线程间通信必须经过主内存

在Java中可以有哪些⽅法来保证线程安全
1. 加锁,⽐如 synchronize / ReentrantLock
2. 使⽤ volatile 声明变量,轻量级同步,不能保证原⼦性(需要解释)
3. 使⽤线程安全类(原⼦类 AtomicXXX,并发容器,同步容器
4. CopyOnWriteArrayList / ConcurrentHashMap等
5. ThreadLocal 本地私有变量/ 信号量Semaphore等
常⻅进程和线程间的调度算法
时间⽚轮转调度算法:
轮流的为各个进程服务,让每个进程在⼀定时间间隔内都可以得到响应,由于⾼频率的进程切换,会增加了开销,且不区分任务的紧急程度
优先级调度算法:
根据任务的紧急程度进⾏调度,⾼优先级的先处理,低优先级的慢处理

死锁的4个必要条件(四个条件同时满足,就会产生死锁。)
1. 互斥条件:资源不能共享,只能由⼀个线程使⽤(共享资源 X 和 Y 只能被一个线程占用;(该条件不可被破坏))
2. 请求与保持条件(不释放):线程已经获得⼀些资源,但因请求其他资源发⽣阻塞,对已经获得的资源保持不释放(线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;)
3. 不可抢占:有些资源是不可强占的,当某个线程获得这个资源后,系统不能强⾏回收,只能由线程使⽤完⾃⼰释放
4. 循环等待条件:多个线程形成环形链,每个都占⽤对⽅申请的下个资源(线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待)

了解CAS不,能否解释下什么是CAS
全称是Compare And Swap,即⽐较再交换,是实现并发应⽤到的⼀种技术
内存地址(V)、预期原值(A)和新值(B)。
如果内存位置的值与预期原值相匹配,那么处理器会⾃动将该位置值更新为新值 ,
若果在第⼀轮循环中,a线程获取地址⾥⾯的值被b线程修改了,那么a线程需要⾃旋,到下次循环才有可能机会执⾏。
CAS这个是属于乐观锁,性能较悲观锁有很⼤的提⾼
AtomicXXX 等原⼦类底层就是 CAS实现,⼀定程度⽐synchonized好,因为后者是悲观锁

⽇常开发⾥⾯⽤过java⾥⾯有哪些锁?
悲观锁:当线程去操作数据的时候,总认为别的线程会去修改数据,所以它每次拿数据的时候都会上锁,别的线程去拿数据的时候就会阻塞,⽐如synchronized
乐观锁:每次去拿数据的时候都认为别⼈不会修改,⽐如CAS是乐观锁,但严格来说并不是锁,通过原⼦性来保证数据的同步,
⼩结:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景

公平锁:指多个线程按照申请锁的顺序来获取锁,简单来说 如果⼀个线程组⾥,能保证每个线程都能拿到锁 ⽐如ReentrantLock(底层是同步队列FIFO:First Input First Output来实现)
⾮公平锁:获取锁的⽅式是随机获取的,保证不了每个线程都能拿到锁,⽐如synchronized、ReentrantLock
⼩结:⾮公平锁性能⾼于公平锁,更能重复利⽤CPU的时间

可重⼊锁:也叫递归锁,在外层使⽤锁之后,在内层仍然可以使⽤,并且不发⽣死锁
不可重⼊锁:若当前线程执⾏某个⽅法已经获取了该锁,那么在⽅法中尝试再次获取锁时,就会获取不到被阻塞

⾃旋锁:⼀个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,任何时刻最多只能有⼀个执⾏单元获得锁.
常⻅的⾃旋锁:TicketLock,CLHLock,MSCLock

偏向锁:⼀段同步代码⼀直被⼀个线程所访问,那么该线程会⾃动获取锁,获取锁的代价更低,
轻量级锁:当锁是偏向锁的时候,被其他线程访问,偏向锁就会升级为轻量级锁,其他线程会通过⾃旋的形式尝试获取锁,但不会阻塞,且性能会⾼点
重量级锁:当锁为轻量级锁的时候,其他线程虽然是⾃旋,但⾃旋不会⼀直循环下去,当⾃旋⼀定次数的时候且还没有获取到锁,就会进⼊阻塞,该锁升级为重量级锁,重量级锁会让其他申请的线程进⼊阻塞,性能也会降低

synchronized和ReentrantLock使⽤的场景是什么
两者都是可重入锁(自己可以再次获取自己的内部锁)
synchronized 依赖于 JVM,ReenTrantLock 依赖于 API(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成)
synchronized :wait()和notify/notifyAll()方法相结合可以实现等待/通知机制
ReentrantLock:Condition 实例的 signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程
synchronized只能是非公平锁,ReenTrantLock默认情况是非公平的,ReentrantLock(boolean fair)构造方法来制定是否是公平的
ReentrantLock 和ReentrantReadWriteLock有啥不同?
ReentrantLock是独占锁且可重⼊的,相⽐synchronized⽽⾔功能更加丰富也更适合复杂的并发场景,
读数据是不会改变数据没有必要加锁,但是还是加锁了,降低了程序的性能,所以就有了ReadWriteLock读写锁接⼝
常⻅的阻塞队列
ArrayBlockingQueue: 基于数组实现的⼀个阻塞队列,需要指定容量⼤⼩,FIFO先进先出顺序
LinkedBlockingQueue: 基于链表实现的⼀个阻塞队列,如果不指定容量⼤⼩,默认Integer.MAX_VALUE, FIFO先进先出顺序
PriorityBlockingQueue: ⼀个⽀持优先级的⽆界阻塞队列,默认情况下元素采⽤⾃然顺序升序排序,也可以⾃定义排序实现 java.lang.Comparable接⼝
DelayQueue: 延迟队列,在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素,⾥⾯的对象必须实现 java.util.concurrent.Delayed 接⼝并实现CompareTo和getDelay⽅法

线程池

线程池作用?
创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程。对线程做统一管理。
线程池参数:
1,corePoolSize:核⼼线程数,线程池也会维护线程的最少数量,默认情况下核⼼线程会⼀直存活,即使没有任务也不会受存keepAliveTime控制。坑:在刚创建线程池时线程不会⽴即启动,到有任务提交时才开始创建线程并逐步线程数⽬达到corePoolSize
2,maximumPoolSize:线程池维护线程的最⼤数量,超过将被阻塞。坑:当核⼼线程满,且阻塞队列也满时,才会判断当前线程数是否⼩于最⼤线程数,才决定是否创建新线程
3,keepAliveTime:⾮核⼼线程的闲置超时时间,超过这个时间就会被回收,直到线程数量等于corePoolSize
4,unit:指定keepAliveTime的单位,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS
5,BlockingQueue:线程池中的任务队列,常⽤的是 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue
常用的几个阻塞队列:
ArrayBlockingQueue:数组阻塞队列,底层数据结构是数组,需要指定队列的大小。
LinkedBlockingQueue:链式阻塞队列,底层数据结构是链表,默认大小是Integer.MAX_VALUE,也可以指定大小。
DelayQueue:延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素 。
PriorityBlockingQueue:基于优先级的无界阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),内部控制线程同步的锁采用的是公平锁。
SynchronousQueue:同步队列,内部容量为0,每个put操作必须等待一个take操作,反之亦然。

6,threadFactory:创建新线程时使⽤的⼯⼚
7,handler: RejectedExecutionHandler是⼀个接⼝且只有⼀个⽅法,线程池中的数量⼤于maximumPoolSize,对拒绝任务的处理策略,
默认有4种策略 AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy
1. 直接抛出一个异常;
2. 会默默的把新来的这个任务给丢弃
3. 会把队列中存在时间最久的那个任务给丢弃掉,以便给新来的任务腾位置;
4. 因为线程池已经无法接纳新的任务了,那么谁提交的这个任务,谁就去跑这个业务;(由调用线程处理该任务)
延迟策略:1报异常,2丢弃,3丢弃最先进入的队列4,直接创建线程执行,延长了执行时间,给线程池争取时间。

newFixedThreadPool ⼀个定⻓线程池,可控制线程最⼤并发数
newCachedThreadPool ⼀个可缓存线程池
newSingleThreadExecutor ⼀个单线程化的线程池,⽤唯⼀的⼯作线程来执⾏任务
newScheduledThreadPool ⼀个定⻓线程池,⽀持定时/周期性任务执⾏
常⻅的线程池问题:
newFixedThreadPool 和 newSingleThreadExecutor:
队列使⽤ LinkedBlockingQueue,队列⻓度为 Integer.MAX_VALUE,可能造成堆积,导致OOM
newCachedThreadPool 和 newScheduledThreadPool
线程池⾥⾯允许最⼤的线程数是 Integer.MAX_VALUE,可能会创建过多线程,导致OOM

核心线程数设置多少合适
CPU密集型:核心线程数 = CPU核数 + 1,逻辑代码比较多
IO密集型:核心线程数 = CPU核数 * 2,io 是代码中设计读文件写文件的时候。

execute 和 submit 区别
execute 只可以接收一个 Runnable 的参数,
submit 可以接收 Runable 和 Callable 这两种类型的参数,
execute() ⽅法⽤于提交不需要返回值的任务,所以⽆法判断任务是否被线程池执⾏成功与否;
submit() 会返回⼀个 Future 类型的对象,判断是否成功。

Condition
  • Condition和Object的wait/notify基本相似。其中,Condition的await方法对应的是Object的wait方法,而Condition的signal/signalAll方法则对应Object的notify/notifyAll()。但Condition类似于Object的等待/通知机制的加强版。我们来看看主要的方法:
Lock 锁,ReentrantLock 可重入锁,ReentrantReadWriteLock 读写锁

线程之间的通信
Java中提供了wait/notify这个机制,用来实现条件等待和唤醒。
假设线程A持有锁,线程B再去抢占锁时,它需要等待持有锁的线程释放之后才能抢占,那线程B怎么知道线程A什么时候释放呢?这个时候就可以采用通信机制。

notify:表示持有对象锁的线程 A 准备释放对象锁权限,通知 JVM 唤醒某个竞争该对象锁的线程 X。线程 A synchronized 代码执行结束并且释放了锁之后,线程 X 直接获得对象锁权限,其他竞争线程继续等待(即使线程 X 同步完毕,释放对象锁,其他竞争线程仍然等待,直至有新的 notify ,notifyAll 被调用。

notifyAll 和 notify 的区别在于,notifyAll 会唤醒竞争同一个对象锁的所有线程,当已经获得锁的线程 A 释放锁之后,所有被唤醒的线程都有可能获得对象锁权限
CAS操作失败时,会通过自旋操作等待后继续尝试,直到成功。但是如果CAS操作一直失败呢?那么CAS的自旋会一直进行,消耗大量CPU资源。在JUC中有些位置限制了CAS自旋的次数,

ReentrantLock

ReentrantLock是一个非抽象类,它是Lock接口的JDK默认实现,实现了锁的基本功能。
在ReentrantLock的构造方法里,可以传入一个boolean类型的参数,来指定它是否是一个公平锁,默认情况下是非公平的。这个参数一旦实例化后就不能修改,只能通过isFair()方法来查看。

和 synchronized ReentrantLock可以提供公平+非公平两种特性
当代码执行到synchronized修饰的代码块的时候,如果在同步代码块内部发生了异常,没有及时处理的话,会提前退出并且让线程释放锁。
而ReentrantLock无法做到立刻解锁,因此,unLock()的解锁操作一定要在finally代码块当中,避免加锁之后忘记解锁的情况

synchronized加锁的过程,涉及了锁升级的过程。
从无锁->偏向锁->轻量级锁->重量级锁;并且还可能涉及锁粗化、锁消除。

ReentrantLock是基于AQS来实现的。
我们也提到了什么是AQS,它的核心就是两个属性。一个是内部封装的队列。用来保存获取不到锁的线程。另外一个是state属性,用来标记是否可以获取锁。
ReentrantLock
它是Lock接口的JDK默认实现,实现了锁的基本功能。
ReentrantLock的构造方法里,可以传入一个boolean类型的参数,来指定它是否是一个公平锁,默认情况下是非公平的。
ReentrantReadWrite它是LockReadWriteLock接口的JDK默认实现。
ReentrantReadWrite同样是可重入的,支持非公平锁和公平锁。不同的是,它还支持”读写锁“。
StampedLock它实现了“读写锁”的功能,并且性能比ReentrantReadWriteLock更高。StampedLock还把读锁分为了“乐观读锁”和“悲观读锁”两种。
在读的时候如果发生了写,应该通过重试的方式来获取新的值,而不应该阻塞写操作。这种模式也就是典型的无锁编程思想,和CAS自旋的思想一样。这种操作方式决定了StampedLock在读线程非常多而写线程非常少的场景下非常适用。

对象头中锁的信息:

无锁,偏向锁,轻量级锁,重量级锁
偏向锁:在第一次请求时采用CAS在锁对象的标记中记录当前线程的ID,在之后该线程再次进入同步代码块时,不需要抢占锁,直接判断线程ID即可,这种适用于锁会被同一个线程多次抢占的情况
轻量级锁:用CAS操作,把锁对象的标记字段替换为一个指针指向当前线程栈帧中的LockRecord,它针对的是多个线程在不同时间段内申请同一把锁的情况
重量级锁:和唤醒加锁的线程,它适用于多个线程同时竞争同一把锁的情况

CAS实现原子操作的三大问题
ABA问题
所谓ABA问题,就是一个值原来是A,变成了B,又变回了A。这个时候使用CAS是检查不出变化的,但实际上却被更新了两次。
ABA问题的解决思路是在变量前面追加上版本号或者时间戳。
ReentrantLock(重入锁):线程在获得锁之后,再次获取该锁不需要阻塞,而是直接关联计数器增加重入次数
ReentrantReadWriteLock(重入读写锁):重入读写锁,它实现了 ReadWriteLock 接口,在这个 类中维护了两个锁,一个是 ReadLock,一个是 WriteLock,读和读不互斥、读和写互斥、写和写互斥。
StampedLock:stampedLock 是 JDK8 引入的新的锁机制,可以简单认为是读写锁的改进版本,读和写是有冲突的,如果大量的读线程存在,可能会引起写线程的饥饿。 StampedLock是一种乐观的读策略,使得乐观锁完全不会阻塞写线程

ReentrantReadWriteLock
  • 它是ReadWriteLock接口的JDK默认实现。它与ReentrantLock的功能类似,同样是可重入的,支持非公平锁和公平锁。不同的是,它还支持”读写锁“。
// 内部结构
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
    // 具体实现
}
static final class NonfairSync extends Sync {
    // 具体实现
}
static final class FairSync extends Sync {
    // 具体实现
}
public static class ReadLock implements Lock, java.io.Serializable {
    private final Sync sync;
    protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
    }
    // 具体实现
}
public static class WriteLock implements Lock, java.io.Serializable {
    private final Sync sync;
    protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
    }
    // 具体实现
}
// 构造方法,初始化两个锁
public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}
// 获取读锁和写锁的方法
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

StampedLock
  • StampedLock类是在Java 8 才发布的,也是Doug Lea大神所写,有人号称它为锁的性能之王,它实现了“读写锁”的功能,并且性能比ReentrantReadWriteLock更高。StampedLock还把读锁分为了“乐观读锁”和“悲观读锁”两种。

  • 在读的时候如果发生了写,应该通过重试的方式来获取新的值,而不应该阻塞写操作。这种模式也就是典型的无锁编程思想,和CAS自旋的思想一样。这种操作方式决定了StampedLock在读线程非常多而写线程非常少的场景下非常适用。

J.U.C

J.U.C 一“ReentrantLock 可重入锁
synchronized是JVM层次的锁, ReentrantLock是jdk层次的锁
synchronized的 锁状态是无法在代码中直接判断的,但是reentrantLock可以通过ReentrantLock.isLocked()判断
synchronized是非公平锁,reentrantLock可以是公平也可以是非公平
synchronized是不可以被中断的,而reentrantLock.lockInterruptibly方法是可以中断的
在发生异常时,synchronized会自动释放锁,而reentrantLock需要开发者在finally块显示释放

J.U.C 二“Condition 条件控制
它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效
Condition依赖于Lock接口
Conditon中的await()对应Object的wait();
Condition中的signal()对应Object的notify();
Condition中的signalAll()对应Object的notifyAll()。
Condition await signal signalAll 和 Object 的 wait notify notifyAll 区别
Lock(ReentrantLock) + Condition它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。
Condition是个接口,基本的方法就是await()和signal()方法;

J.U.C 三 “阻塞队列”
J.U.C 四 “Atomic原子操作类”
原子更新基本类型
AtomicBoolean
AtomicInteger
AtomicLong
原子更新数组
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
原子更新引用
AtomicReference
AtomicReferenceFieldUpdater
AtomicMarkableReference(更新带有标记位的引用类 型)
原子更新字段
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicStampedReference

J.U.C 五 “JUC其他常用并发工具”
并发工具类 CountDownLatch 倒计时计算器
CountDownLatch 是倒数的意思,类似于我们倒计时的概念。
并发工具类 CycliBarrier 可循环使用(Cyclic)的屏障 (Barrier)
让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时, 屏障才会开门,所有被屏障拦截的线程才会继续工作
并发工具类 Semaphore 信号灯
semaphore 也就是我们常说的信号灯
比如某商场就 5 个停车位,每个停车位只能停一辆车,如果这个时候来了 10 辆车,必须要等前面有空的车位才能进入。
Phaser
Phaser(阶段协同器)是一个Java实现的并发工具类,用于协调多个线程的执行。
Phaser可以被视为CyclicBarrier和CountDownLatch的进化版,它能够自适应地调整并发线程数,可以动态地增加或减少参与线程的数量。
Fork/Join
大任务分解为若干个小任务,并行执行这些小任务,最终通过合并每个小任务的结果得到大任务的结果。

J.U.C 六 “CAS的底层实现原理”
CAS的底层实现原理
原理:读到数据是123,操作后是234,把234数据更新到主内存中, 再次读取主内存中的值,如果数据(操作起始时读取的值123)一样,就更新。
它的功能是判断内存某个位置的值是否为预期值,则更改为新的值,这个过程是原子的
会产生ABA 问题。
所有要加上版本号。
数据库修改语句:每次扣减减一操作: (and num > 0 防止出现负数扣减)(num-1 扣减商铺数量,同时版本+1)(判断版本是修改前的版本号是否一致)
update num= num-1 version+1 where version= {version} and num > 0

J.U.C 七 “ConcurrentHashMap与 HashMap、HashTable
ConcurrentHashMap
ConcurrentHashMap是Java中的一个线程安全的哈希表实现,它允许多个线程同时访问和修改其中的元素
HashMap
put()和get()的实现原理:
1,首先将k,v封装到Node对象当中(节点)。
2,然后它的底层会调用K的hashCode()方法得出hash值。
3,通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。
如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal
如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。
如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
HashMap的原理1.7 和1.8 的区别
jdk1.7中底层是由数组+链表实现;jdk1.8中底层是由数组+链表/红黑树实现
可以存储null键和null值,线程不安全
初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
Map中元素总数超过Entry数组的75%,触发扩容操作,

hash冲突
当两个key通过hashCod计算相同时(其实hashCode是随机产生的,是有可能hashCode相同),则发生了hash冲突,开放定址法、再哈希法、链地址法、建立公共溢出区
HashMap解决hash冲突的方式是用链表,
Hashtable
Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占
ConcurrentHashMap
底层采用分段的数组+链表实现,线程安全
通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容

AQS简介

AQS,是Java并发编程中的一个重要组件,提供了一种实现锁和同步器的框架,它是Java并发编程中非常重要的一部分。
1,状态变量:AQS通过一个int类型的状态变量来表示同步器的状态。0表示未被任何线程持有,非0表示被某个线程持有。
2, 等待队列: AQS提供了一个基于FIFO队列的等待队列,
3,共享锁和独占锁:AQS支持共享锁和独占锁,共享锁可以被多个线程同时获取,而独占锁只能被一个线程获取。例如,ReadWriteLock就是基于AQS实现的共享锁和独占锁
4,可重入:AQS支持可重入,一个线程可以多次获取同一个锁而不会被阻塞,这可以避免死锁和提高性能。
独占:独占锁,每次只能有一个线程持有锁,比如前面的 ReentrantLock 就是以独占方式实现的互斥锁
共享:共享锁,允许多个线程同时获取锁,并发访问共享资源,比如 ReentrantReadWriteLock
AQS 的内部实现
AQS 队列内部维护的是一个 FIFO 的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向后继节点和前驱节点。
每个 Node 其实是由线程封装,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去;当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。

JVM 简介

加载,验证,准备,解析,初始化,使用,卸载

线程不共享:程序计数器,本地方法栈,栈内存
线程共享:堆内存,(方法区,运行时常量池),直接内存
元空间大小: 512 存放类信息,(如类名、方法信息、字段信息等,而不再像永久代那样存储在 JVM 的堆内存中)
10M能存放260个对象信息 1m可以存放 5678个栈帧
程序计数器:记录字节码执行顺序,实现代码的流程控制,记录线程执行的位置 线程切换回来后知道执行在那个位置。
本地方法栈:程序运行需要的本地方法。
栈内存: 里面有栈针的概念,入栈出栈,先入后出,栈针里存储的是,局部变量,以及 对象引用。
堆内存:存储对象实例,
垃圾回收主要的是 堆内存 。堆内存分为,新生代,老年代。
新生代分为, Eden 区 和 s1 s2 新创建的对象会在,Eden ,分别进入s1 s2 (对象连续存活超过15 会移到老年代中,大对象会直接在老年代中创建)
如果判断对象死亡
可达性:Root Gc ,栈帧中的本地变量引用,类静态属性引用,
1,方法堆栈中使用到的参数、局部变量、临时变量等(栈帧中的本地变量表,参数、局部变量、临时变量等。)
2,java类的引用类型静态变量(方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量)
3,字符串常量池里的引用(方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。)
4,被同步锁(synchronized关键字)持有的对象
引用计数:对象多一次引用,引用计数+1 有个问题:对象相互引用,引用计数算法无法通知 GC 回收器回收他们。
类的双亲委派:
就是类在加载的时候,先去委派给父类进行加载,如果没有,就让子类去找需要加载的类
为什么需要双亲委派?
防止内存中出现对份同样的字节码

JVM垃圾回收算法和垃圾收集器
标记清除,标记整理,复制算法
新生代使用复制算法,老年代使用标记清除或者标记整理
对象首先分配到Eden区(每次只用Eden和其中一块Survivor.),新生代内存不足的时候,触发minor gc,将存活的对象复制到To区域
当对象的年龄超过阈值的时候(大对象会直接进入到老年代),会晋升到老年代(最大寿命是15)
老年代内存还是不足,就会触发堆内存溢出
垃圾收集器
Serial(串行)收集器:采用复制算法; 单线程收集,进行垃圾收集时,必须暂停所有工作线程,直到完成; 即会"Stop The World";
ParNew收集器 是serial的并发版本:(串行的多线程版本执行)
Cms(并发收集):(针对老年代,以获取最短回收停顿时间为目标,并发收集、低停顿)
G1:(可预测的停顿)
CMS收集器运作过程
初始标记:暂停所有的其他线程,初始标记仅仅标记GC Roots能直接关联到的对象,速度很快;
并发标记:并发标记就是进行GC Roots Tracing的过程;
重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录
并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫,回收所有的垃圾对象
G1 收集器
并行与并发:其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
分代收集:
1,能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;
2,将整个堆划分为多个大小相等的独立区域(Region);
3,新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合;
可预测的停顿

(面向服务端应用,针对具有大内存、多处理器的机器;)
如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒; (实践:对账系统中将CMS垃圾收集器修改为G1,降低对账时间20秒以上
新生代算法:标记复制
老年代算法是:标记清除(会造成内存碎片),标记整理
新生代和老年代的比例是 1:1 新生代End 和 s1 s2 的分配比例是 8:1:1
年轻代进入老年代
1,年龄超过15进入来年待,
2,动态年龄判断,假如一部分对象大于 s 区域的50%,不用到15岁,就直接把 一部分移动到老年代
3,大对象直接进入到老年代中
4,S区域如果无法放下存活的对象,会直接到 老年代中
老年代触发条件
1,每次 YongGC 都会触发一次, 老年代剩余大小 < 平均从新生代进入老年代的对象大小
2,进入老年代的大小,大于 老年代的剩余空间,会进行 FULL GC
3,老年代本身空间大小小于, 92% 的时候就会 触发 FULL GC
G1
年轻代垃圾回收只会回收Eden区和Survivor区
1)初始标记(InitingMark)标记GC Roots,会STW
2)根分区扫描(RootRegionScan)获取对老年代的引用,并标记被引用的对象。 该阶段与应用线程并发执行,
3)并发标记(ConcurrentMark)遍历整个堆,查找所有可达的存活对象。
4)最终标记(Remark)此阶段有一次STW暂停,以完成标记周期。
5)清除阶段(Clean UP)
清除阶段之后,还会对存活对象进行转移(复制算法)复制转移主要是为了解决分区内的碎片问题
优点:
1)并行与并发:G1能充分利用多CPU,G1收集器仍然可以通过并发的方式让Java程序继续运行。
2)可预测的停顿
老年代GC,耗时是 Yong GC的10倍(所谓的JVM优化就是,尽可能的让对象在新生代中分配回收。)
消灭FullGC,降低YongGC频率Serial 是单线程的垃圾回收机制,所以使用
GC会,把工作线程停止,停止创建新对象

优化案例(生成dump文件,jvisualvm 查看报错信息,能找到代码出错位置)
案例1:
导出功能,(14万的数据,企业近12 个月,每个月金融资产汇总。中间设计到计算平均值,通过平均值进行筛选。)
因为订单信息导出这个方法可能会有几万的数据量,首先要从数据库里面查询出来订单信息,然后把订单信息生成excel,这个过程会产生大量的String对象。
(其中有个简单的计算,)

案例2:
(定时任务执行中,获取字段过多,的客户后,其实只用了 组织机构代码或统代,返回参数过多导致的,YGC 过多。修改后,明显Ygc 频率明显降低了。)
咋循环体中创建对象。

案例 3:
单个缓存数据过大导致,(导入数据,方法超过 800 行,for 循环创建对象,进行入库操作,gc 次数,和时间增加。 ,)(拆分代码,循环外创建对象,for循环里面进行赋值操作。)
把新闻保存到redis缓存里面这个方式是没有问题的,
有问题的是新闻的50000多条数据都是保存在一个key里面,这样就导致每次调用查询新闻接口都会从redis里面把50000多条数据都拿出来,再做筛选分页拿出10条返回给前端。
50000多条数据也就意味着会产生50000多个对象,每个对象280个字节左右,50000个对象就有13.3M,
这就意味着只要查看一次新闻信息就会产生至少13.3M的对象,那么并发请求量只要到10,那么每秒钟都会产生133M的对象,而这种大对象会被直接分配到老年代,这样的话一个2G大小的老年代内存,只需要几秒就会塞满,从而触发GC。

元空间大小不足,频繁扩容触发FullGC,而MaxMetaspaceSize指定的大小并未很好生效。频繁FullGC 造成 CPU飙升

JVM优化:
减少FullGc次数
修改YongGc的参数,让对象在YongGc的时候回收
对大对象进行拆分成小对象,进行对象拆分。让对象在 YongGc 总就被回收。
还要一个,栈帧,把比较长的方法进行拆分,执行入栈出栈操作。
还有一个,对象的声明,需要在使用前声明,不能把对象声明放到方法头位置

常用命令:
jps 1320 (1320 进程号)
jstat -gcutil 1320 2000 (2秒打印一次GC)
jmap -dump:format=b,file=/home/ecmp/test.hpprof 1320 (生成 dump 文件)
需要观察生成的参数:
S0 S0 E O M CCS YGC YGCT FGC FGCT GCT
次数 时间 次数 时间
常用启动参数配置

-XX:MetaspaceSize=128m (元空间默认大小)
-XX:MaxMetaspaceSize=128m (元空间最大大小)

-Xms1024m (堆最大大小)
-Xmx1024m (堆默认大小)

-Xmn256m (新生代大小)
-Xss256k (棧最大大小)

-XX:SurvivorRatio=8 (新生代分区比例 8:2)

-XX:+UseConcMarkSweepGC (指定使用的垃圾收集器,这里使用CMS收集器)
-XX:+PrintGCDetails (打印详细的GC日志)

-Xms:初始化堆大小,如-Xms 256m表示初始化堆大小为256MB。
-Xmx:最大堆大小,如-Xmx 1024m表示最大堆大小为1024MB。

-Xmn:年轻代大小,如-Xmn256m表示年轻代大小为256MB。注意,增大年轻代后将会减小年老代大小,此值对系统性能影响较大,需合理配置。
常见的JVM调优方法
堆内存调优:调整JVM堆内存的大小,包括初始堆大小和最大堆大小,以适应应用程序的内存需求。
垃圾回收调优:选择合适的垃圾回收器、调整垃圾回收参数,以减少垃圾回收的停顿时间和提高吞吐量。
线程调优:调整JVM线程相关的参数,如线程栈大小、线程池大小等,以优化线程的并发性能和资源利用。

在进行JVM调优之前,建议先进行性能分析和监控,找出潜在的瓶颈和问题点,并对调优策略进行实际测试和验证。

posted @   微辰  阅读(6)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示