Java的线程池
一个线程池的运行流程如下:
看下面的例子:
123456789101112131415161718192021public class TestExecutionWebServer { private static final int READS = 100; private static final Executor executor = Executors.newFixedThreadPool(READS); public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(80); while (true) { final Socket socket = serverSocket.accept(); Runnable task = new Runnable() { ...
HotSpot的实现细节
根节点枚举目前为止,所有收集器在根节点枚举这一步都必须暂停用户线程。
目前可达性分析算法耗时最长的查找引用链的过程可以与用户线程一起并发,但根节点枚举需要在一个类似于一致性快照才可以进行,这里一致性是指,整个枚举根节点的过程,子系统就像被冻结在某个点,这个过程中对象间的引用关系是不能发生变化的,否则分析的结果没有意义。
在暂停用户线程时,虚拟机使用了一组称为OopMap的数据结构来避免遍历所有的执行上下文和全局引用的位置。一旦类加载完成时,HotSpot就会把对象内每个偏移量上是什么类型的数据计算出来,也会在特定位置记录下栈里和寄存器里哪些位置是引用。这样收集器在扫描时就可以直接得到这些信息,就不用遍历了。
安全点上面提到使用OopMap来避免进行遍历所有的引用,但是导致OopMap进行变化的指令特别多,如果为每一条指令都生成OopMap,那么将耗费大量的内存空间。
实际上,HotSpot并没有为每一条都指令都生成对应的OopMap,而是在特定的位置记录了这些信息,这个特定的位置就是安全点。这也就意味着用户代码并不是在任何时候都可以暂停,然后进行垃圾回收,而是必须到达安全点才可以暂停。 ...
MySQL表空间回收
数据删除流程假如现在有如下数据:
假如此时我们要删除R4的数据,InnoDB引擎只会把R4这个记录标记为删除。如果之后需要再300和600之间插入数据,可能会复用这个位置。所以这时删除一条数据时空间不会变小。
如果我们删除一整个页的数据,那么这整个数据页都可以复用。
如果删除了一整张表,那么所有的数据页都会被标记为可用。
以上这三个删除都是针对delete命令。
也就是说,delete命令只会把对应的数据标记为可以复用,下次需要写对应位置的数据时,直接覆盖掉原来的数据,并没有真正的释放空间。如果这些位置没有被复用,就会造成空间的浪费。
不仅删除会导致这些空洞,插入数据也会。因为如果插入的数据不是按照索引有序的,就有可能导致页分裂。比如数据页A已经存满了,此时又有一条数据需要插入数据页A,此时会导致B+树页分裂,会多出一个数据页,但是数据页A和新数据页都不会存满数据。
重建表如果想要消除表里面的空洞,就需要重建表。即把数据按照有序的方式从表A插入到表B当中,然后利用表B替换掉表A。在这个过程中,表A不能有更新操作。
Online DDL在MySQL 5.6之前,重建表的逻辑和上面说的类 ...
垃圾收集算法
分代收集理论分代收集理论建立在两个假说之上:
1)弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
2)强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
正是由于这两个假说,所以多款垃圾收集器都有一个设计原则:Java堆应该划分不同的区域,然后根据对象的年龄(对象熬过垃圾手机过程的次数)分配到不同的区域存储。
把那些朝生夕灭的对象放在一起存储,那么虚拟机只需要关注如果保留少量存活的对象即可,而不是大量的去进行标记。
把那些不容易被清除的对象放在一起,那么虚拟机就可以不那么频繁的去清理这部分的对象。
由于区域的划分,所以存在了Minor GC(指目标只是新生代的垃圾回收,也叫Young GC),Major GC(指的是目标老年代的垃圾回收,也叫Old GC。目前只有CMS收集器有。),Full GC(收集整个Java堆和方法区的垃圾收集)这样回收类型的划分。而且也根据不同区域对象存亡的特征,发展除了标记-复制算法,标记-清除算法,标记-整理算法。Mixed GC(混合 ...
MySQL为什么会卡一下
InnoDB在处理更新操作时,它并不会直接把数据直接写入磁盘,而是先在内存找到对应的数据页做修改,之后把修改写入redo log,然后这一次更新操作就算完成了。(这里也存在数据页不在内存的情况,那么它会直接将数据修改的操作写入change buffer,然后记录redo log)。
这些被修改过的数据页,或者说内存中的数据页与磁盘中的数据页不一样的,就叫做脏页。而MySQL卡的那一下,就是把脏页写回内存时发生的。
写回脏页的时机1、redo log满了
redo log是一个循环队列,当它写满时,系统会停止当前的操作,把redo log中记录的一部分操作所对应的脏页写回内存,然后在redo log中腾出足够的地方。
2、内存满了
在这种情况下,内存里无法加载更多的内存页,需要淘汰掉一部分内存页,而淘汰掉的内存页如果有脏页,就会把他们写入磁盘。
3、MySQL空闲时
4、MySQL正常关闭的情况。
情况1和2对性能的影响情况1是需要避免的,因为发生这种情况后,整个系统就无法处理新的请求。
情况2是一种常态,InnoDB用缓冲池(buffer pool)管理内存,缓冲池中的内存页有三种 ...
CPU结构对Redis性能的影响
主流CPU架构一个 CPU 处理器中一般有多个运行核心,我们把一个运行核心称为一个物理核。每个物理核都有私有的一级缓存(L1 cache)以及二级缓存(L2 cache),即其他物理核无法访问。
不同的物理核还会共享一个共同的三级缓存(L3 cache)
现在主流的 CPU 处理器中,每个物理核通常都会运行两个超线程,也叫作逻辑核。同一个物理核的逻辑核会共享使用 L1、L2 缓存。
一个CPU会有多个物理核,而一个服务器上可能会有多个CPU,如下图所示:
在上图的这种CPU架构中,Redis可以在不同的CPU上运行,意味着它可以现在Socket1上运行,然后被调度到Socket2上执行。
这里就会出现问题,如果应用程序先在一个 Socket 上运行,并且把数据保存到了内存,然后被调度到另一个 Socket 上运行,此时,应用程序再进行内存访问时,就需要访问之前 Socket 上连接的内存,这种访问属于远端内存访问。和访问 Socket 直接连接的内存相比,远端内存访问会增加应用程序的延迟。
每一个CPU会管理一部分内存,所以存在从Socket1切换到Socket2上存在远端访问。
...
Redis缓冲区可能引发的问题
Redis缓冲区缓冲区的作用主要就是用一块内存空间来暂时存放命令数据,以免出现因为数据和命令的处理速度慢于发送速度而导致的数据丢失和性能问题。但缓冲区的内存空间有限,如果往里面写入数据的速度持续地大于从里面读取数据的速度,就会导致缓冲区需要越来越多的内存来暂存数据。当缓冲区占用的内存超出了设定的上限阈值时,就会出现缓冲区溢出。
缓冲区在 Redis 中的一个主要应用场景,就是在客户端和服务器端之间进行通信时,用来暂存客户端发送的命令数据,或者是服务器端返回给客户端的数据结果。此外,缓冲区的另一个主要应用场景,是在主从节点间进行数据同步时,用来暂存主节点接收的写命令和数据。
客户端输入和输出缓冲区为了避免客户端和服务端的请求发送和处理速度不匹配,服务器端给每个连接的客户端都设置了一个输入缓冲区和输出缓冲区。
输入缓冲区会把客户端发送过来的命令暂存起来,Redis 主线程再从输入缓冲区中读取命令,进行处理,当Redis主线程处理完数据后,会把结果写入到输出缓冲区,再通过输出缓冲区返回给客户端。
如何应对输入缓冲区溢出可能溢出的情况:
1、写入了 bigkey,比如一下子写入了多个百万级别 ...
如何判断Java对象“已死”
JVM在进行垃圾回收前,需要判断这个对象是否还在使用,哪些是不可能再被任何途径使用的,而主要的判断方法有以下两种
引用计数法在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
这种方法的优点是原理比较简单,判定效率也高,但实际情况是想要用这种方法,需要做很多额外的工作,因为单靠引用计数法很难解决对象互相引用的问题。比如下面代码:
12345678910class A { Object instance;}class B { Object instance;}objA.instance = objB;objB.instance = objA;
尽管他们两个都不会再被使用,但是因为他们两个互相引用着对方,导致计数器的值不为0。
而实际上,很少有虚拟机会采用这种方法。
可达性分析算法这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference ...
MySQL普通索引和唯一索引
假如有一张表,它需要存储用户的身份证号以及姓名,业务层已经保证了身份证号唯一,此时有以下查询:
1select name from table where id_card = "xxxxyy"
为了提高效率,我们会在id_card上面加索引,那么到底是加唯一索引还是普通索引呢?我们可以从这两种索引对查询语句以及更新语句的性能影响来进行分析。
查询过程假设执行的语句是select id from T where k=5。对于两种索引执行过程如下:
普通索引:他会先找到k = 5的这一条数据,然后会继续往下找,直到第一条不满足k = 5的数据。
唯一索引:因为索引保证了唯一性,所以碰到第一条为5的记录后,就可以直接返回。
这一点的不同带来的性能差距很小。因为InnoDB的数据是以数据页为单位来读的,意味着读一条数据,会将那条数据所在的页都加载到内存当中,所以读取k = 5的时候,大概率后续几条数据会在同一个数据页中,此时性能差异很小。一个特殊情况是后续几条数据和k = 5不在同一个页中,那么此时效率会有点低。
更新过程 ...
this引用逃逸
构造函数中启动线程1234567891011121314151617181920212223public class ThisEscape { private String name; public ThisEscape(EventSource source) { source.registerListener(new EventListener() { public void onEvent(Event e) { doSomething(); } }); // 启动线程,此时this引用逃逸 Thread thread = new Thread() { public void run() { doSomething(); } }; thread. ...