MySQL索引和查询优化

索引相关

索引基数

基数是数据列所包含的不同值的数量,例如,某个数据列包含值 1、3、7、4、7、3,那么它的基数就是 4。

索引的基数相对于数据表行数较高(也就是说,列中包含很多不同的值,重复的值很少)的时候,它的工作效果最好。

如果某数据列含有很多不同的年龄,索引会很快地分辨数据行;如果某个数据列用于记录性别(只有“M”和“F”两种值),那么索引的用处就不大;如果值出现的几率几乎相等,那么无论搜索哪个值都可能得到一半的数据行。

在这些情况下,最好根本不要使用索引,因为查询优化器发现某个值出现在表的数据行中的百分比很高的时候,它一般会忽略索引,进行全表扫描。惯用的百分比界线是“30%”。

索引失效原因

索引失效的原因有如下几点:

  • 对索引列运算,运算包括(+、-、*、/、!、<>、%、like’%_'(% 放在前面)。
  • 类型错误,如字段类型为 varchar,where 条件用 number。
  • 对索引应用内部函数,这种情况下应该要建立基于函数的索引。例如 select * from template t where ROUND (t.logicdb_id) = 1,此时应该建 ROUND (t.logicdb_id) 为索引。

MySQL 8.0 开始支持函数索引,5.7 可以通过虚拟列的方式来支持,之前只能新建一个 ROUND (t.logicdb_id) 列然后去维护。

  • 如果条件有 or,即使其中有条件带索引也不会使用(这也是为什么建议少使用 or 的原因),如果想使用 or,又想索引有效,只能将 or 条件中的每个列加上索引。
  • 如果列类型是字符串,那一定要在条件中数据使用引号,否则不使用索引。
  • B-tree 索引 is null 不会走,is not null 会走,位图索引 is null,is not null 都会走。
  • 组合索引遵循最左原则。

索引的建立

索引的建立需要注意以下几点:

  • 最重要的肯定是根据业务经常查询的语句。
  • 尽量选择区分度高的列作为索引,区分度的公式是 COUNT(DISTINCT col) / COUNT(*),表示字段不重复的比率,比率越大我们扫描的记录数就越少。
  • 如果业务中唯一特性最好建立唯一键,一方面可以保证数据的正确性,另一方面索引的效率能大大提高。

EXPLIAN 中有用的信息

基本用法

EXPLIAN 基本用法如下:

  • desc 或者 explain 加上你的 SQL。
  • extended explain 加上你的 SQL,然后通过 show warnings 可以查看实际执行的语句,这一点也是非常有用的,很多时候不同的写法经 SQL 分析后,实际执行的代码是一样的。

提高性能的特性

EXPLIAN 提高性能的特性如下:

  • 索引覆盖(covering index):需要查询的数据在索引上都可以查到不需要回表 EXTRA 列显示 using index。
  • ICP特性(Index Condition Pushdown):本来 index 仅仅是 data access 的一种访问模式,存数引擎通过索引回表获取的数据会传递到 MySQL Server 层进行 where 条件过滤。

5.6 版本开始当 ICP 打开时,如果部分 where 条件能使用索引的字段,MySQL Server 会把这部分下推到引擎层,可以利用 index 过滤的 where 条件在存储引擎层进行数据过滤。

EXTRA 显示 using index condition。需要了解 MySQL 的架构图分为 Server 和存储引擎层。

  • 索引合并(index merge):对多个索引分别进行条件扫描,然后将它们各自的结果进行合并(intersect/union)。

一般用 or 会用到,如果是 AND 条件,考虑建立复合索引。EXPLAIN 显示的索引类型会显示 index_merge,EXTRA 会显示具体的合并算法和用到的索引。

Extra 字段

Extra 字段使用:

  • using filesort:说明 MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。

MySQL 中无法利用索引完成的排序操作称为“文件排序”,其实不一定是文件排序,内部使用的是快排。

  • using temporary:使用了临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。
  • using index:表示相应的 SELECT 操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错。
  • impossible where:where 子句的值总是 false,不能用来获取任何元组。
  • select tables optimized away:在没有 group by 子句的情况下基于索引优化 MIN/MAX 操作或者对于 MyISAM 存储引擎优化 COUNT(*) 操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
  • distinct:优化 distinct 操作,在找到第一匹配的元组后即停止找同样值的操作。

using filesort、using temporary 这两项出现时需要注意下,这两项是十分耗费性能的。

在使用 group by 的时候,虽然没有使用 order by,如果没有索引,是可能同时出现 using filesort,using temporary 的。

因为 group by 就是先排序在分组,如果没有排序的需要,可以加上一个 order by NULL 来避免排序,这样 using filesort 就会去除,能提升一点性能。

type 字段

type 字段使用:

  • system:表只有一行记录(等于系统表),这是 const 类型的特例,平时不会出现。
  • const:如果通过索引依次就找到了,const 用于比较主键索引或者 unique 索引。因为只能匹配一行数据,所以很快。如果将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量。
  • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
  • ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而它可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体。
  • range:只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引,一般就是在你的 where 语句中出现 between、<、>、in 等的查询。
  • 这种范围扫描索引比全表扫描要好,因为只需要开始于缩印的某一点,而结束于另一点,不用扫描全部索引。
  • index:Full Index Scan ,index 与 ALL 的区别为 index 类型只遍历索引树,这通常比 ALL 快,因为索引文件通常比数据文件小。
  • 也就是说虽然 ALL 和 index 都是读全表,但 index 是从索引中读取的,而 ALL 是从硬盘读取的。
  • all:Full Table Scan,遍历全表获得匹配的行。

字段类型和编码

MySQL 返回字符串长度

CHARACTER_LENGTH(同CHAR_LENGTH)方法返回的是字符数,LENGTH 函数返回的是字节数,一个汉字三个字节。

varchar 等字段建立索引长度计算语句

select count(distinct left(test,5))/count(*) from table;越趋近 1 越好。

MySQL 的 utf8

MySQL 的 utf8 最大是 3 个字节不支持 emoji 表情符号,必须只用 utf8mb4。需要在 MySQL 配置文件中配置客户端字符集为 utf8mb4。

JDBC 的连接串不支持配置 characterEncoding=utf8mb4,最好的办法是在连接池中指定初始化 SQL。

例如:hikari 连接池,其他连接池类似 spring . datasource . hikari . connection – init – sql =set names utf8mb4。否则需要每次执行 SQL 前都先执行 set names utf8mb4。

MySQL 排序规则

一般使用 _bin 和 _genera_ci:

  • utf8_genera_ci 不区分大小写,ci 为 case insensitive 的缩写,即大小写不敏感。
  • utf8_general_cs 区分大小写,cs 为 case sensitive 的缩写,即大小写敏感,但是目前 MySQL 版本中已经不支持类似于 ***_genera_cs 的排序规则,直接使用 utf8_bin 替代。
  • utf8_bin 将字符串中的每一个字符用二进制数据存储,区分大小写。

那么,同样是区分大小写,utf8_general_cs 和 utf8_bin 有什么区别?

  • cs 为 case sensitive 的缩写,即大小写敏感;bin 的意思是二进制,也就是二进制编码比较。
  • utf8_general_cs 排序规则下,即便是区分了大小写,但是某些西欧的字符和拉丁字符是不区分的,比如 ä=a,但是有时并不需要 ä=a,所以才有 utf8_bin。
  • utf8_bin 的特点在于使用字符的二进制的编码进行运算,任何不同的二进制编码都是不同的,因此在 utf8_bin 排序规则下:ä<>a。

初始化命令

SQLyog 中初始连接指定编码类型使用连接配置的初始化命令,如下图:

1553049871-8082-f30aeb6b3a7c26fdbd2e48f6ecf3

SQL 语句总结

常用但容易忘的

SQL 语句常用但容易忘的总结如下:

  • 如果有主键或者唯一键冲突则不插入:insert ignore into。
  • 如果有主键或者唯一键冲突则更新,注意这个会影响自增的增量:INSERT INTO room_remarks(room_id,room_remarks)VALUE(1,”sdf”) ON DUPLICATE KEY UPDATE room_remarks = “234”。
  • 如果有就用新的替代,values 如果不包含自增列,自增列的值会变化:REPLACE INTO room_remarks(room_id,room_remarks) VALUE(1,”sdf”)。
  • 备份表:CREATE TABLE user_info SELECT * FROM user_info。
  • 复制表结构:CREATE TABLE user_v2 LIKE user。
  • 从查询语句中导入:INSERT INTO user_v2 SELECT * FROM user 或者 INSERT INTO user_v2(id,num) SELECT id,num FROM user。
  • 连表更新:UPDATE user a, room b SET a.num=a.num+1 WHERE a.room_id=b.id。
  • 连表删除:DELETE user FROM user,black WHERE user.id=black.id。

锁相关

锁相关(作为了解,很少用):

  • 共享锁:select id from tb_test where id = 1 lock in share mode。
  • 排它锁:select id from tb_test where id = 1 for update。

优化时用到

优化时用到:

  • 强制使用某个索引:select * from table force index(idx_user) limit 2。
  • 禁止使用某个索引:select * from table ignore index(idx_user) limit 2。
  • 禁用缓存(在测试时去除缓存的影响):select SQL_NO_CACHE from table limit 2。

查看状态

查看状态:

  • 查看字符集:SHOW VARIABLES LIKE ‘character_set%’。
  • 查看排序规则:SHOW VARIABLES LIKE ‘collation%’。

SQL 编写注意

SQL 编写请注意:

  • where 语句的解析顺序是从右到左,条件尽量放 where 不要放 having。
  • 采用延迟关联(deferred join)技术优化超多分页场景,比如 limit 10000,10,延迟关联可以避免回表。
  • distinct 语句非常损耗性能,可以通过 group by 来优化。
  • 连表尽量不要超过三个表。

踩坑

踩坑总结如下:

  • 如果有自增列,truncate 语句会把自增列的基数重置为 0,有些场景用自增列作为业务上的 ID 需要十分重视。
  • 聚合函数会自动滤空,比如 a 列的类型是 int 且全部是 NULL,则 SUM(a) 返回的是 NULL 而不是 0。
  • MySQL 判断 null 相等不能用 “a=null”,这个结果永远为 UnKnown,where 和 having 中,UnKnown 永远被视为 false,check 约束中,UnKnown 就会视为 true 来处理。所以要用“a is null”处理。

千万大表在线修改

MySQL 在表数据量很大的时候,如果修改表结构会导致锁表,业务请求被阻塞。

MySQL 在 5.6 之后引入了在线更新,但是在某些情况下还是会锁表,所以一般都采用 PT 工具( Percona Toolkit)。

如对表添加索引:

  1. pt-online-schema-change –user=’root’ –host=’localhost’ –ask-pass –alter “add index idx_user_id(room_id,create_time)” 
  2. D=fission_show_room_v2,t=room_favorite_info –execute

慢查询日志

有时候如果线上请求超时,应该去关注下慢查询日志,慢查询的分析很简单,先找到慢查询日志文件的位置,然后利用 mysqldumpslow 去分析。

查询慢查询日志信息可以直接通过执行 SQL 命令查看相关变量,常用的 SQL 如下:

1553049871-4435-7eca532cafc585df2df3f6950d99

mysqldumpslow 的工具十分简单,我主要用到的参数如下:

  • -t:限制输出的行数,我一般取前十条就够了。
  • -s:根据什么来排序默认是平均查询时间 at,我还经常用到 c 查询次数,因为查询次数很频繁但是时间不高也是有必要优化的,还有 t 查询时间,查看那个语句特别卡。
  • -v:输出详细信息。

例子:mysqldumpslow -v -s t -t 10 mysql_slow.log.2018-11-20-0500。

查看 SQL 进程和杀死进程

如果你执行了一个 SQL 的操作,但是迟迟没有返回,你可以通过查询进程列表看看它的实际执行状况。

如果该 SQL 十分耗时,为了避免影响线上可以用 kill 命令杀死进程,通过查看进程列表也能直观的看下当前 SQL 的执行状态;如果当前数据库负载很高,在进程列表可能会出现,大量的进程夯住,执行时间很长。

命令如下:

  1. –查看进程列表
  2. SHOW PROCESSLIST;
  3. –杀死某个进程
  4. kill 183665

如果你使用的 SQLyog,那么也有图形化的页面,在菜单栏→工具→显示→进程列表。

在进程列表页面可以右键杀死进程,如下所示:

1553049871-4118-e1f8c1f08376a8c359dbe4873f72

1553049871-5239-a0a6.jpg-wh-600x-s-902309858

一些数据库性能的思考

在对公司慢查询日志做优化的时候,很多时候可能是忘了建索引,像这种问题很容易解决,加个索引就行了。但是有几种情况就不是简单加索引能解决了:

业务代码循环读数据库

考虑这样一个场景,获取用户粉丝列表信息,加入分页是十个,其实像这样的 SQL 是十分简单的,通过连表查询性能也很高。

但是有时候,很多开发采用了取出一串 ID,然后循环读每个 ID 的信息,这样如果 ID 很多对数据库的压力是很大的,而且性能也很低。

统计 SQL

很多时候,业务上都会有排行榜这种,发现公司有很多地方直接采用数据库做计算,在对一些大表做聚合运算的时候,经常超过五秒,这些 SQL 一般很长而且很难优化。

像这种场景,如果业务允许(比如一致性要求不高或者是隔一段时间才统计的),可以专门在从库里面做统计。另外我建议还是采用 Redis 缓存来处理这种业务。

超大分页

在慢查询日志中发现了一些超大分页的慢查询如 Limit 40000,1000,因为 MySQL 的分页是在 Server 层做的,可以采用延迟关联在减少回表。

但是看了相关的业务代码正常的业务逻辑是不会出现这样的请求的,所以很有可能是有恶意用户在刷接口,最好在开发的时候也对接口加上校验拦截这些恶意请求。

Hadoop2.0分布式集群的平台搭建

一、Hadoop集群安装前的准备
基础环境

四台Centos6.5
IP地址:
192.168.174.128
192.168.174.129
192.168.174.130
192.168.174.131
四台主机新建hadoop用户并实现ssh免密登陆
iptables关闭和selinuxdisabled

1.修改主机名和ip地址映射
为了后面操作方便,修改主机名分别为hadoop01、hadoop02、hadoop03、hadoop04。修改主机名只需修改/etc/sysconfig/network文件hostname行即可,这里博主不再复述。然后修改/etc/hosts文件,将ip地址和主机名的映射写入进去,这样,后面其它主机就可根据主机名去对应ip地址。
1553049668-2104-cf72d11967ef780df27ba5cdd994
2.安装JDK
Hadoop的核心组成之一MapReduce是基于java的,因此需要配置基本的java环境。JDK安装十分简单,前面也多次提到。下载jdk安装包,解压jdk到指定目录。

tar -zxvf jdk-8u181-linux-x64.tar.gz -C /usr/local/java

修改环境变量,进入/etc/profile

export JAVA_HOME=/usr/local/java/jdk1.8.0_181
export PATH=$PATH:$JAVA_HOME/bin

重新加载环境变量生效。JDK需在四个节点都安装配置
3.Zookeeper安装配置
Zookeeper是负责协调Hadoop一致性,是Hadoop实现HA的不可或缺的组件。根据Zookeeper的工作机制,需要在奇数个节点安装Zookeeper。本文在hadoop01、hadoop02、hadoop03三个节点安装Zookeeper。
下载zookeeper安装包,解压zookeeper安装包
1553049668-5318-127622313b4fc934cee2bd69890c
设置环境变量,修改/etc/profile

export ZOOKEEPER_HOME=/usr/local/zookeeper/zookeeper-3.4.6
export PATH=$PATH:$JAVA_HOME/bin:$ZOOKEEPER_HOME/bin

重新加载环境变量生效
进入zookeeper解压目录下的conf目录下,修改配置文件zoo.cfg,一开始并没有zoo.cfg文件,拷贝zoo_sample.cfg文件重命名为zoo.cfg即可。
1553049674-4206-bd6f9ed6dd0e4be48beee2c9b782
创建相应的data目录及datalog目录

mkdir  -p /opt/zookeeper/datalog

在每个data目录下新建myid文件,hadoop01的myid文件写入1,hadoop02的myid文件写入2,即server.后的数字。另外注意给/opt/zookeeper目录及其子目录给hadoop用户读写操作权限,因为后面使用zookeeper时是以hadoop用户使用的。
到这里zookeeper基本安装配置完成,以hadoop用户启动zookeeper服务

zkServer.sh start

查看zookeeper状态

zkServer.sh status

二、Hadoop安装配置
下载hadoop安装包,解压hadoop安装包
1553049668-7059-1c4d83e8df66cd8d0ac7e1b3e711
注意解压后的目录user和group应该为hadoop,道理与前面zookeeper一样,在Hadoop使用过程中使用者是hadoop用户。
设置环境变量,修改配置文件/etc/profile

export HADOOP_HOME=/usr/local/hadoop-2.6.4
export PATH=$PATH:$JAVA_HOME/bin:$ZOOKEEPER_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin

注意hadoop需要配置bin和sbin,不然后面许多命令无法使用。重新加载环境变量生效。
然后就是修改hadoop的配置文件,进入hadoop安装目录下的etc/hadoop目录下,修改配置文件:hadoop-env.sh、core-site.xml、hdfs-site.xml、mapred-site.xml、yarn-site.xml,其中配置文件mapred-site.xml在该目录下有一个样本mapred-site.xml.template,复制该文件重命名为mapred-site.xml即可。
修改配置文件hadoop-env.sh。主要是配置java目录
1553049668-2112-cbcc8aa520faab9fa11c0ead1971
修改配置文件core-site.xml

<!-- Put site-specific property overrides in this file. -->

<configuration>
<property>
  <name>fs.defaultFS</name>
  <value>hdfs://jsj/</value>
</property>
<property>
  <name>hadoop.tmp.dir</name>
  <value>/usr/local/hdpdata</value>
</property>
<property>
  <name>ha.zookeeper.quorum</name>
  <value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>
</property>
</configuration>

修改配置文件hdfs-site.xml,从配置文件名也可知这是关于HDFS的配置。

<!-- Put site-specific property overrides in this file. -->

<configuration>
<property>
  <name>dfs.nameservices</name>
  <value>jsj</value>
</property>
<property>
  <name>dfs.ha.namenodes.jsj</name>
  <value>nn1,nn2</value>
</property>
<property>
  <name>dfs.namenode.rpc-address.jsj.nn1</name>
  <value>hadoop01:9000</value>
</property>
<property>
  <name>dfs.namenode.rpc-address.jsj.nn2</name>
  <value>hadoop02:9000</value>
</property>
<property>
  <name>dfs.namenode.http-address.jsj.nn1</name>
  <value>hadoop01:50070</value>
</property>
<property>
  <name>dfs.namenode.http-address.jsj.nn2</name>
  <value>hadoop02:50070</value>
</property>
<property>
  <name>dfs.namenode.shared.edits.dir</name>
  <value>qjournal://hadoop01:8485;hadoop02:8485;hadoop03:8485/jsj</value>
</property>
<property>
  <name>dfs.journalnode.edits.dir</name>
  <value>/usr/local/journaldata</value>
</property>
<property>
  <name>dfs.ha.automatic-failover.enabled</name>
  <value>true</value>
</property>
<property>
  <name>dfs.client.failover.proxy.provider.jsj</name>
  <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
<property>
  <name>dfs.ha.fencing.methods</name>
  <value>
  sshfence
  shell(/bin/true)
  </value>
</property>
<property>
  <name>dfs.ha.fencing.ssh.private-key-files</name>
  <value>/home/hadoop/.ssh/id_rsa</value>
</property>
<property>
  <name>dfs.ha.fencing.ssh.connect-timeout</name>
  <value>30000</value>
</property>
</configuration>

修改配置文件mapred-site.xml,即MapReduce相关配置。

<!-- Put site-specific property overrides in this file. -->

<configuration>
<property>
   <name>mapreduce.framework.name</name>
   <value>yarn</value>
</property>
<property>
   <name>mapreduce.jobhistory.address</name>
   <value>hadoop03:10020</value>
</property>
<property>
   <name>mapreduce.jobhistory.webapp.address</name>
   <value>hadoop03:19888</value>
</property>
</configuration>

修改配置文件yarn-site.xml。yarn平台的相关配置

<configuration>

<!-- Site specific YARN configuration properties -->
<property>
   <name>yarn.log-aggregation-enable</name>
   <value>true</value>
</property>
<property>
   <name>yarn.resourcemanager.ha.enabled</name>
   <value>true</value>
</property>
<property>
   <name>yarn.resourcemanager.cluster-id</name>
   <value>abc</value>
</property>
<property>
   <name>yarn.resourcemanager.ha.rm-ids</name>
   <value>rm1,rm2</value>
</property>
<property>
   <name>yarn.resourcemanager.hostname.rm1</name>
   <value>hadoop01</value>
</property>
<property>
   <name>yarn.resourcemanager.hostname.rm2</name>
   <value>hadoop02</value>
</property>
<property>
   <name>yarn.resourcemanager.zk-address</name>
   <value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>
</property>
<property>
   <name>yarn.nodemanager.aux-services</name>
   <value>mapreduce_shuffle</value>
</property>
</configuration>

最后修改slaves文件

hadoop02
hadoop03
hadoop04

至此,Hadoop集群相关配置文件配置完成,在hadoop01、hadoop02、hadoop03、hadoop04四个节点都完成相关配置。
配置文件修改完成并不代表Hadoop安装结束,还需要几个操作才能正常使用。
在hadoop01、hadoop02、hadoop03启动zookeeper服务。

zkServer.sh start

在hadoop01、hadoop02、hadoop03启动journalnode

hadoop-daemon.sh start journalnode

格式化hdfs,hadoop01执行

hdfs namenode -format

然后查看hadoop安装目录确保hdpdata和journaldata在hadoop01和hadoop02都有。没有从一个节点拷贝到另一个节点。
在hadoop01启动namenode

hadoop-daemon.sh start namenode

在Hadoop02执行

hdfs namenode -bootstrapStandby

格式化zkfc,Hadoop01执行

hdfs zkfc -formatZk

在hadoop01启动HDFS

start-dfs.sh

完成以上操作后,Hadoop应该可以正常对外做出服务。在浏览器输入hadoop01的ip地址,端口号为50070,查看HDFS的web界面是否正常对外做出服务。
1553049669-6024-f2359417ee5551e2196c1d8bd697
在hadoop01和hadoop02启动yarn平台

start-yarn.sh

访问hadoop01的ip地址的8088端口,查看yarn平台是否正常对外做出服务。
1553049674-6192-f59ff4b6d0e5ba538026527ee83e
Hadoop安装配置完成,关于配置文件的解释后期有时间再加上去。本文使用的安装包是在学习过程老师给的,Hadoop是开源的,相信相关安装包不难找到。

 

注:转自https://blog.51cto.com/13917261/2364868,原因是觉得写的不错。

JAVA的垃圾回收机制

一、 技术背景你要了解吧
谈谈JVM垃圾回收的前世今生的,说起垃圾回收GC,大部分人都把这项技术当做Java语言的伴生产物。事实上,GC的历史比Java久远,早在1960年Lisp这门语言中就使用了内存动态分配和垃圾回收技术。设计和优化C++这门语言。

二、 哪些内存需要回收?
都知道JVM的内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆区方法区则不一样,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。
垃圾收集器在对堆区和方法区进行回收前,首先要确定这些区域的对象哪些可以被回收,哪些暂时还不能回收,这就要用到判断对象是否存活的算法。

2.1 引用计数算法
2.1.1 算法分析
引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。
2.1.2 优缺点
优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。
2.1.3 是不是很无趣,来段代码压压惊

    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();
          
        object1.object = object2;
        object2.object = object1;
          
        object1 = null;
        object2 = null;
    }
}

这段代码是用来验证引用计数算法不能检测出循环引用。最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。

2.2 可达性分析算法
可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。
1552823266-9938-5c8ddbe88c5b7
在Java语言中,可作为GC Roots的对象包括下面几种:
a) 虚拟机栈中引用的对象(栈帧中的本地变量表);
b) 方法区中类静态属性引用的对象;
c) 方法区中常量引用的对象;
d) 本地方法栈中JNI(Native方法)引用的对象。
2.3 Java中的引用你了解多少
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。在Java语言中,将引用又分为强引用、软引用、弱引用、虚引用4种,这四种引用强度依次逐渐减弱。

强引用
在程序代码中普遍存在的,类似 Object obj = new Object() 这类引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

软引用
用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。

弱引用
也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用
也叫幽灵引用或幻影引用(名字真会取,很魔幻的样子),是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。它的作用是能在这个对象被收集器回收时收到一个系统通知。
不要被概念吓到,也别担心,还没跑题,再深入,可就不好说了。小编罗列这四个概念的目的是为了说明,无论引用计数算法还是可达性分析算法都是基于强引用而言的。

2.4 对象死亡(被回收)前的最后一次挣扎
即使在可达性分析算法中不可达的对象,也并非是“非死不可”,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。
第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记;
第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。在finalize()方法中没有重新与引用链建立关联关系的,将被进行第二次标记。
第二次标记成功的对象将真的会被回收,如果对象在finalize()方法中重新与引用链建立了关联关系,那么将会逃离本次回收,继续存活。

2.5 方法区如何判断是否需要回收
方法区存储内容是否需要回收的判断可就不一样咯。方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面3个条件:
1.该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
2.加载该类的ClassLoader已经被回收;
3.该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

三、常用的垃圾收集算法
3.1 标记-清除算法
标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如下图所示。标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。
1552823267-7730-5c8ddbea01285
3.2 复制算法

复制算法的提出是为了克服句柄的开销和解决内存碎片的问题。它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集合(GC Roots)中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。
1552823266-9289-5c8ddbeb709a4
3.3 标记-整理算法
标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。具体流程见下图:
1552823271-1641-5c8ddbecdcb7b
3.4 分代收集算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
1552823267-3484-5c8ddbee8b29a
3.4.1 年轻代(Young Generation)的回收算法
a) 所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。

b) 新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。

c) 当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
d) 新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)。

3.4.2 年老代(Old Generation)的回收算法

a) 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
b) 内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。

3.4.3 持久代(Permanent Generation)的回收算法

用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代也称方法区,具体的回收可参见上文2.5节。

四、常见的垃圾收集器
下面一张图是HotSpot虚拟机包含的所有收集器,图是借用过来滴:
5c8ddbf02226f
Serial收集器(复制算法)
新生代单线程收集器,标记和清理都是单线程,优点是简单高效。是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定。

Serial Old收集器(标记-整理算法)
老年代单线程收集器,Serial收集器的老年代版本。

ParNew收集器(停止-复制算法)
新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。

Parallel Scavenge收集器(停止-复制算法)
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数。

Parallel Old收集器(停止-复制算法)

Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。

CMS(Concurrent Mark Sweep)收集器(标记-清理算法)
高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择。

五、GC是什么时候触发的(面试最常见的问题之一)
由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。

5.1 Scavenge GC
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

5.2 Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:
a) 年老代(Tenured)被写满;
b) 持久代(Perm)被写满;
c) System.gc()被显示调用;
d) 上一次GC之后Heap的各域分配策略动态变化;

结束语
内容的完整度和深度在一篇博文里面真的很难全部考虑,本文做了很大尝试,最后还是得投降。对于各个垃圾收集器的区别、运行过程中各内存区域参数的设置、GC日志的查看等内容后续再补上吧。文章概念很多,也借用了一些书籍和博文的经典总结,算是一个知识点整理后的输出吧,希望对大家有所裨益。

 

转自:https://www.e-learn.cn/content/java/2069493

Java中常用的API

1. 字符串与数字之间的相互转换

① 将数字转化为字符串:

String s = Integer.toString(int n);

String s = Double.parseDouble(s);

 

② 将数字型的字符串转化为数字

int n = Integer.parseInt(String s)

double n = Double.parseDouble(s)

public class Main {
	public static void main(String[] args) {
		String s = "123456";
		int n1 = Integer.parseInt(s);
		System.out.println(n1);
		
		int n2 = 234812;
		s = Integer.toString(n2);
		
		System.out.println(s);
		double n3 = 23.45;
		
		s = Double.toString(n3);
		System.out.println(s);
		
		s = "45.87";
		System.out.println(Double.parseDouble(s));
	}
}

 

2. String对象与char数组的转换

① 字符串对象转为char类型的数组

char arr[] = s.toCharArray();

 

② 将char类型的数组转为字符串

String s = new String(char arr[]);

String s = String.valueOf(char arr[]);

public class Main {
	public static void main(String[] args) {
		String s = "123345";
		char arr[] = s.toCharArray();
		for(int i = 0; i < arr.length; i++){
			System.out.print(arr[i]);
		}
		System.out.print("\n");
		char arr2[] = {'a', 'b', 'c', 'd', 'e'};
		s = new String(arr2);
		System.out.println(s);
		
		char arr3[] = {'e', 'f', 'g', 'h', 'i'};
		s = String.valueOf(arr3);
		System.out.println(s);
	}
}

3. 根据索引获取字符和根据字符获取索引

① 根据索引获取字符:

char c = s.charAt(i);

 

② 获取字符的最后一个下标(可能有多个重复的字符):

int index = s.lastIndexOf(char c);

获取字符的第一个下标:

int index = s.indexOf(char c);

public class Main {
	public static void main(String[] args) {
		String s = "abbc";
		char c = s.charAt(3);
		System.out.println(c);
		System.out.println(s.indexOf('b'));
		System.out.println(s.lastIndexOf('b'));
	}
}

 

利用Sharding-Jdbc实现分表

看到了当当开源的Sharding-JDBC组件,它可以在几乎不修改代码的情况下完成分库分表的实现。摘抄其中一段介绍:

      Sharding-JDBC直接封装JDBC API,可以理解为增强版的JDBC驱动,旧代码迁移成本几乎为零:

  • 可适用于任何基于java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC
  • 可基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid等。
  • 理论上可支持任意实现JDBC规范的数据库。虽然目前仅支持MySQL,但已有支持Oracle,SQLServer,DB2等数据库的计划。

先做一个最简单的试用,不做分库,仅做分表。选择数据表bead_information,首先复制成三个表:bead_information_0、bead_information_1、bead_information_2

1552822689-9656-20180725

测试实现过程

前提:已经实现srping+mybatis对单库单表做增删改查的项目。

 1、修改pom.xml增加dependency

        <dependency>
            <groupId>com.dangdang</groupId>
            <artifactId>sharding-jdbc-core</artifactId>
            <version>1.4.2</version>
        </dependency>
        <dependency>
            <groupId>com.dangdang</groupId>
            <artifactId>sharding-jdbc-config-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

2、新建一个sharding-jdbc.xml文件,实现分库分表的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:rdb="http://www.dangdang.com/schema/ddframe/rdb"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd 
                        http://www.springframework.org/schema/tx 
                        http://www.springframework.org/schema/tx/spring-tx.xsd
                        http://www.springframework.org/schema/context 
                        http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.dangdang.com/schema/ddframe/rdb 
                        http://www.dangdang.com/schema/ddframe/rdb/rdb.xsd">
    
 
    
     <!-- 配置数据源 -->
    <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/beadhouse" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
    </bean>
                 
    <rdb:strategy id="tableShardingStrategy" sharding-columns="id" algorithm-class="com.springdemo.utill.MemberSingleKeyTableShardingAlgorithm"/>
    
    <rdb:data-source id="shardingDataSource">
        <rdb:sharding-rule data-sources="dataSource">
            <rdb:table-rules>
                <rdb:table-rule logic-table="bead_information" actual-tables="bead_information_${0..2}"  table-strategy="tableShardingStrategy"/>
            </rdb:table-rules>
        </rdb:sharding-rule>
    </rdb:data-source>
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="shardingDataSource" />
    </bean>
</beans>

3、将文件引入spring配置文件中。

需要修改几个地方,把sqlSessionFactory和transactionManager原来关联的dataSource统一修改为shardingDataSource(这一步作用就是把数据源全部托管给sharding去管理)

1552822688-6796-20180725

 

4、实现分表(分库)逻辑,我们的分表逻辑类需要实现SingleKeyTableShardingAlgorithm接口的三个方法doBetweenSharding、doEqualSharding、doInSharding

(取模除数需要按照自己需求改变,我这里分3个表,所以除以3)

import java.util.Collection;
import java.util.LinkedHashSet;
import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm;
import com.google.common.collect.Range;
public class MemberSingleKeyTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Integer> {

    @Override
    public Collection<String> doBetweenSharding(Collection<String> tableNames, ShardingValue<Integer> shardingValue) {
        Collection<String> result = new LinkedHashSet<String>(tableNames.size());
        Range<Integer> range = (Range<Integer>) shardingValue.getValueRange();
        for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
            Integer modValue = i % 3;
            String modStr = modValue < 3 ? "" + modValue : modValue.toString();
            for (String each : tableNames) {
                if (each.endsWith(modStr)) {
                    result.add(each);
                }
            }
        }
        return result;
    }
    @Override
    public String doEqualSharding(Collection<String> tableNames, ShardingValue<Integer> shardingValue) {
        Integer modValue = shardingValue.getValue() % 3;
        String modStr = modValue < 3 ? "" + modValue : modValue.toString();
        for (String each : tableNames) {
            if (each.endsWith(modStr)) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }
    @Override
    public Collection<String> doInSharding(Collection<String> tableNames, ShardingValue<Integer> shardingValue) {
        Collection<String> result = new LinkedHashSet<String>(tableNames.size());
        for (Integer value : shardingValue.getValues()) {
            Integer modValue = value % 3;
            String modStr = modValue < 3 ? "" + modValue : modValue.toString();
            for (String tableName : tableNames) {
                if (tableName.endsWith(modStr)) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }
}

5、配置完成,可以实现增删改查测试。

12387