20190826
小骄傲:
线程池知识点
为什么不使用 new Thread()创建线程
1.每次创建线程 都需要创建对象
2.线程缺少统一的管理, 在程序不严谨的情况下,很有可能无限制的创建线程,造成资源浪费,严重时造成oom或者机器宕机
3.功能比较单一,如定期执行,线程中断
使用线程池的好处
1.重用存在的线程,减少了对象创建和销毁的资源开销
2.可以有效的控制最大并发线程数,提高资源的利用率,可以避免过多的资源竞争,造成阻塞
3.提供了定期执行,定时执行,并发控制等功能
创建线程池的参数:
corePoolSize: 核心线程数
maximumPoolSize: 最大线程数
workQueue: 阻塞队列 存储等待线程执行的任务
keepAliveTime:非核心线程能够空闲的时间
unit:时间 配合keepAliveTime使用
ThreadFactory:创建线程的工厂
RejectedExecutionHandler:线程拒绝策略
各个参数之间的关系:
1.如果运行的线程的数量小于corePoolSize 则创建一个新的线程执行任务,执行完成任务的线程不会立即销毁,线程会进入缓存队列中等待下次使用
2.如果运行的线程大于corePoolSize 小于maximumPoolSize 且workQueue满了的情况下,线程池会创建新的线程去处理任务,但是创建线程的数量不能大于maximunPoolSize,否则会采取线程池的拒绝策略
3.如果线程池的corePoolSize的数量等于maximumPoolSize数量,则可以认为创建的线程数是固定的,当空闲线程的数量为0时,会判断workQueue是否满了,如果没有满咋进入workQueue等待空闲线程,如果workQueue满了则会采取线程池的拒绝策略
4.keepAliveTime只针对非核心线程即当线程的数量大于核心线程池的数量corePoolSize创建出来的线程,超过时间,则销毁线程,如果线程数小于核心线程数,这个参数不起作用
5.workQueue三种队列
(1)SynchronousQueue:直接提交的任务队列,没有容量,提交的任务不会真是保存在队列中,而总是将新的任务提交给线程也执行,如果没有空闲的线程则会尝试创建新的线程,如过线程的数量大于maximumPoolSize时直接使用拒绝策略
(2)ArrayBlockingQueue:有界序列,创建队列时创建队列的最大容量,当线程池的数量小于corePoolSize时,优先创建新的线程,当核心线程数corePoolSize满的时候会优先加入workQueue中,如果工作队列满了才会尝试创建新的队列,知道线程数大于
maximunPoolSize才会执行拒绝策略
(3)LinkedBlockingQueue:无界队列,理论上说除非资源耗尽,否则不存在任务入队失败的场景,当线程池的数量小于corePoolSize时,优先创建新的线程,当线程数量到达corePoolSize时不会创建新的队列,当线程的处理任务的速度和任务加入队列速度差距很 大时,无解队列会一直增长,直到资源耗尽。也就是说如果使用无界队列,maximunPoolSize参数就没有意义了,这种队列适用于所有的任务都互不影响,例如适用于web请求时瞬间请求很多时,每个请求互不影响。
(4)PriorityBlockingQueue:优先任务队列 带有优先级的任务队列,其他队列都是按照先进先出的队列处理任务,而PriorityBlockingQueue是按照任务优先级顺序先后执行。
6.ThreadFactory可以设置创建线程的类型,比如守护线程,线程的优先级
7.RejectedExecutionHandler四种拒绝策略 条件当线程池和任务队列都满的情况下
(1)AbortPolicy:直接抛出异常
(2)CallerRunsPolicy:调用者所在线程运行任务 比如main线程调用线程池 当线程池满的时候。新的任务会交给调用者去执行 即交给main线程去执行
(3)DiscardOldestPolicy:丢弃队列中最近的一个任务,并执行当前最近的任务 即移除任务队列中等待最久的任务 也就是最近的一个任务
(4)DiscardPolicy:直接丢弃 没有任何反馈
(5)自定义拒绝策略 扩展RejectedExecutioHandler接口
线程池的几种状态
1.running 处于这个状态的线程池可以接受新的任务,以及对已经添加的状态进行处理 线程池初始化时就已经时running状态
2.shutdown 处于这个状态的线程池不接受新的任务,但是可以处理已经添加的任务 shutdown()方法。 running -> shutdown
3.stop 处于此状态的线程池不接受新的任务,也不处理已经处理的任务 shutdownNow()方法 running/shutdown -> stop
4.tidying 所有任务都已经终止,任务数量为0时 shutdown/stop -> tidying
5.terminated 线程彻底终止 tidying -> terminated
线程池的种类
1.newCachedThreadPool 可缓存的线程池 如果线程池的长度超出处理需要时 先判断是否有空闲的线程可以回收使用 如果没有则创建新的线程 特点:线程池无限大 当执行第二个任务时第一个任务已经完成则复用第一个任务的线程
2.newFixedThreadPool 定长的线程池 可以控制线程的最大并发数。 特点:比如有一个线程池长度为3的线程 有9个任务需要处理 任务则会分成三次处理
3.newScheduledThreadPool 定长的线程池 支持任务周期性执行 或者定时执行
4.newSingleThreadExecutor 创建一个单线程线程池 它只会有唯一的工作线程来处理任务,可以保证任务的顺序执行。即FIFO
线程池执行的方法
excute() 提交任务给线程池
submit() 提交任务给线程池,可以返回执行结果 即返回Future
线程池优化
1.使用newSingleThreadExecutor(x) x并不是越大越好 也就是核心线程数 如果线程数过多 会造成上下问切换问题 也会造成性能问题
使用队列的几种模式:
长队列+小maximumPoolSize 会减少cpu上下文的切换,但是会降低吞吐量
短队列+大小maximumPoolSize 会增加cpu上下文的切换,但是会增加吞吐量
即 如果时io密集型的需求可以增加线程的数量,减少阻塞。 参考值 2倍的cpu数量 cpu密集型的可以减少线程池的数量,减少上下文的切换 参考值 cpu+1
2.尽量避免Executor框架创建线程池
newFixedThreadPool newSingleThreadExecutor
允许的请求队列的长度为 Integer.MAX_VALUE 可能会堆积大量的请求 从而导致OOM
newCachedThreadPool newScheduledThreadPool
允许线程创建的最大长度为 Integer.MAX_VALUE 可能会堆积大量的线程 从而导致OOM
3.逻辑 发生异常时 要处理 否则会影响线程的执行
-------------------------------------------------------------------------------------------------------------------------------------------------
20190904
java中锁的知识点
java中的主流锁:
1.根据要不要锁住同步资源
悲观锁和乐观锁并不是真正的锁,多个线程操作共享资源的角度
(1)悲观锁
一个线程在操作数据的时候,认为一定有别的线程来修改数据,因此在操作数据的时候先加一把锁,只有在数据操作完成后才会释放锁。synchronized 和 lock 都是悲观锁 在数据库体现 select ... for update
(2)乐观锁
一个线程在操作数据的时候,认为别的线程不会来修改数据,它不会添加锁,只是会在操作数据的时候判断数据有没有被其他线程修改,如果没有被修改,则当前线程更新数据
如果已经被其他线程更新,则根据不同的实现方式执行不同的操作,比如报错或者重试 乐观锁在java中通过无锁编程来实现 CAS算法 *在数据库中提现 加版本号 version
悲观锁适合写操作的场景,加锁能够保证数据的正确性
乐观锁适合读操作比较多的场景,不加锁可以使读操作的性能大幅度提升
CAS算法是一种无锁的算法,CAS算法涉及到三个操作数 内存中值V 当前进行比较的值A 要更新的值B 当且仅当A和V的值相同时,才会把值B更新到内存里
CAS的缺点:
(1)ABA问题 CAS需要在操作值的时候检查内存值是否存在变化,没有变化才会更新内存中的值,如果内存值从A更新到了B然后再更新到了A,CAS在检查时发现值并没有更新,实际上内存中的值已经更新了。
解决ABA问题,jdk1.5开始提供了AtomicStampedRefrence类解决ABA问题,引入版本号机制,只有当当前引用等于预期值并且当前版本号等于预期版本号的时候,才会更新内存中的值
(2)循环时间长 浪费cpu资源 CAS操作如果长时间不成功,会导致一致自旋,给CPU带来很大的开销
(3)只能保证一个共享变量的原子操作 对一个共享变量进行操作时,CAS能够保证原子操作,但是对多个共享资源进行操作时,是无法保证原子操作的
2.锁住资源失败,线程要不要阻塞
不阻塞 自旋锁
阻塞和唤醒一个java线程需要操作系统切换cpu的状态来完成,cpu转换需要耗费处理器的时间,对于那些状态转换消耗的时间比用户代码块执行时间长的情况下,可以让当前线程不必立刻进入阻塞状态
而是让线程进行自旋操作,如果在自旋完成后前面锁定资源的线程释放了锁,那么线程可以不必要阻塞直接或得锁,避免切换线程的开销。自旋锁实现原理同样也是CAS算法,do-while循环就相当于一个
自旋操作。
自旋锁在JDK1.4.2中引入,使用-XX:+UseSpinning来开启。JDK 6中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。
自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,
自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,
那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。
3.多个线程竞争同步资源的流程和细节区别
这四种锁实际上是指所得状态的四种状态,针对synchronized。
Hotspot的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。
Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。
Monitor Monitor可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。
Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。
(1)无锁 没有对资源进行锁定,所有线程都能访问并修改同一个资源,但是只能有一个线程成功。
(2)偏向锁 同一段代码一直被一个线程所访问,那么线程会自动获取锁,降低获取锁的代价。mark word 会有一个偏向锁的开关标志位 以及一个存储thread信息的区域 如果当前线程和储存中的线程id不一致
即是否有其他线程竞争时,如果发生竞争会释放锁资源,偏向锁会升级为轻量级锁。
(3)轻量级锁 开启了轻量级锁标志位 关闭偏向锁的标志位 清楚thread的线程信息 当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。
(4)重量级锁 所有线程在争抢锁的时候会进入阻塞阶段 加入了 monitor 锁监视器
四种状态的切换 实际上都是jvm提高程序的效率 jvm对我们加锁时的优化 同样还有锁消除 锁粗化之类的优化方式
4.多个线程竞争要不要排队
(1)公平锁 需要排队
(2)非公平锁 不需要排队 synchronized
5.一个线程多个流程能不能获得一把锁
(1)重入锁 可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞
(2)非重入锁
6.多个线程能不能共享同一把锁
(1)共享锁(读锁) 该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。
(2)独享锁(写锁) 是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。
https://blog.csdn.net/zqz_zqz/article/details/70233767
-------------------------------------------------------------------------------------------------------------------------------------------------
20190905
多线程知识点
线程的状态
1.New new一个thread 但是未调用start方法的状态
2.Runnbled 可运行线程的状态,等待cpu调度
3.Waiting 等待线程的状态,当线程满足某一条件才会进入 Runnabled状态 存放在 waitSet里 调用 object.wait object.join LockSupport.park
4.Timed Waiting 带有超时时间的等待状态
5.Blocked 线程阻塞状态 等待其他线程释放锁 存放在entrySet里
6.Terminated 终止线程的线程状态,线程正常执行完成或者是出现异常时的状态
wait和sleep的区别
1.wait 是来自object类 sleep来自于Thread类
2.wait notify 和 notifyAll 必须配合同步代码块使用 synchronsized
3.sleep 必须捕获异常 wait 不需要捕获异常
4.sleep方法 是让线程进入睡眠状态,等待一定时间之后,自动醒来变成runnabled状态,但是不一定立刻运行,而是要等到cpu调度。sleep方法不会释放锁资源,其他等待锁的线程依旧需要
线程醒来运行完成之后释放锁资源,线程在sleep的过程中,被其他线程调用他得interrupt(),产生InterruptedException异常,如果不被捕获,线程就会异常终止,进入terminated状态
如果程序捕获了这个异常,那么程序会继续执行catch后面的代码块以及后面的程序代码块
5.sleep()是一个静态方法 只对当前线程有效
6.wait()是属于object的成员方法,必须采用notify()和notifyAll()唤醒线程,调用wait()方法后,线程会释放锁资源
补充:如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException。
yield()和join()的区别
yield() 让同等优先权的线程或更高优先级的线程有执行的机会。如果没有的话,那么yield()方法将不会起作用,并且由可执行状态后马上又被执行。
join() 某一个线程的执行过程中调用另一个线程执行,等到被调用的线程执行结束后,再继续执行当前线程。
线程终止
stop()方式 强行终止线程 会导致线程的安全性 jdk废弃
interrupt()方式 如果目标正在调用object class的wait() join() sleep() 方法时会被阻塞。 那么interrupt会生效,改线程终断现象将会停止,抛出interrupt异常 如果是被io 或者被nio的channel所阻塞,io操作会被中断或者返会一个异常值。
线程标志位 volatile 修饰的标志位。
线程通信 四种方式:
文件系统
网络共享
共享变量 volatile关键字
jdk提供的api
suspend/resume(废弃)挂起时不会释放锁 suspend要比resume先执行
object wait/notify wait方法导致当前线程等待,加入该对象的等待集合中,并释放锁 虽然wait()可以自动解锁,但是对顺序有要求,如果再notify后调用,线程会永远处于等待状态。
LockSupport park/unpark park/unpark没有顺序要求,但是park并不会释放锁,所有再同步代码中使用要注意 令牌机制
ReentrantLock 结合 Condition
JUC包下 countDownLatch
使用时 要使用while循环去判断 不要用if 因为有伪唤醒机制的存在
线程封闭:
并不是所有的多线程都是访问共享数据的,数据被封闭在各自的线程中。
ThreadLocal<T> var = new Threadlocal<T>();
会自动的在每个线程上创建一个T的副本,副本之间彼此不会影响,可以再threadlocal中存储一些参数,以便在线程中多个方法中使用,用来代替方法传参。
简单的理解:jvm维护了一个Map<Thread,T>,每个线程要使用这个T的时候,需要当前线程去Map里取。
局部变量也是线程封闭的一种
多线程编程的好处
多个线程并发执行提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态,其实就是提高cpu的利用率,减少代码的执行时间。
如何java中实现多线程
1.extends Thread
2.implements Runnable接口
3.futureTask 和 callable 配合使用 这个是异步或得线程执行结果的一种办法
start()和run()的区别
调用start方法会启动一个新的线程,执行run方法。调用run方法不会启动一个新的线程而是在当前线程上运行run方法
notify和notifyAll的区别
notify 随机唤醒一个等待的线程 notify他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁,此时如果该对象没有再次使用notify语句,即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。
notifyAll 唤醒所有等待的线程 notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里,为什么不放在Thread类里面
不把它放在Thread类里的原因,一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得,简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象
Java的每个对象中都有一个锁(monitor,也可以成为监视器)并且wait(),notify()等方法用于等待对象的锁或者通知其他线程对象的监视器可用
在Java的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是Object类的一部分,这样Java的每一个类都有用于线程间通信的基本方法
进程间通讯方式
管道 信号量 共享内存 信号 消息队列 套接字(socket)
--------------------------------------------------------------
20190909
jvm知识点
jvm虚拟机:一种能够运行java字节码的虚拟机。 jvm有很多,不只是hotspot,还有Jockit,J9等等。
jvm有三个子系统构成:
1.类加载子系统
2.运行时数据区(内存结构)
3.执行引擎
jvm内存结构
1.堆
2.本地方法栈
3.栈
4.方法区
5.程序计数器
堆和方法区是线程间共有的
程序计数器,栈,本地方法栈是线程间私有的
栈是程序运行时运行单位:
栈帧 一个方法会生成一个栈帧
1.局部变量表 基本数据类型和对象引用
2.动态链接 在程序运行的过程中,将符号引用转换成直接引用的过程
3.返回地址 方法的返回地址
4.操作数栈 存放方法中临时变量的区域
本地方法栈
和栈差不多只不过本地方法栈对应操作的是本地方法
程序计数器
记录线程间上下文切换时程序运行的字节码行号,唯一不会发生oom的区域
方法区
类中所有字段和,信息,字节码 简单来说所有定义方法的信息都保存在该区域,静态变量+常量+类信息(构造方法/接口定义)+运行时常量池都存在方法区。别名叫做非堆,目的是为了和java的堆分开。
堆是程序运行时存储单位
几乎所有的对象都在堆上分配内存,当对象无法在该空间下申请内存的时候将抛出oom异常
1.新生代
Eden -> From -> To
2.老年代
大对象和从新生代数次轻GC存活下来的对象 轻GC不会停止用户线程 fullGC 会stop the world 停掉所有的用户线程
jvm主要调优的两个最重要的参数 fullGC的次数 fullGC的时间
元空间 meta Data 是对方法区的一种实现 1.7之前方法区叫做永久代
jdk性能调优工具
jps 可以看到服务器上启动的java程序
jinfo -flags pid 打印一个项目的jvm的参数和垃圾收集器
jstat -gc pid 堆里面每一个内存区域分配情况 使用情况 垃圾回收的次数和时间
jmap -histo pid > 123.txt 导出堆内存的dump的文件 jmap -dump:format=b,file=temp.hprof 也可以在设置内存溢出的时候自动导出dump文件 (项目内存很大的时候,可能会导不出来)
jvisualVM客户端程序可以分析dump文件 可以手动gc 也可以手动导出dump文件
jstack 打印线程栈的信息 分析线程中是否出现过死锁的情况。jvm线程 一个线程对应可一个操作系统的线程
gc日志是我们分析jvm调优的日志
类加载机制
类的生命周期 加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
1.加载
将.class文件从磁盘读到内存中
2.链接
(1)验证 验证字节码的正确性 比如魔数是否正确,符号引用是否正确
(2)准备 给类的静态变量分配内存,并赋予默认值
(3)解析 类装载器装入类所引用的其他所有类 静态链接 将加载的类中的引用的其他类加载进来
3.初始化
为类的静态变量赋于正确的程序编写者分配的真正的初始值,执行静态代码块
4.使用
5.卸载 类卸载的两种方式 程序异常退出 调用system.exit()
类加载器的种类
1.bootstrap classloader 启动类加载器 负责加载jre核心类库 如jre/lib下的rt.jar ,charsets.jar 等
2.extension classloader 负责加载jre扩展目录下的jre/lib/ext 下的jar包
3.application classloader 负责加载classpath路径下的类包
4.用户自定义类加载器 user classloader 负责加载用户自定义路径下的类包
类加载机制
全盘负责委托机制
当一个classloader加载一个类时,除非显示的 使用另一个classloader,该类所依赖和引用的类也由这个classloader载入
双亲委派机制
先委托父类加载器寻找目标类,在找不到的情况下 载人自己路径中查找并载入目标类
优点
沙箱安全机制:保证了安全性,防止核心库被篡改
防止类重复加载 当父class loader已经加载了该类,就不需要要子classloader再加载一次了
如何判断对象可以被回收
堆中几乎放着所有对象的实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡,即不能再被任何途径使用的对象
引用计数法 可达性分析法(GCRoot)
引用计数器:
给对象添加一个引用计数器,每当有一个地方引用,计数器就会+1,当引用失效,计数器就-1. 任何计数器为0的对象都是不可再被使用的。对象间互相引用的问题,导致引用计数器不为0,GC无法被回收。
可达性分析算法:
基本思想通过一系列称为“GC root”的对象作为起点,从这些节点开始向下搜索,节点走过的路径被称为引用链,当一个对象到GC root没有任何引用链的话,则证明对象不可用
gc root 根节点:类加载器,thread,虚拟机栈的局部变量表,static成员,常量引用,本地方法栈的变量等等
如何判断一个常量废弃常量
运行时常量池主要回收的废弃常量,假如常量池中存在字符串“abc” 如果当前没有任何string对象引用该字符串常量的话,就说明常量“abc”就是废弃常量。
如何判断一个类为无用的类
1.该类的所有实例都已经被回收,也就是java堆中不存在该类的任何实例
2.加载该类的classloader已经被回收
3.该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
注意:只是可以被回收而不是一定会回收
如果不想类不被回收 对象自救 重写类的finalize()方法
垃圾回收算法
复制算法 标记清除 标记整理算法 分代收集算法
标记清除算法
缺点:效率问题,标记和清除两个过程效率都不高 空间问题 会产生大量的不连续的碎片 不是连续的内存的空间会导致大对象无法放下 会发生GC 不能用到老年代
复制算法
为了解决效率问题,可以把内存分为大小相同的两块区域,每次只使用其中的一块,当这一块内存使用完成后,就将还存活的对象复制到另一块区域,然后再把使用的空间一次清理掉,这样每次内存回收都是对内存区域的一半进行回收。新生代适合用 from -> to s0 -> s1
标记整理算法
根据老年代的特点提出的一种标记算法,标记过程和标记-清除算法一样,但是后续步骤不是直接对可回收对象进行回收,而是将存活的对象像一段移动,然后直接清除边界以外的内存 缺点:效率不是特别高
停顿时间本质上垃圾回收算法的原因
分代收集算法
一般将java堆分为新生代和老年代,我们可以根据各个年代的特点使用适合自己的垃圾回收算法 比如 年轻代:对象存活几率低,对象占用空间比较小适合复制算法 老年代 对象存活几率比较高,并且没有额外的内存空间对它进行分配担保,所以适合使用垃圾标记-清除算法或者标记-整理算法
eden区适合标记清除算法
s0 和 s1 适合复制算法
老年代 标记整理算法
分配担保机制 实际上就是大对象会直接进入老年代
垃圾收集器 互联网项目现在基本都是用G1
serial 串行收集器 stop the world 单线程收集器 停掉所有的用户线程 然后运行垃圾回收线程。新生代采用的是复制算法,老年代采用的是标记-整理算法 优点:简单高效。缺点:不良的用户体验
parNew 是串行serial收集器的对象程版本 新生代采用复制算法,老年代采用标记-整理算法 停顿也会长 吞吐量也不高
parallel scavenge收集器 一般都是默认的jdk8 关注点在吞吐量上,高效率的利用CPU。 CMS等垃圾收集器关注点更多的关注是用户线程停顿的时间。所谓吞吐量就是cpu中运行用户代码的时间与cpu总消耗时间的比值。parallel scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量。 新生代采用复制算法,老年代采用标记-整理算法。
serial old 针对于老年代的单线程垃圾收集器。一种用途是在jdk1.5以及以前的版本中与parallel scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案
parallel old parallel scavenge收集器的老年代版本,使用多线程和标记-整理算法,在注重吞吐量和cpu资源场合可以选择此垃圾收集器
CMS收集器是一种获得最短回收停顿时间为目标的收集器。注重用户体验。是hotspot虚拟机第一款真正意义上的并行收集器,它第一次让垃圾收集线程和用户线程同时工作。
步骤:
1:初始标记,暂停所有其他线程,并记录下直接与root相连的对象,速度很快
2:并发标记,同时开启GC和用户线程。
3:重新标记,修正并发标记期间因用户程序继续运行产生变动的那一部分标记记录,停顿时间一般比初始标记时间长,但是比并发标记时间短的多。
4:并发清除,开启用户线程,同时GC线程开始对标记区域进行回收。
CMS优点:并发收集,低停顿。
缺点
1:对cpu资源敏感。对cpu要求比较高 即硬件要求比较高
2:无法处理浮动垃圾 并发清理的时候会产生浮动垃圾
3:它适应的是标记清除-算法会导致大量空间碎片产生
G1收集器:
一款面对服务器的垃圾收集器。分成了很多region 大小相同 多了一个humongous的区域 这一块相当于分配担保的区域 当对象比较大时 进入humongous区域
特点:
1.并行和并发 充分理由cpu,多核环境下的硬件优势,使用多个cpu来缩短stw的时间。
2.分代收集 虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。与CMS的标记-清理算法不同,G1从整体上看是基于标记整理算法的,从局部上看基于复制算法
3.可预测停顿时间 G1收集器的优势,G1收集器除了可以追求停顿时间比较低还可以预测停顿的时间模型,能让使用者明确指定在一个长度为多少毫秒的时间段内。
步骤
1.初始标记
2.并发标记
3.重新标记
4.筛选回收
G1收集器主要处理的是mixGC fullGC是项目出现问题的上才会出现。
并发量比较高的服务器项目适合G1收集器。
并发和并行的概念
并行:多条垃圾回收线程并行工作,但是用户线程处于等待状态
并发:垃圾回收线程和用户线程同时执行,但是不一定并行,有可能交替执行,用户程序在继续执行,而垃圾回收收集器运行在另外一个cpu上。
怎么选择垃圾回收器
1.优先调整堆大小让服务器自己选择
2.如果内存小于100M,使用串行收集器
3.如果是单核,并且没有停顿时间的要求,可以使用串行,或者jvm自己选择
4.如果允许停顿时间超过1秒,选择并行或者jvm自己算则
5.如果响应时间最重要,并且不能超过1秒,使用并发收集器
官方推荐使用G1收集器,性能高
调优
jvm调优主要是调整下面两个指标:
停顿时间:垃圾收集器做垃圾回收中断应用执行的时间。-XX:MaxGCPauseMillis
吞吐量:垃圾回收的时间和总时间的占比:1/(1+n) 吞吐量 1-1/(1+n)。 -XX:GCTimeRatio=n
GC调用步骤:
1.打印GC日志 设置启动参数
-XX:+printGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -xloggc:./gc.log
2.分析日志得到关键性的指标 使用GC工具 GC Easy
3.分许GC原因,调优jvm参数
-----------------------------
20190916
rebbitMQ知识点:
1.什么是JMS?
JMS是java平台面向消息中间件的技术规范,它便于消息系统中java应用程序进行消息交换,并且通过提供标准产生,发送消息,接受消息的接口简化企业应用开发,翻译为java下消息服务
rabbitmq遵循的是amqp协议,高级消息队列协议,是进程间传递异步消息的网络协议,工作流程发布者发布消息到交换机,交换机根据路由规则将收到的消息分发给与该交换机绑定的对列,
最后amqp代理会将消息传递给订阅了此队列的消费者,或者消费者按照需求自行获取。
produce;生产者,就是投递消息的一方,生产者生产消息,然后发布到rabbitmq中。
broker: 消息中间件的服务节点
virtual host: 虚拟主机,类似mysql里不同的数据库
channel;频道或者信道,是建立在Connection连接之上的一种轻量级的链接。大部分操作是在channel这个接口中完成的,包括定义队列的声明queueDeclare,交换机的声明exchangeDeclare,
队列的绑定,queueBind,发布消息basicPublic,消息消费basicConsume等
RoutingKey:路由键,生产者将消息发给交换机时会指定一个路由key,用来指定这个消息的路由规则
exchange:交换机,生产者将消息发送到交换机,交换机将消息路由到一个或者多个队列里,如果路由不到,返回给生产者或者直接丢弃。
交换机类型:fanout 将消息发送到所有与之交换机绑定的队列上
direct 将消息发送到BindingKey和RoutingKey完全匹配的对列中
topic 宇direct类似,他可以通过匹配符进行模糊匹配
headers 不依赖路由键的匹配规则,而是根据发送消息内容中headers的属性进行匹配。
Consumer:消费者,就是接受消息的一方,消费者链接到rabbitmq,并订阅到对列上。
rabbitmq集群和高可用:
rabbitmq有两种集群方式,一种是普通集群,一种是镜像队列的形式的集群。
普通集群,集群间只会同步元数据,并不会同步队列中的消息,所谓这些元数据是指:对列的元数据,交换器的元数据,绑定的元数据,vhost的元数据。普通集群如果主节点挂了,那么整个集群
都会挂掉,非队列数据所在的节点其实只是根据元数据做路由转发的作用。RabbitMQ集群中节点包括内存节点、磁盘节点。内存节点就是将所有数据放在内存,磁盘节点将数据放在磁盘上。如果在投递消息时,打开了消息的持久化,那么即使是内存节点,数据还是安全的放在磁盘。那么内存节点的性能只能体现在资源管理上,比如增加或删除队列(queue),虚拟主机(vrtual hosts),交换机(exchange)等,发送和接受message速度同磁盘节点一样。一个集群至少要有一个磁盘节点。一个rabbitmq集群中可以共享user,vhost,exchange等,所有的数据和状态都是必须在所有节点上复制的,对于queue根据集群模式不同,应该有不同的表现。在集群模式下只要有任何一个节点能够工作,RabbitMQ集群对外就能提供服务。
默认的集群模式,queue创建之后,如果没有其它policy,则queue就会按照普通模式集群。对于Queue来说,消息实体只存在于其中一个节点,A、B两个节点仅有相同的元数据,即队列结构,但队列的元数据仅保存有一份,即创建该队列的rabbitmq节点(A节点),当A节点宕机,你可以去其B节点查看,./rabbitmqctl list_queues发现该队列已经丢失,但声明的exchange还存在。
当消息进入A节点的Queue中后,consumer从B节点拉取时,RabbitMQ会临时在A、B间进行消息传输,把A中的消息实体取出并经过B发送给consumer,所以consumer应平均连接每一个节点,从中取消息。该模式存在一个问题就是当A节点故障后,B节点无法取到A节点中还未消费的消息实体。如果做了队列持久化或消息持久化,那么得等A节点恢复,然后才可被消费,并且在A节点恢复之前其它节点不能再创建A节点已经创建过的持久队列;如果没有持久化的话,消息就会失丢。这种模式更适合非持久化队列,只有该队列是非持久的,客户端才能重新连接到集群里的其他节点,并重新创建队列。假如该队列是持久化的,那么唯一办法是将故障节点恢复起来。
镜像模式的集群 ,将队列做镜像队列,存在到多个节点上,属于rabbitmq的HA方案
消息实体会主动在镜像节点之间同步,而不是临时消息消费时临时拉取,缺点,降低性能,如果镜像对列比较多的话,加之大量的消息进入,集群之间的网络带宽会被大大消耗。
为什么RabbitMQ不将队列复制到集群里每个节点呢?这与它的集群的设计本意相冲突,集群的设计目的就是增加更多节点时,能线性的增加性能(CPU、内存)和容量(内存、磁盘)。当然RabbitMQ新版本集群也支持队列复制(有个选项可以配置)。比如在有五个节点的集群里,可以指定某个队列的内容在2个节点上进行存储,从而在性能与高可用性之间取得一个平衡(应该就是指镜像模式)。
rabbitmq持久化机制:
消息持久化 队列持久化 交换器持久化 不管是持久化的消息和非持久化的消息都可以写入磁盘,重启后还存不存在。当内存不够用的时候,非持久化的消息也会存在硬盘上,重启之后不存在。
rabbitmq内存控制:
内存阈值 配置项
内存换页 当内存达到配置值时,把目前内存的消息持久化磁盘,将内存空出来。
磁盘阈值 警告值
当内存使用配置的阈值或者磁盘剩余的空间低于配置的阈值时,Rabbitmq会暂时阻塞客户端链接,并停止接受从客户端发来的消息,避免服务崩溃,客户端和服务端心跳检测也会失效。
rabbitmq消息的可靠性:
分为三步:
1:生产者到rabbitmq传输时发生消息丢失
解决办法:confirm模式,每次发送消息,rabbitmq处理后会返回一个对应的回执信息,失败重发。
2:rabbitmq挂掉了导致消息丢失
解决办法:消息持久化
3:消费者在消费的时候挂掉了导致消息丢失
解决办法:关闭自动确认,开启手动确认消息正确消费 异常消费可以设置参数requeue true 重发,false不重发 丢弃和移动到死信队列。
死信队列:
死信队列和普通的队列基本没有区别,就是概念意义上的存储失败消息的队列,死信队列可以和消息过期时间配合使用组成延时队列进行业务使用。
如何确定消息解决消息重复消费问题
解决重复消费问题,主要是在代码业务逻辑上控制,我们不能保证消息不被重发,因为各种补偿机制,失败重发机制的存在 我们很难保证消息不被重发,所以解决问题的办法有
数据库可以根据建立唯一索引,利用redis做一个拦截,乐观锁。