Java垃圾回收

概念

内存回收

因为语言的特性,Java不需要开发者手动回收内存,这部分工作由JVM来完成,至于何时回收需要回收的对象,从理论上来说最好的时机是在对象失去引用的时候,但是做到这一点对性能影响颇大,所有Java选择了另一种方法,那就是周期性去遍历所有对象,找出需要回收的对象然后做回收操作。

分代

正如上文所说,对象失去引用在回收线程发现它之前仍将存在一段时间,但是Java希望没用的对象能尽快被回收,比较简单的方式是提高回收线程的执行频率,但是这将导致性能问题,对象的生命周期内将被检查多次,生命周期长的对象将被无谓的多次被检查。为了权衡两者的关系,这才催生了另一个概念:分代,不同生命周期的对象分别放在不同的区域,不同区域的回收频率不同,这大大提高系统效率。

垃圾回收算法

  • 年轻代算法:标记复制。因为年轻代的垃圾回收频率高,年轻代的回收时间应该越短越好,所以年轻代以空间换时间,使用标记复制算法。
  • 年老代算法:标记整理、标记清除。因为年老代的对象都是在年轻代多次被回收后才来的,所以JVM就认为它们的生命周期长不可测,不应该频繁被无谓检查,所以年老代往往比较大,而且正常情况下长时间不应该被检查。

垃圾回收器

年轻代

年老代

组合方式

重点介绍CMS

CMS有何缺点?

  • 减小吞吐量
  • 造成浮动垃圾,增加了碎片问题负担

CMS模式下何时会触发FullGC?

  • Concurrent-mode-failure:空间问题;Promotion-mode-failure:碎片问题
  • 持久代满,且未启用 CMSClassUnloadingEnabled
  • 显式调用System.gc,且未启用ExplicitGCInvokesConcurrent

Java类加载

JVM的三种加载器

根加载器

  • 加载lang包核心类、rt.jar

扩展加载器

  • 加载java.ext.dir目录下的jar包

系统加载器

  • 加载java.class.path下jar包

机制

除根加载器外,每个类加载器都有父加载器,当加载某个类时首先让父加载器(加载器链路)们尝试加载,如果父加载器们加载失败,则捕获异常并调用自己的findClass方法尝试加载。

依赖顺序:自定义加载器->系统加载器->扩展加载器->根加载器。

用户自定义加载器需要继承抽象类ClassLoad,创建自定义加载器时可以指定父加载器,默认的父加载器为系统加载器,如果指定为空则父加载器为根加载器。

JVM运行过程中遇到了需要加载的类,由当前类的定义类加载器(加载当前类的加载器)来加载。

命名空间

每个加载器都有自己的命名空间,命名空间由该加载器和所有父加载器加载的类构成的,可以理解为同一个类加载系统。同一个命名空间中,只存在一个名称及包名相同的Class实例,同一个命名空间内才允许类之间相互访问。

JVM之所以使用父委托机制来实现类加载,主要出于安全考虑,在同一个加载器系统中不存在同一个类名、包名相同的类,一个同样类名、包名的类不会加载第二次,因此体系中已经被加载的系统类不会被恶意代码篡改。

运行时包

定义:相同包名且由同一加载器加载的一部分类。

如果有在同一加载器系统中包名相同但由不同类加载器加载的类,他们不在同一个运行时包,所以理论上不具有相同包的权限。

运行时包的存在同样是出于安全考虑。

自定义加载器

如何实现自定义加载器?

调用抽象类ClassLoad的loadClass方法时会调用parent的loadClass方法,如果parent为空则调用根加载器的loadClass,如果父加载器方法抛出异常则调用自己的findClass方法,
该方法在ClassLoad类中默认抛出ClassNotDeError,用户需要自己实现相关find class逻辑。ClassLoad中的defineClass方法可以通过class文件的二进制数据初始化为Class对象。

JavaEE的类加载

JavaEE的类加载器默认行为是首先在Bootstrap中加载,如果加载不到则先从Webapp下面加载。JavaEE规范推荐每个模块的类加载器先加载本类加载的内容,如果加载不到才回到parent类加载器中尝试加载。
Tomcat有自定义的类加载器,父加载器为根加载器,加载时会首先加载应用下的lib,然后再加载tomcat下的lib。

JavaEE的 委派模型:
JavaEE的类加载

类加载过程

加载、连接、初始化:
类加载过程

连接阶段:
类加载过程

划时代的MySQL线程池

历史背景

当翻阅某些互联网公司的技术架构图时,可能会发现他们当中很多有一个共同的特点,那就是应用与数据库之间有一所谓的“数据代理层”(比如美团、58同城、早期的淘宝等),至于数据代理层存在的价值,一般都会谈到以下两点:

  • 分离数据操作。将许多复杂的数据操作分离到数据层实现(如:实施分库分表后访问路由、数据合并),降低业务层代码复杂度;
  • 降低MySQL连接数。由于数据层需要的机器数量远小于业务层,所以数据库连接数相对较小。尤其是对于业务量庞大,需要分库分表的系统,可能一个表被水平切分到成百上千台机器上,数量更庞大的应用服务器需要与每台MySQL连接,由此导致MySQL服务器的连接数是惊人的。

然而为实现数据代理层所付出的代价也是相当可观的,不仅需要增加该层的硬件设施、增加人员维护,由于应用层与数据层之间的传输协议普遍性能不高,与MySQL自己的二进制专用协议相比,无论在序列化性能还是数据包大小都有较大差距,因此应用层需要付出更多的代价去与数据层交互。以上这些缺点对于具有海量业务的公司来说似乎不可容忍,有没有可以解决以上问题的方式呢,答案是肯定的。
首先了解一下淘宝的TDDL。

淘宝TDDL

TDDL是淘宝应用于分库分表的项目,经过多个版本的发展,整合阿里另一个项目”CORBA”之后愈发强大。TDDL主要提供分库分表场景下动态访问路由、数据合并、流控等功能。因为TDDL在代码层面被项目依赖,依托”CORBA”强大的SQL解析能力,领域模型定位于数据库与JDBC之间,所以对业务代码无侵入,基本上实现了上文中数据层的第一个任务。

可以说如果没有MySQL线程池的话,TDDL没有任何意义,因为在TDDL模式下,应用层需要直连数据库,对上文中数据层的第二个任务并不是TDDL的能力范围,需要借助线程池来解决MySQL海量连接问题。

MySQL线程池

数据库线程池并不是MySQL的独门绝技,在5.5时代MariaDB和PerconaDB就已经实现了线程池功能,MySQL在随后的5.6社区版本当中也加入了该功能。

在MySQL5.6之前,线程模型比较简单,类似于Java的BIO模型,每个线程处理只能处理一个连接,每次MySQL请求的数据IO、业务响应都在同一个线程当中。如果集群庞大的分库分表场景下,MySQL将会产生大量的TCP连接,系统会耗费大量的时间用来做线程上下文切换、缓存换入换出以及资源竞争,如果并发量陡增(比如电商系统的秒杀场景),MySQL将直接无响应。

MySQL通过线程池所实现的线程模型与当今主流IO系统的线程模型基本一致(如Mina、Netty、Tomcat、Nginx等),简要来说就是一部分线程负责网络数据包的IO(IO线程:负责在核心态内存与用户态内存间交换数据),它们每次接受SQL请求并将任务加入队列之后立即返回,另一部分线程负责响应请求(业务线程),不断从队列获取任务并执行。IO线程使用非阻塞模型(epoll),可以创建海量连接且保持性能稳定,由于与业务线程独立,当业务线程繁忙时不会影响IO响应。两部分线程之间有队列负责缓冲任务,保护数据库引擎不会在短时间内涌入太多任务,保证正常运行。

线程池相关参数

  • thread_handling:表示线程池模型。
  • thread_pool_size:表示线程池的group个数,一般设置为当前CPU核心数目。理想情况下,一个group一个活跃的工作线程,达到充分利用CPU的目的。
  • thread_pool_stall_limit:用于timer线程定期检查group是否“停滞”,参数表示检测的间隔。
  • thread_pool_idle_timeout:当一个worker空闲一段时间后会自动退出,保证线程池中的工作线程在满足请求的情况下,保持比较低的水平。
  • thread_pool_oversubscribe:该参数用于控制CPU核心上“超频”的线程数。这个参数设置值不含listen线程计数。
  • threadpool_high_prio_mode:表示优先队列的模式。

线程池实现

线程池实现
每一个绿色的方框代表一个group,group的数量由thread_pool_size参数决定。

每个group包含一个普通队列和一个优先队列,包含一个listen线程和若干worker线程,两种线程间可以动态转换,worker线程数量由工作负载决定,同时也受thread_pool_oversubscribe参数影响。普通队列中的任务超过一定时间未被执行会被列入优先队列,而优先队列的任务会被优先执行。

整个group组有一个timer监控线程。

MySQL性能优化

摘要

与其说性能优化,其实本质上来说是根据业务场景在数据一致性和性能之间做出权衡,以及根据硬件设置做相应适配。

21世纪的TCP

关于TCP

TCP有几十年的发展历史,可以算得上一门古老的学问,虽然它并不完美也不能解决所有问题,但是足够优雅,也足够复杂。如果需要详尽地描述TCP的实现可能几本书都不够,在几十年的时间里不断在争议中发展,虽然说古老,但随着使用场景的变化目前仍然在不断革新,本文就TCP协议中的一些新理念做简要描述。

##

MYSQL索引

为什么需要索引

  • 索引的顺序性
  • 快速查找、排序、分组
  • 减少扫表数量,避免创建临时表,将随机IO变为顺序IO

B-Tree

  • MySQL的Btree实际上是balance tree基础上做了小改动,每个叶子节点都有一个指向后一个叶子节点的指针。

聚簇索引

  • 聚簇并不是一种单独的索引类型,而是一种存储方式:InnoDB的聚簇索引在同一个结构中保存了B-Tree索引和行数据,索引就是主键。而覆盖索引也是相同的模型。

高性能索引

  • 三星级索引

    • 一星:要查找的记录放在一起
    • 二星:多列索引的顺序与查找条件顺序一致
    • 三星:覆盖索引
  • 选择性

    • 定义:不重复的列数量与所有列数量的比值,最大值为1
    • 对于BLOG、TEXT、长varchar,虽然选择性很高,但mysql不允许如此大的字段,只能使用前缀索引
    • 正常情况下,多列索引应该将选择性高的列放到前面
  • 什么情况下无法使用索引

索引使用案例

SQL分析

  • explain
    • type列:官方的说法,说这列表示的是“访问类型”,更通俗一点就是:mysql找到需要的数据行的方式,由差到好:All(扫全表),index(使用索引扫全表),range(使用索引范围查询),ref(使用索引返回一行数据),const(主键查询)。
    • extra列:Only index:使用到了索引;Where used:使用到了where限制;Using filesort使用了全文排序;Using temporary使用到了临时表。当extra里显示有using filesort或using temporary时,sql的执行就会很吃力,时间就会增加。
    • keys列:使用到的索引
    • rows列:扫描的行数

你好,色彩

Hello World

新博客:GitHub + Hexo

安装Node.js、Git

1
2
3
Node.js
&
Git

配置GtiHub

1
2
3
4
5
6
7
生成密钥:ssh-keygen -t rsa -C "911@zhaoqun911.cn"

将配置到GitHub: Account Settings—->SSH Public keys —-> add another public keys(C:\Users\xx\.ssh\id_rsa.pub)

测试:ssh -T git@github.com

设置用户信息:git config --global user.name "xx"; git config --global user.email "xx"

安装、配置hexo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
npm install -g hexo

cd blog

hexo init

npm install

cd theme

git clone git@github.com:litten/hexo-theme-yilia yilia

修改配置文件:theme:yilia

hexo g

hexo server

发布

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cd public

git init

git remote add blog git@github.com:zhaoqun1021/zhaoqun1021.github.io

将所有文件备份

git pull blog master

将备份的文件再覆盖回来

git add *

git commit -m 'cc'

git push blog master

Other

1
2
3
4
5
6
7
8
9
10
11
12
13
_config.yml:
archive_dir:归档位置?
category_dir:分类URL名称?

主题:
hexo-theme-yilia

编辑器:
sublime + MarkdownEditing + MarkdownPreview
http://www.jianshu.com/p/378338f10263
插件:
package install + imesurpport
http://qianduanblog.com/post/sublime-text-3-plugin-imesupport.html