分类 java 中的文章

Condition

Condition Condition概述 在线程的同步时可以使一个线程阻塞而等待一个信号,同时放弃锁使其他线程可以能竞争到锁。 在synchronized中我们可以使用Object的wait()和notify方法实现这种等待和唤醒。 在Lock可以实现相同的功能就是通过Condition。Condition中的await()和signal()/signalAll()就相当于Object的wait()和notify()/notifyAll()。 除此之外,Condition还是对多线程条件进行更精确的控制。notify()是唤醒一个线程,但它无法确认是唤醒哪一个线程。 但是,通过Condition,就能明确的指定唤醒读线程。 参考:https://www.cnblogs.com/qdhxhz/p/9206076.html……

阅读全文

线程的生命周期和通讯机制

线程的生命周期和通讯机制 生命周期 ###1、yield()方法 - yield()让当前正在运行的线程回到就绪,以允许具有相同优先级的其他线程获得运行的机会。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。 同时yield()不会放弃锁资源,所以有可能会出现死锁。 ###2、wait和sleep方法的区别 - 1)第一个很重要的区别就是,wait方法必须正在同步环境下使用,比如synchronized方法或者同步代码块。如果你不在同步条件下使用,会抛出IllegalMonitorStateException异常。另外,sleep方法不需要再同步条件下调用,你可以任意正常的使用。 2)第二个区别是,wait方法用于和定义于Object类的,而sleep方法操作于当前线程,定义在java.lang.Thread类里面。 3)第三个区别是,调用wait()的时候方法会释放当前持有的锁,而sleep方法不会释放任何锁。 ###3、wait和sleep方法使用场景 -(1)wait方法定义在Object类里面,所有对象都能用到,一般wait()和notify()方法或notifyAll使用于线程间的通信。 -(2)sleep()方法用于暂停当前线程的执行。 ###4、join方法() - thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。 - 比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。 ###5、stop方法 线程启动完毕后,在运行可能需要终止,Java提供的终止方法只有一个stop,但是不建议使用此方法,因为它有以下三个问题: 1)stop方法是过时的。 从Java编码规则来说,已经过时的方式不建议采用. 2)stop方法会导致代码逻辑不完整 stop方法是一种”恶意” 的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的. 3)stop方法会破坏原子逻辑 多线程为了解决共享资源抢占的问题,使用了锁的概念,避免资源不同步,但是正是因为此原因,stop方法却会带来更大的麻烦,它会丢弃所有的锁,导致原子逻辑受损 线程通讯 1.依次运行用join 2.交叉运行用wait()和notify()或者notifyAll() 3.四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的。 用CountdownLatch,主要方法latch.await(),latch.countDown(); 4.三个运动员各自准备,等到三个人都准备好后,再一起跑 用CyclicBarrier 5.子线程完成某件任务后,把得到的结果回传给主线程 用FutureTask和Callable……

阅读全文

GC算法和种类

GC算法和种类 GC的工作区域 1.不是GC的工作区域 程序计数器、虚拟机栈和本地方法栈三个区域是线程私有的,随线程生而生,随线程灭而灭; 在这几个区域不需要过多考虑回收的问题方法结束或线程结束时,内存自然就跟随着回收了。 2.GC的工作区域(哪些内存需要GC回收?) 垃圾回收重点关注的是堆和方法区部分的内存。 程序处于运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,所以垃圾回收器所关注的主要是这部分的内存。 垃圾对象的判定 Java堆中存放着几乎所有的对象实例,垃圾收集器对堆中的对象进行回收前,要先确定这些对象是否还有用,哪些还活着。对象死去的时候才需要回收。 1. 引用计数法 在堆中存储对象时,在对象头处维护一个counter计数器,如果一个对象增加了一个引用与之相连,则将counter++。 如果一个引用关系失效则counter–。如果一个对象的counter变为0,则说明该对象已经被废弃,不处于存活状态。 2. 可达性分析算法 通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,就证明此对象是不可用的。 - 可作为GC Roots的对象包括下面几种: (1)虚拟机栈(栈帧中的本地变量表)中引用的对象。 (2)方法区中的常量、类静态属性引用的对象(静态变量)。 (3)本地方法栈中JNI(Native方法)的引用对象。 垃圾回收算法 标记清除算法 (1). 标记阶段:找到所有可访问的对象,做个标记 (2). 清除阶段:遍历堆,把未被标记的对象回收 缺点: (1)因为涉及大量的内存遍历工作,所以执行性能较低 (2)对象被清除之后,被清除的对象留下内存的空缺位置会造成内存不连续,空间浪费。 标记整理算法 适合用于存活对象较多的场合,如老年代。 (1)、标记阶段:它的第一个阶段与标记/清除算法是一模一样的。 (2)、整理阶段:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。 优点:内存连续,消除复制算法内存减半的代价问题 缺点:不仅要标记所有存活对象,还要整理所有存活对象的引用地址。性能低于复制算法。 复制算法 复制算法简单来说就是把内存一分为二,但只使用其中一份,在垃圾回收时,将正在使用的那份内存中存活的对象复制到另一份空白的内存中,最后将正在使用的内存空间的对象清除,完成垃圾回收。 优点:复制算法使得每次都只对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。 缺点:复制算法的代价是将内存缩小为原来的一半,这个太要命了。 **现在的虚拟机使用复制算法来进行新生代的内存回收。因为在新生代中绝大多数的对象都是“朝生夕亡”,所以不需要将整个内存分为两个部分,而是分为三个部分,一块为Eden(伊甸区)和两块较小的Survivor(幸存区)空间(默认比例->8:1:1)。每次使用Eden和其中的一块Survivor,垃圾回收时候将上述两块中存活的对象复制到另外一块Survivor上,同时清理上述Eden和Survivor。所以每次新生代就可以使用90%的内存。只有10%的内存是浪费的。(不能保证每次新生代都少于10%的对象存活,当在垃圾回收复制时候如果一块Survivor不够时候,需要老年代来分担,大对象直接进入老年代) ** 总的来讲:复制算法不适用于存活对象较多的场合,如老年代(复制算法适合做新生代的GC) 三种算法比较 相同点: 进行GC时需要暂停应用程序。 区别: 效率:复制算法>标记-整理算法>标记-清除算法; 内存整齐度:复制算法=标记-整理算法>标记-清除算法 内存利用率:标记-整理算法=标记-清除算法>复制算法 分代收集思想 新生代:由于存活的对象相对比较少,因此可以采用复制算法该算法效率比较快。 > 新生代包含Eden(伊甸区)和两块Survivor(幸存区) 老年代:由于存活的对象比较多哈,可以采用标记-清除算法或是标记-整理算法。 java8以后,已经没有(PermGen Space)永久区了,之前永久区存放的东西基本上放到了元空间(Meta Space)中。 参考:https://www.……

阅读全文

垃圾回收器

垃圾回收器 基础概念 1、并发和并行 - a:并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 - b:并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。 2、新生代 GC 和老年代GC - a:新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。 - b:老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的)。Major GC的速度一般会比Minor GC慢10倍以上。 3、吞吐量 - 吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。 虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。 垃圾收集器 Serial收集器 新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;收集过程中服务会暂停。 参数控制:-XX:+UseSerialGC 串行收集器 ParNew收集器 新生代并行,老年代串行;新生代复制算法、老年代标记-压缩;ParNew收集器其实就是Serial收集器的多线程版本。 参数控制: -XX:+UseParNewGC ParNew收集器 -XX:ParallelGCThreads 限制线程数量 Parallel Scavenge收集器 新生代复制算法。老年代标记压缩,串行 参数控制:-XX:+UseParallelGC Serial Old收集器 它是Serial收集器的老年代版,它同样是一个单线程收集器,使用“标记–整理”算法。 Parallel Old 收集器……

阅读全文

性能调优

性能调优 调优策略 调优的目的 调优的最终目的当然增大吞吐量,减少暂停时间,映射到GC层面主要关心下面这两点: (1)将转移到老年代的对象数量降低到最小。 (2)减少full GC的执行时间。(尽量减少GC的次数) 那什么情况对象会转移到老年代 主要有这四种: (1)新生代对象每经历依次minor gc,年龄会加一,当达到年龄阀值会直接进入老年代。阀值大小一般为15。 (2)Survivor空间中年龄所有对象大小的总和大于survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而无需等到年龄阀值。 (3)大对象直接进入老年代。 (4)新生代复制算法需要一个survivor区进行轮换备份,如果出现大量对象在minor gc后仍然存活的情况时,就需要老年代进行分配担保,让survivor无法容纳的对象直接进入老年代。 再来分析为什么说要减少full GC时间次数,那得先看GC的两大分类 Partial GC:并不收集整个GC堆的模式 Young GC:只收集young gen的GC Old GC:只收集old gen的GC。只有CMS的concurrent collection是这个模式 Mixed GC:收集整个young gen以及部分old gen的GC。只有G1有这个模式 Full GC:针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。 一般Full GC所花费的时间是Young GC的十倍,这里就明白为什么要减少Full GC的次数了。 哪些方面可以考虑调优? (1)减少使用全局变量和大对象。 (2)新生代和老年代的大小是否合适。 (3)新生代和老年代所占的比例是否合适。 (4)幸存者区和新生区所占的比例到是否合适。 (5)选择合适的GC收集器。 什么情况说明GC已经不错了呢? 此外,如果GC执行时间满足下列所有条件,就没有必要进行GC优化了: Minor GC执行非常迅速(50ms以内) Minor GC没有频繁执行(大约10s执行一次) Full GC执行非常迅速(1s以内) Full GC没有频繁执行(大约10min执行一次) 括号中的数字并不是绝对的,它们也随着服务的状态而变化。 二、调优经验(规则) 这些规则,一般是大家比较建议的,可以作为初始配置的时候进行配置建议,当然具体的还得通过JVM工具监测来具体分析。 (1) -Xmx 和-Xms 一般设置为一样大小。这样能稍微提高GC的运行效率,因为他/她不再需要估算堆是否需要调整大小了。 (2)官方推荐新生代占堆的3/8。 (3)幸存区占新生代的1/10。 (4)垃圾收集器如果内存比较大建议G1收集器,当然也可以用CMS收集器。 (5)-XX:+DisableExplicitGC禁止System.……

阅读全文

系统高并发

系统高并发 1.集群化部署 采用nginx负载均衡将请求分发到多台机器上。 2.数据库分库(比较麻烦)分表分区、读写分离 3.缓存集群 写数据库的时候同时写一份数据到缓存集群里,然后用缓存集群来承载大部分的读请求。这样的话,通过缓存集群,就可以用更少的机器资源承载更高的并发。 4.消息中间件 可以引入消息中间件集群,把允许异步化的请求请求写入MQ,然后基于MQ做一个削峰填谷。……

阅读全文

OOM

Java OOM 1.什么是OOM OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个erro 2.为什么会OOM、出现的原因是什么? 分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。 应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。 3.解决办法 java.lang.OutOfMemoryError: Java heap space ——>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。 java.lang.OutOfMemoryError: PermGen space ——>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。 java.lang.StackOverflowError ——> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小 ps:JVM调优参数 -Xms:为jvm启动时分配的内存,比如-Xms200m,表示分配200M -Xmx:为jvm运行过程中分配的最大内存,比如-Xms500m,表示jvm进程最多只能够占用500M内存 -Xss:为jvm启动的每个线程分配的内存大小,默认JDK1.4中是256K,JDK1.5+中是1M 参考:https://blog.csdn.net/weixin_41835916/article/details/81558310……

阅读全文

Java内存模型

Java内存模型 1.java程序执行过程: >Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存。因此,在Java中我们常常说到的内存管理就是针对这段空间进行管理(如何分配和回收内存空间)。 2.运行时数据区包括哪几部分: 2.1 方法区(Method Area) 方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。 方法区里存放着类的版本,字段,方法,接口和常量池。常量池里存储着字面量和符号引用。符号引用包括: 1.类的全限定名 2.字段名和属性 3.方法名和属性。 2.2 JVM堆(Java Heap) Java 堆也是属于线程共享的内存区域,它在虚拟机启动时创建,是Java 虚拟机所管理的内存中最大的一块,主要用于存放对象实例,几乎所有的对象实例都在这里分配内存,注意Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做GC堆,如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。 2.3 程序计数器(Program Counter Register): 字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 多线程中,为了让线程切换后能恢复到正确的执行 位置,每条线程都需要有一个独立的程序计数器,各条线程之间互不影响、独立存储,因此这块内存是线程私有的。 2.4 虚拟机栈(Java Virtual Machine Stacks): Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链表、方法出口信息等。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 2.5 本地方法栈(Native Method Stacks): 本地方法栈属于线程私有的数据区域,这部分主要与虚拟机用到的 Native 方法相关,一般情况下,我们无需关心此区域。 参考:http://blog.csdn.net/javazejian/article/details/72772461……

阅读全文

布隆Bloom过滤器

1.什么是布隆过滤器? 一个名叫 Bloom 的人提出了一种来检索元素是否在给定大集合中的数据结构,这种数据结构是高效且性能很好的,但缺点是具有一定的错误识别率和删除难度。并且,理论情况下,添加到集合中的元素越多,误报的可能性就越大。 >位数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1。这样申请一个 100w 个元素的位数组只占用 1000000Bit / 8 = 125000 Byte = 125000⁄1024 kb ≈ 122kb 的空间。 > bit数组 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2.布隆过滤器的原理 当一个元素加入布隆过滤器中的时候,会进行如下操作: 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。 根据得到的哈希值,在位数组中把对应下标的值置为 1。 当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作: 对给定元素再次进行相同的哈希计算; 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。 布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。 3.使用Google开源的 Guava中自带的布隆过滤器 创建了一个最多存放 最多 1500个整数的布隆过滤器,并且我们可以容忍误判的概率为百分之(0.……

阅读全文

Java缓存

缓存 1.基本思想 避免用户在请求数据的时候获取速度过于缓慢,所以我们在数据库之上增加了缓存这一层来弥补。 2.本地缓存解决方案 常见的有Ehcache、guavaCache、Caffeine Cache等,性能最优的为Caffeine cache。 常见的单体架构使用 Nginx 来做负载均衡,部署两个相同的服务到服务器,两个服务使用同一个数据库,并且使用的是本地缓存。 3.为什么要用分布式缓存?而不用本地缓存? 本地缓存存在局限性: 本地缓存对分布式架构支持不友好,比如同一个相同的服务部署在多台机器上的时候,各个服务之间的缓存是无法共享的,因为本地缓存只在当前机器上有。 本地缓存容量受服务部署所在的机器限制明显。 如果当前系统服务所耗费的内存多,那么本地缓存可用的容量就很少。 4.缓存读写模式/更新策略/处理流程 Cache Aside Pattern(旁路缓存模式) 读:从 cache 中读取数据,读取到就直接返回 。读取不到的话,先从 DB 加载,写入到 cache 后返回响应。 写:更新 DB,然后直接删除 cache 。 Read/Write Through Pattern(读写穿透) 读:从 cache 中读取数据,读取到就直接返回 。读取不到的话,先从 DB 加载,写入到 cache 后返回响应。 写:先查 cache,cache 中不存在,直接更新 DB。 cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(同步更新 cache 和 DB)。 Write Behind Pattern(异步缓存写入) 读:从 cache 中读取数据,读取到就直接返回 。读取不到的话,先从 DB 加载,写入到 cache 后返回响应。 写:无论是否存在,都直接跟新缓存,最好异步批量的方式来更新 DB。 ……

阅读全文