2017年8月11日

gerrit还是轻易不要尝试引入,它的权限管理,真是复杂极了。对于小型团队,初期这将是个噩梦,但是对于像OpenStack,安卓这种大型team,又是一把利器。
下面尝试测试了两个用户的简单情况,很多配置都是系统默认,没有进行啥复杂配置,即使这样也是错误百出,光一个commit就要折腾半天,而且还有些机制没搞清楚。
首先要做的准备工作就是准备两个gerrit用户,user1和user2,并且分别把user1和user2的ssh pub-key通过gerrit setting添加好。
1. 首先user1创建一个叫HelloWord的project。
   如何创建project请参考前期博客或者官方文档。
2. user1在自己的工作环境中把HelloWord clone下来
[user1@jenkins ~]$ git clone ssh://user1@gerrit.example.com:29418/HelloWorld.git
Initialized empty Git repository in /home/user1/HelloWorld/.git/
remote: Counting objects: 2, done
remote: Finding sources: 100% (2/2)
remote: Total 2 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (2/2), done.
加入user1没有添加ssh pubkey的话,这一步会出permission deny

clone后,创建一个README文件并add,commit
[user1@jenkins ~]$ cd HelloWorld
[user1@jenkins HelloWorld]$ ls
[user1@jenkins HelloWorld]$ touch README
[penxiao@jenkins test]$ git add README 
[penxiao@jenkins test]$ git commit -m add README
这里注意一点,在下面要push之前,一定要配置好git config的 username和email
可以通过命令行或者直接编辑 ~/.gitconfig文件实现,而且email一定要和gerrit里注册的email一致,否者push也会出错。
[user1@jenkins HelloWorld]$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 213 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Processing changes: refs: 1, done    
To ssh://user1@gerrit.example.com:29418/HelloWorld.git
 * [new branch]      master -> master
[user1@jenkins HelloWorld]$

在gerrit的gitweb链接可以查看push的文件。
3. user2加入
[user2@jenkins ~]$ git clone ssh://user1@gerrit.example.com:29418/HelloWorld.git
Initialized empty Git repository in /home/user2/HelloWorld/.git/
remote: Counting objects: 3, done
remote: Finding sources: 100% (3/3)
remote: Total 3 (delta 0), reused 3 (delta 0)
Receiving objects: 100% (3/3), done.
[user2@jenkins ~]$ cd HelloWorld
[user2@jenkins HelloWorld]$ ls
README
[user2@jenkins HelloWorld]$ 
user2对README文件进行修改,然后要commit,push
!!!也同样注意,user2的git config,username和email的配置,email要和gerrit setting里的一致。
commit完以后可以看到
[user2@jenkins HelloWorld]$ git log
commit 7959fe47bc2d2f53539a1861aa6b0d71afe0a531
Author: user2 <user2@gerrit.com>
Date:   Thu Dec 12 00:24:53 2013 -0500
    edit README
commit 98099fc0de3ba889b18cf36f9a5af267b3ddb501
Author: user1 <user@gerrit.com>
Date:   Thu Dec 12 00:15:08 2013 -0500
    add README
[user2@jenkins HelloWorld]$
现在user2要把这次的改变push到gerrit,可以么?
不行的,可以看到
[user2@jenkins HelloWorld]$ git push origin master
Counting objects: 5, done.
Writing objects: 100% (3/3), 249 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Branch refs/heads/master:
remote: You are not allowed to perform this operation.
remote: To push into this reference you need 'Push' rights.
remote: User: user2
remote: Please read the documentation and contact an administrator
remote: if you feel the configuration is incorrect
remote: Processing changes: refs: 1, done    
To ssh://user2@gerrit.example.com:29418/HelloWorld.git
 ! [remote rejected] master -> master (prohibited by Gerrit)
error: failed to push some refs to 'ssh://user2@gerrit.example.com:29418/HelloWorld.git'
[user2@jenkins HelloWorld]$ 
这就是gerrit的精髓所在了。原因是gerrit不允许直接将本地修改同步到远程仓库。客户机必须先push到远程仓库的refs/for/*分支上,等待审核。这也是为什么我们需要使用gerrit的原因。gerrit本身就是个代码审核工具。

接下来更该push的地址:  
[user2@jenkins HelloWorld]$git config remote.origin.push refs/heads/*:refs/for/*  
此命令实际是更改的是本地仓库test_project/.git/config文件。 
再次push   
[user2@jenkins HelloWorld]$git push origin  
这次不要加master
[user2@jenkins HelloWorld]$ git push origin
Counting objects: 5, done.
Writing objects: 100% (3/3), 249 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Processing changes: refs: 1, done    
remote: ERROR: missing Change-Id in commit message footer
remote: Suggestion for commit message:
remote: edit README
remote: 
remote: Change-Id: I7959fe47bc2d2f53539a1861aa6b0d71afe0a531
remote: 
remote: Hint: To automatically insert Change-Id, install the hook:
remote:   gitdir=$(git rev-parse --git-dir); scp -p -P 29418 user2@gerrit.example.com:hooks/commit-msg ${gitdir}/hooks/
remote: 
remote: 
To ssh://user2@gerrit.example.com:29418/HelloWorld.git
 ! [remote rejected] master -> refs/for/master (missing Change-Id in commit message footer)
error: failed to push some refs to 'ssh://user2@gerrit.example.com:29418/HelloWorld.git'
尼玛,还是不行,说缺change-Id,为了能让每次commit能自己insert 这个change-id,需要从gerrit server上下载一个脚本
[user2@jenkins HelloWorld] scp -p 29418 user2@gerrit.example.com:hooks/commit-msg <local path to your git>/.git/hooks/
然后重新commit
[user2@jenkins HelloWorld]$ git commit --amend
再次查看git log
[user2@jenkins HelloWorld]$ git log
commit f6b5919170875b5b4870fca2ab906c516c97006e
Author: user2 <user2@gerrit.com>
Date:   Thu Dec 12 00:24:53 2013 -0500
    edit by user2
    
    Change-Id: Ieac68bebefee7c6d4237fa5c058386bf7c4f66b7
commit 98099fc0de3ba889b18cf36f9a5af267b3ddb501
Author: user1 <user1@gerrit.com>
Date:   Thu Dec 12 00:15:08 2013 -0500
    add README
[user2@jenkins HelloWorld]$ 
这次就有了change id
然后再次push
[user2@jenkins HelloWorld]$ git push origin
Counting objects: 5, done.
Writing objects: 100% (3/3), 289 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Processing changes: new: 1, refs: 1, done    
remote: 
remote: New Changes:
remote:   http://gerrit.example.com:8080/1
remote: 
To ssh://user2@gerrit.example.com:29418/HelloWorld.git
 * [new branch]      master -> refs/for/master
[user2@jenkins HelloWorld]$ 
posted @ 2017-08-11 11:23 小马歌 阅读(11) | 评论 (0)编辑 收藏

2017年8月10日

from:http://blog.csdn.net/acmman/article/details/50848595

版权声明:本文为博主原创文章,未经博主允许不得转载。

目前,业内关于OSGI技术的学习资源或者技术文档还是很少的。我在某宝网搜索了一下“OSGI”的书籍,结果倒是有,但是种类少的可怜,而且几乎没有人购买。
因为工作的原因我需要学习OSGI,所以我不得不想尽办法来主动学习OSGI。我将用文字记录学习OSGI的整个过程,通过整理书籍和视频教程,来让我更加了解这门技术,同时也让需要学习这门技术的同志们有一个清晰的学习路线。

我们需要解决一下几问题:
1.如何正确的理解和认识OSGI技术?

我们从外文资料上或者从翻译过来的资料上看到OSGi解释和定义,都是直译过来的,但是OSGI的真实意义未必是中文直译过来的意思。OSGI的解释就是Open Service Gateway Initiative,直译过来就是“开放的服务入口(网关)的初始化”,听起来非常费解,什么是服务入口初始化?

所以我们不去直译这个OSGI,我们换一种说法来描述OSGI技术。

我们来回到我们以前的某些开发场景中去,假设我们使用SSH(struts+spring+hibernate)框架来开发我们的Web项目,我们做产品设计和开发的时候都是分模块的,我们分模块的目的就是实现模块之间的“解耦”,更进一步的目的是方便对一个项目的控制和管理。
我们对一个项目进行模块化分解之后,我们就可以把不同模块交给不同的开发人员来完成开发,然后项目经理把大家完成的模块集中在一起,然后拼装成一个最终的产品。一般我们开发都是这样的基本情况。

那么我们开发的时候预计的是系统的功能,根据系统的功能来进行模块的划分,也就是说,这个产品的功能或客户的需求是划分的重要依据。

但是我们在开发过程中,我们模块之间还要彼此保持联系,比如A模块要从B模块拿到一些数据,而B模块可能要调用C模块中的一些方法(除了公共底层的工具类之外)。所以这些模块只是一种逻辑意义上的划分。

最重要的一点是,我们把最终的项目要去部署到tomcat或者jBoss的服务器中去部署。那么我们启动服务器的时候,能不能关闭项目的某个模块或功能呢?很明显是做不到的,一旦服务器启动,所有模块就要一起启动,都要占用服务器资源,所以关闭不了模块,假设能强制拿掉,就会影响其它的功能。

以上就是我们传统模块式开发的一些局限性。

我们做软件开发一直在追求一个境界,就是模块之间的真正“解耦”、“分离”,这样我们在软件的管理和开发上面就会更加的灵活,甚至包括给客户部署项目的时候都可以做到更加的灵活可控。但是我们以前使用SSH框架等架构模式进行产品开发的时候我们是达不到这种要求的。

所以我们“架构师”或顶尖的技术高手都在为模块化开发努力的摸索和尝试,然后我们的OSGI的技术规范就应运而生。

现在我们的OSGI技术就可以满足我们之前所说的境界:在不同的模块中做到彻底的分离,而不是逻辑意义上的分离,是物理上的分离,也就是说在运行部署之后都可以在不停止服务器的时候直接把某些模块拿下来,其他模块的功能也不受影响。

由此,OSGI技术将来会变得非常的重要,因为它在实现模块化解耦的路上,走得比现在大家经常所用的SSH框架走的更远。这个技术在未来大规模、高访问、高并发的Java模块化开发领域,或者是项目规范化管理中,会大大超过SSH等框架的地位。

现在主流的一些应用服务器,Oracle的weblogic服务器,IBM的WebSphere,JBoss,还有Sun公司的glassfish服务器,都对OSGI提供了强大的支持,都是在OSGI的技术基础上实现的。有那么多的大型厂商支持OSGI这门技术,我们既可以看到OSGI技术的重要性。所以将来OSGI是将来非常重要的技术。

但是OSGI仍然脱离不了框架的支持,因为OSGI本身也使用了很多spring等框架的基本控件(因为要实现AOP依赖注入等功能),但是哪个项目又不去依赖第三方jar呢?



2.OSGI技术对我们项目的开发有什么帮助?

(1)项目展示
接下来我们同过项目代码来展示一下OSGI的魅力:
我们先不要去急着理解如何使用OSGI,我们通过一个项目先来看一下OSGI的效果。
(以下工程代码是网上教学视频中的样例,源码我这里是没有的)
(提前说一下:我们要学习的重点就是我们这个购物网站如何结合OSGI技术,使得项目更加的灵活可控,而购物网站本身并不是重点。)


首先在Eclipse中先打开我们的单服务器版本的项目:

启动成功:




这是一个Web项目,我们打开浏览器看一下效果:

可以看出是一个网上购物的项目。

我们来看一下我们基于OSGI技术的项目和我们一般的项目有什么区别。
首先介绍一下这个项目的模块:

1.大类展示


2.小类展示(大类的子产品)

点进去之后就是产品的具体信息



3.购物车
没买东西是空的:

买完之后:



4.商品管理(上架、下架)



可以看到,这个项目和我们平常开发的项目没有什么不同(我知道界面很简陋= =),重点是它的启动和加载过程。


(2)关于服务器
我们是通过动态加载,也就是“热部署”来启动我们的项目的。就是说,我们这个项目把它放在Web容器中之后,我们可以将某些功能给它拿下来,而且拿下来的时候不会对其他模块造成影响。

我们以前运行tomcat的时候,启动一下服务器,将Web项目一次性装载完毕,控制台会出现类似这种信息:


但是我们启动这个项目的时候并不是这样:


那么我们没有用tomcat和jBoss,那是如何部署和启动Web项目的呢?不可能没有Web服务器中间件的啊?这里告诉大家,OSGI技术里面也是内嵌了一个Web服务器的,就是jetty。


我们打开这个项目的Run Configuration配置窗口,看一下运行这个项目所需要的插件包:


可以看到,除了一些Web项目需要的jar包,还是有jetty的存在的。所以用到的服务器是jetty,不再是tomcat。


大家可能还是比较熟悉tomcat,对于jetty不是太熟悉,那么我们简单介绍一下jetty:
jetty也是一个比较优秀的Web容器,在某些性能方面要比tomcat强大的多(如高并发,长连接)。而且它的整个结构比tomcat轻巧很多(tomcat更臃肿),具体区别大家可以去网上自己看一下。


(3)运行模式和插件
我们接下来正式看一下此项目在OSGI下的运行模式:
我们在启动的时候,加载了四个模块,分别是:

按照模块化的思想他们就是四个对应的功能模块。
他们对应的四个功能模块的工程代码我们可以在Eclipse中看到:



我们看一下我们的启动配置(依然打开是Run Configuration配置窗口):


配置分为“WorkSpace”和“Target Platform”,分别是我们工作空间(我们自己写的项目模块和工具类)的插件和运行平台(一些依赖jar的配置)的插件,两者结合启动我们的项目就会正常运行。

我们启动项目之后,在控制台输入指令“ss”,就会出现我们所有加载的插件的运行情况:


一启动的时候,它会首先加载Eclipse的OSGI插件(Eclipse本身也是一种OSGI的容器):

我们打开我们的Eclipse安装目录,然后找到plugins文件夹,可以看到Eclipse所有的插件:

可以看到有文件夹形式的,有jar形式的插件。

我们怎么去理解插件呢?
插件其实就是被开发工具或OSGI容器管理和配置起来的jar包。  

我们随便打开一个文件夹类型的插件,可以看到:

可以看到里面除了lib之外还有其它东西,然后有一个“OSGI-INF”文件夹。且不管它是什么,这都足以说明我们的Eclipse就是一个OSGI容器。

(4)热部署和热启动
我们接下来回到重点,在我们启动的过程中,我们不停止运行,然后去停掉其中的一个模块:

假如我们要停掉“管理”模块:

也就是停掉id为22的插件

结果:


然后刷新我们的网站主页面:

发现我们的“管理”模块消失了!

这个模块的消失并不是javascript的技术,而是一种服务器技术,我们是通过服务器内部把它动态卸载掉的。

我们的管理模块去掉之后,网站的其它功能不受任何影响。至此我们的服务器没有进行任何的暂停或关闭。

我们再停掉“购物车”模块:

效果:

其它模块依旧不受影响。

我们关闭了两个模块,现在输入ss看一下所有插件和模块的运行情况:

可以看到我们的两个模块处于RESOLVED状态,也就是待解决状态。

当然我们也可以将我们的模块在服务器开启状态下部署上去:
如我们启动购物车模块:


发现购物车回来了:


这就是所谓的热部署,即是这个项目把它放在Web容器中之后,我们可以将某些功能给它拿下来,而且拿下来的时候不会对其他模块造成影响。


通过购物网站这个项目让大家真实的感受一下OSGI这个技术在项目开发和管理的一些强大的功能。

想进一步了解更多关于OSGI的知识可以查看以后的总结文章。

转载请注明出处:http://blog.csdn.net/acmman/article/details/50848595

20
posted @ 2017-08-10 15:57 小马歌 阅读(18) | 评论 (0)编辑 收藏

2017年8月2日

from:http://www.jianshu.com/p/ccadc2bdb6d7

第5章 Spring Boot自动配置原理

5.1 SpringBoot的核心组件模块

首先,我们来简单统计一下SpringBoot核心工程的源码java文件数量:

我们cd到spring-boot-autoconfigure工程根目录下。执行

$ tree | grep -c .java$
模块 java文件数
spring-boot 551
spring-boot-actuator 423
spring-boot-autoconfigure 783
spring-boot-devtools 169
spring-boot-cli 180
spring-boot-tools 355

我们可以看到有783个java文件。spring-boot核心工程有551个java文件。从上面的java文件数量大致可以看出,SpringBoot技术框架的核心组成部分:

spring-boot-autoconfigure spring-boot spring-boot-tools

我们把SpringBoot源码导入IntelliJ IDEA,查看artifact的全部依赖关系。

IDEA有个Maven Projects窗口,一般在右侧能够找到,如果没有可以从菜单栏打开:View>Tool Windows>Maven Projects;

选择要分析的maven module(idea的module相当于eclipse的project),右击show dependencies,会出来该module的全部依赖关系图,非常清晰细致。

例如,spring-boot-starter-freemarker的依赖图分析如下:


在spring-boot-build 的pom中,我们可以看到:

           <modules>                 <module>spring-boot-dependencies</module>                 <module>spring-boot-parent</module>                 <module>spring-boot-tools</module>                 <module>spring-boot</module>                 <module>spring-boot-test</module>                 <module>spring-boot-autoconfigure</module>                 <module>spring-boot-test-autoconfigure</module>                 <module>spring-boot-actuator</module>                 <module>spring-boot-devtools</module>                 <module>spring-boot-docs</module>                 <module>spring-boot-starters</module>                 <module>spring-boot-actuator-docs</module>                 <module>spring-boot-cli</module>             </modules>

其中,在spring-boot-dependencies中,SpringBoot项目维护了一份庞大依赖。这些依赖的版本都是经过实践,测试通过,不会发生依赖冲突的。就这样一个事情,就大大减少了Spring开发过程中,出现jar包冲突的概率。spring-boot-parent依赖spring-boot-dependencies。

下面我们简要介绍一下SpringBoot子modules。

spring-boot

SpringBoot核心工程。

spring-boot-starters

是SpringBoot的启动服务工程。

spring-boot-autoconfigure

是SpringBoot实现自动配置的核心工程。

spring-boot-actuator

提供SpringBoot应用的外围支撑性功能。 比如:

  • Endpoints,SpringBoot应用状态监控管理
  • HealthIndicator,SpringBoot应用健康指示表
  • 提供metrics支持
  • 提供远程shell支持

spring-boot-tools

提供了SpringBoot开发者的常用工具集。诸如,spring-boot-gradle-plugin,spring-boot-maven-plugin就是这个工程里面的。

spring-boot-cli

是Spring Boot命令行交互工具,可用于使用Spring进行快速原型搭建。你可以用它直接运行Groovy脚本。如果你不喜欢Maven或Gradle,Spring提供了CLI(Command Line Interface)来开发运行Spring应用程序。你可以使用它来运行Groovy脚本,甚至编写自定义命令。

5.2 SpringBoot Starters

Spring boot中的starter概念是非常重要的机制,能够抛弃以前繁杂的配置,统一集成进starter,应用者只需要引入starter jar包,spring boot就能自动扫描到要加载的信息。

starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。Spring Boot会自动通过classpath路径下的类发现需要的Bean,并织入bean。

例如,如果你想使用Spring和用JPA访问数据库,你只要依赖 spring-boot-starter-data-jpa 即可。

目前,github上spring-boot项目的最新的starter列表spring-boot/spring-boot-starters如下:

spring-boot-starter spring-boot-starter-activemq spring-boot-starter-actuator spring-boot-starter-amqp spring-boot-starter-aop spring-boot-starter-artemis spring-boot-starter-batch spring-boot-starter-cache spring-boot-starter-cloud-connectors spring-boot-starter-data-cassandra spring-boot-starter-data-couchbase spring-boot-starter-data-elasticsearch spring-boot-starter-data-jpa spring-boot-starter-data-ldap spring-boot-starter-data-mongodb spring-boot-starter-data-mongodb-reactive spring-boot-starter-data-neo4j spring-boot-starter-data-redis spring-boot-starter-data-rest spring-boot-starter-data-solr spring-boot-starter-freemarker spring-boot-starter-groovy-templates spring-boot-starter-hateoas spring-boot-starter-integration spring-boot-starter-jdbc spring-boot-starter-jersey spring-boot-starter-jetty spring-boot-starter-jooq spring-boot-starter-jta-atomikos spring-boot-starter-jta-bitronix spring-boot-starter-jta-narayana spring-boot-starter-log4j2 spring-boot-starter-logging spring-boot-starter-mail spring-boot-starter-mobile spring-boot-starter-mustache spring-boot-starter-parent spring-boot-starter-reactor-netty spring-boot-starter-security spring-boot-starter-social-facebook spring-boot-starter-social-linkedin spring-boot-starter-social-twitter spring-boot-starter-test spring-boot-starter-thymeleaf spring-boot-starter-tomcat spring-boot-starter-undertow spring-boot-starter-validation spring-boot-starter-web spring-boot-starter-web-services spring-boot-starter-webflux spring-boot-starter-websocket

(源代码目录执行shell:l|awk '{print $9}', l|awk '{print $9}'|grep -c 'starter')

共52个。每个starter工程里面的pom描述有相应的介绍。具体的说明,参考官网文档[1]。关于这些starters的使用例子,可以参考spring-boot/spring-boot-samples

比如说,spring-boot-starter是:

Core starter, including auto-configuration support, logging and YAML

这是Spring Boot的核心启动器,包含了自动配置、日志和YAML。它的项目依赖图如下:



可以看出,这些starter只是配置,真正做自动化配置的代码的是在spring-boot-autoconfigure里面。同时spring-boot-autoconfigure依赖spring-boot工程,这个spring-boot工程是SpringBoot的核心。

SpringBoot会基于你的classpath中的jar包,试图猜测和配置您可能需要的bean。

例如,如果你的classpath中有tomcat-embedded.jar,你可能会想要一个TomcatEmbeddedServletContainerFactory Bean (SpringBoot通过获取EmbeddedServletContainerFactory来启动对应的web服务器。常用的两个实现类是TomcatEmbeddedServletContainerFactory和JettyEmbeddedServletContainerFactory)。

其他的所有基于Spring Boot的starter都依赖这个spring-boot-starter。比如说spring-boot-starter-actuator的依赖树,如下图:


5.3 @EnableAutoConfiguration自动配置原理

通过@EnableAutoConfiguration启用Spring应用程序上下文的自动配置,这个注解会导入一个EnableAutoConfigurationImportSelector的类,而这个类会去读取一个spring.factories下key为EnableAutoConfiguration对应的全限定名的值。

这个spring.factories里面配置的那些类,主要作用是告诉Spring Boot这个stareter所需要加载的那些xxxAutoConfiguration类,也就是你真正的要自动注册的那些bean或功能。然后,我们实现一个spring.factories指定的类,标上@Configuration注解,一个starter就定义完了。

如果想从自己的starter种读取应用的starter工程的配置,只需要在入口类上加上如下注解即可:

@EnableConfigurationProperties(MyProperties.class)

读取spring.factories文件的实现

是通过org.springframework.core.io.support.SpringFactoriesLoader实现。

SpringFactoriesLoader的实现类似于SPI(Service Provider Interface,在java.util.ServiceLoader的文档里有比较详细的介绍。java SPI提供一种服务发现机制,为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要[3])。

SpringFactoriesLoader会加载classpath下所有JAR文件里面的META-INF/spring.factories文件。

其中加载spring.factories文件的代码在loadFactoryNames方法里:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";  ....      public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {         String factoryClassName = factoryClass.getName();         try {             Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :                     ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));             List<String> result = new ArrayList<>();             while (urls.hasMoreElements()) {                 URL url = urls.nextElement();                 Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));                 String factoryClassNames = properties.getProperty(factoryClassName);                 result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));             }             return result;         }         catch (IOException ex) {             throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +                     "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);         }     }

通过org.springframework.boot.autoconfigure.AutoConfigurationImportSelector里面的getCandidateConfigurations方法,获取到候选类的名字List<String>。该方法代码如下:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,             AnnotationAttributes attributes) {         List<String> configurations = SpringFactoriesLoader.loadFactoryNames(                 getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());         Assert.notEmpty(configurations,                 "No auto configuration classes found in META-INF/spring.factories. If you "                         + "are using a custom packaging, make sure that file is correct.");         return configurations;     }

其中,getSpringFactoriesLoaderFactoryClass()方法直接返回的是EnableAutoConfiguration.class, 代码如下:

    protected Class<?> getSpringFactoriesLoaderFactoryClass() {         return EnableAutoConfiguration.class;     }

所以,getCandidateConfigurations方法里面的这段代码:

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(                 getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());

会过滤出key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的全限定名对应的值。全限定名都使用如下命名方法:

包名.外部类名 包名.外部类名$内部类名  e.g:  org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

SpringBoot中的META-INF/spring.factories(完整路径:spring-boot/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories)中关于EnableAutoConfiguration的这段配置如下:

# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\ org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.ReactiveMongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.ReactiveMongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\ org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\ org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\ org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\ org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.ReactiveMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\ org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\ org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\ org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\ org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.WebFluxAnnotationAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

当然了,这些AutoConfiguration不是所有都会加载的,会根据AutoConfiguration上的@ConditionalOnClass等条件,再进一步判断是否加载。我们下文通过FreeMarkerAutoConfiguration实例来分析整个自动配置的过程。

5.4 FreeMarkerAutoConfiguration自动配置的实例分析

我们首先看spring-boot-starter-freemarker工程,目录结构如下:

. ├── pom.xml ├── spring-boot-starter-freemarker.iml └── src     └── main         └── resources             └── META-INF                 └── spring.provides  4 directories, 3 files

我们可以看出,这个工程没有任何Java代码,只有两个文件:pom.xml跟spring.provides。starter本身在你的应用程序中实际上是空的。

其中,
spring.provides文件

provides: freemarker,spring-context-support

主要是给这个starter起个好区分的名字。

Spring Boot 通过starter对项目的依赖进行统一管理. starter利用了maven的传递依赖解析机制,把常用库聚合在一起, 组成了针对特定功能而定制的依赖starter。

我们可以使用IDEA提供的maven依赖图分析的功能(如下图),得到spring-boot-starter-freemarker依赖的module。


IDEA提供的maven依赖图分析

spring-boot-starter-freemarker依赖的module

从上面的依赖图,我们可以清晰看出其间依赖关系。

当Spring Boot Application中自动配置EnableAutoConfiguration的相关类执行完毕之后,Spring Boot会进一步解析对应类的配置信息。如果我们配置了spring-boot-starter-freemarker ,maven就会通过这个starter所依赖的spring-boot-autoconfigure,自动传递到spring-boot-autoconfigure工程中。

我们来简单分析一下spring-boot-autoconfigure工程的架构。

其中,FreeMarker的自动配置类是org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration。

下面我们来简要分析一下FreeMarkerAutoConfiguration这个类。

在FreeMarkerAutoConfiguration类上面有四行注解:

@Configuration @ConditionalOnClass({ freemarker.template.Configuration.class,         FreeMarkerConfigurationFactory.class }) @AutoConfigureAfter(WebMvcAutoConfiguration.class) @EnableConfigurationProperties(FreeMarkerProperties.class) public class FreeMarkerAutoConfiguration {     ... }

其中,
(1)@Configuration,是org.springframework.context.annotation包里面的注解。这么说吧,用@Configuration注解该类,等价 与XML中配置beans;用@Bean标注方法等价于XML中配置bean。

(2)@ConditionalOnClass,org.springframework.boot.autoconfigure.condition包里面的注解。意思是当类路径下有指定的类的条件下,才会去注册被标注的类为一个bean。在上面的代码中的意思就是,当类路径中有freemarker.template.Configuration.class,FreeMarkerConfigurationFactory.class两个类的时候,才会实例化FreeMarkerAutoConfiguration这个Bean。

(3)@AutoConfigureAfter,org.springframework.boot.autoconfigure包里面的注解。这个通过注解的名字意思就可以知道,当WebMvcAutoConfiguration.class这个类实例化完毕,才能实例化FreeMarkerAutoConfiguration(有个先后顺序)。SpringBoot使用@ AutoConfigureBefore、@AutoConfigureAfter注解来定义这些配置类的载入顺序。

(4)@EnableConfigurationProperties,表示启动对FreeMarkerProperties.class的内嵌配置支持,自动将FreeMarkerProperties注册为一个bean。这个FreeMarkerProperties类里面就是关于FreeMarker属性的配置:

@ConfigurationProperties(prefix = "spring.freemarker") public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {      public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";      public static final String DEFAULT_PREFIX = "";      public static final String DEFAULT_SUFFIX = ".ftl";      /**      * Well-known FreeMarker keys which will be passed to FreeMarker's Configuration.      */     private Map<String, String> settings = new HashMap<>();      /**      * Comma-separated list of template paths.      */     private String[] templateLoaderPath = new String[] { DEFAULT_TEMPLATE_LOADER_PATH };      /**      * Prefer file system access for template loading. File system access enables hot      * detection of template changes.      */     private boolean preferFileSystemAccess = true;      public FreeMarkerProperties() {         super(DEFAULT_PREFIX, DEFAULT_SUFFIX);     }      public Map<String, String> getSettings() {         return this.settings;     }      public void setSettings(Map<String, String> settings) {         this.settings = settings;     }      public String[] getTemplateLoaderPath() {         return this.templateLoaderPath;     }      public boolean isPreferFileSystemAccess() {         return this.preferFileSystemAccess;     }      public void setPreferFileSystemAccess(boolean preferFileSystemAccess) {         this.preferFileSystemAccess = preferFileSystemAccess;     }      public void setTemplateLoaderPath(String... templateLoaderPaths) {         this.templateLoaderPath = templateLoaderPaths;     }  }

综上,当(1)(2)两个条件满足时,才会继续(3)(4)的动作,同时注册FreeMarkerAutoConfiguration这个Bean。该类的结构如下图:


我们来看其内部类FreeMarkerWebConfiguration的代码:

    @Configuration     @ConditionalOnClass(Servlet.class)     @ConditionalOnWebApplication(type = Type.SERVLET)     public static class FreeMarkerWebConfiguration extends FreeMarkerConfiguration {          @Bean         @ConditionalOnMissingBean(FreeMarkerConfig.class)         public FreeMarkerConfigurer freeMarkerConfigurer() {             FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();             applyProperties(configurer);             return configurer;         }          @Bean         public freemarker.template.Configuration freeMarkerConfiguration(                 FreeMarkerConfig configurer) {             return configurer.getConfiguration();         }          @Bean         @ConditionalOnMissingBean(name = "freeMarkerViewResolver")         @ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true)         public FreeMarkerViewResolver freeMarkerViewResolver() {             FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();             this.properties.applyToViewResolver(resolver);             return resolver;         }          @Bean         @ConditionalOnMissingBean         @ConditionalOnEnabledResourceChain         public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {             return new ResourceUrlEncodingFilter();         }      }

其中,
(1)@ConditionalOnWebApplication(type = Type.SERVLET), 是当该应用是基于Servlet的Web应用时。

(2)@ConditionalOnMissingBean(name = "freeMarkerViewResolver"),是当Spring容器中不存在freeMarkerViewResolver的Bean时。

(3)@ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true),指定的spring.freemarker.enabled属性是否有。如果没有(IfMissing),设为true。

当(1)(2)(3)三个条件都满足,则注册freeMarkerViewResolver这个Bean。

我们也可以自定义我们自己的my-starter,以及实现对应的@MyEnableAutoConfiguration。SpringBoot有很多第三方starter,其自动配置的原理基本都是这样,比如mybatis-spring-boot-starter的MybatisAutoConfiguration,阅读源码https://github.com/mybatis/spring-boot-starter[4]

上面文字描述了这么多,再用一张形象生动的图来说明[5]:


SpringBoot Autoconfigure 工作原理图

5.5 spring.factories与定义应用程序的初始化行为

上面说了这么多,讲的都是读取properties文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的全限定名对应的值。SpringBoot内部还有许多其他的key用于过滤得到需要加载的类。

# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer  # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer  # Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener  # Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnClassCondition  # Failure analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\ org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\ org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer  # Template availability providers org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

这些key仍然是定义在spring-boot/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories文件中。

还有对应的用于测试的自动配置,在
spring-boot/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories文件中定义。

另外,我们使用spring.factories里还可以定制应用程序的初始化行为。这样我们就可以在应用程序载入前操纵Spring的应用程序上下文ApplicationContext。

例如,可以使用ConfigurableApplicationContext类的addApplicationListener()方法,在应用上下文ApplicationContext中创建监听器。

自动配置运行日志报告功能就是这么实现的。我们来看在spring.factories中,Initializers一段的配置:

# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

其中,AutoConfigurationReportLoggingInitializer监听到系统事件时,比如上下文刷新ContextRefreshedEvent或应用程序启动故障ApplicationFailedEvent之类的事件,Spring Boot可以做一些事情。这里说的代码在AutoConfigurationReportLoggingInitializer.AutoConfigurationReportListener里面。关于支持的事件类型supportsEventType的如下:

    private class AutoConfigurationReportListener implements GenericApplicationListener {  ...         @Override         public boolean supportsEventType(ResolvableType resolvableType) {             Class<?> type = resolvableType.getRawClass();             if (type == null) {                 return false;             }             return ContextRefreshedEvent.class.isAssignableFrom(type)                     || ApplicationFailedEvent.class.isAssignableFrom(type);         }          @Override         public boolean supportsSourceType(Class<?> sourceType) {             return true;         }          @Override         public void onApplicationEvent(ApplicationEvent event) {     AutoConfigurationReportLoggingInitializer.this.onApplicationEvent(event);         }      }

要以调试模式启动应用程序,可以使用-Ddebug标识,或者在application.properties文件这添加属性debug= true。这样,当我们以调试模式启动应用程序时,SpringBoot就可以帮助我们创建自动配置的运行报告。对于每个自动配置,通过报告我们可以看到它启动或失败的原因。 这个报告内容格式大致如下:

========================= AUTO-CONFIGURATION REPORT =========================   Positive matches: -----------------     DataSourceAutoConfiguration matched:       - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)     DataSourceAutoConfiguration#dataSourceInitializer matched:       - @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer; SearchStrategy: all) did not find any beans (OnBeanCondition)     DataSourceAutoConfiguration.PooledDataSourceConfiguration matched:       - AnyNestedCondition 2 matched 0 did not; NestedCondition on DataSourceAutoConfiguration.PooledDataSourceCondition.PooledDataSourceAvailable PooledDataSource found supported DataSource; NestedCondition on DataSourceAutoConfiguration.PooledDataSourceCondition.ExplicitType @ConditionalOnProperty (spring.datasource.type) matched (DataSourceAutoConfiguration.PooledDataSourceCondition)       - @ConditionalOnMissingBean (types: javax.sql.DataSource,javax.sql.XADataSource; SearchStrategy: all) did not find any beans (OnBeanCondition)     ...  Exclusions: -----------      None   Unconditional classes: ----------------------      org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration      org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration      org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration      org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration

除了SpringBoot官方提供的starter外,还有社区贡献的很多常用的第三方starter,列表可参考[2]。

另外,国内很多公司使用RPC框架dubbo,关于SpringBoot集成dubbo,可参考:https://github.com/linux-china/spring-boot-dubbo。

参考资料:

1.http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-starter
2.https://github.com/spring-projects/spring-boot/tree/master/spring-boot-starters
3.http://www.cnblogs.com/javaee6/p/3714719.html
4.https://github.com/mybatis/spring-boot-starter
5.https://afoo.me/posts/2015-07-09-how-spring-boot-works.html



作者:华夏商周秦汉唐宋元明清中华民国
链接:http://www.jianshu.com/p/ccadc2bdb6d7
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @ 2017-08-02 16:35 小马歌 阅读(74) | 评论 (0)编辑 收藏
 
     摘要: from:http://blog.csdn.net/lylwo317/article/details/52163304序言注解在Java中到底是什么样的东西?具体是如何实现的? 本文将一层一层深入探究注解的实现原理。为了尽可能的将分析的过程呈现出来,所以文章包含了大量的截图和代码。(ps:如果图片看不清楚,请将网页放大来看,chrome可以通过ctrl+鼠标滚轮放大)前期准备知识方面开始...  阅读全文
posted @ 2017-08-02 11:15 小马歌 阅读(44) | 评论 (1)编辑 收藏
 

如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。


注解处理器类库(java.lang.reflect.AnnotatedElement):

  Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:

  Class:类定义
  Constructor:构造器定义
  Field:累的成员变量定义
  Method:类的方法定义
  Package:类的包定义

  java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
  AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

  方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
  方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
  方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
  方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

  一个简单的注解处理器:  

复制代码
/***********注解声明***************/  /**  * 水果名称注解  * @author peida  *  */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitName {     String value() default ""; }  /**  * 水果颜色注解  * @author peida  *  */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitColor {     /**      * 颜色枚举      * @author peida      *      */     public enum Color{ BULE,RED,GREEN};          /**      * 颜色属性      * @return      */     Color fruitColor() default Color.GREEN;  }  /**  * 水果供应者注解  * @author peida  *  */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitProvider {     /**      * 供应商编号      * @return      */     public int id() default -1;          /**      * 供应商名称      * @return      */     public String name() default "";          /**      * 供应商地址      * @return      */     public String address() default ""; }  /***********注解使用***************/  public class Apple {          @FruitName("Apple")     private String appleName;          @FruitColor(fruitColor=Color.RED)     private String appleColor;          @FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")     private String appleProvider;          public void setAppleColor(String appleColor) {         this.appleColor = appleColor;     }     public String getAppleColor() {         return appleColor;     }          public void setAppleName(String appleName) {         this.appleName = appleName;     }     public String getAppleName() {         return appleName;     }          public void setAppleProvider(String appleProvider) {         this.appleProvider = appleProvider;     }     public String getAppleProvider() {         return appleProvider;     }          public void displayName(){         System.out.println("水果的名字是:苹果");     } }  /***********注解处理器***************/  public class FruitInfoUtil {     public static void getFruitInfo(Class<?> clazz){                  String strFruitName=" 水果名称:";         String strFruitColor=" 水果颜色:";         String strFruitProvicer="供应商信息:";                  Field[] fields = clazz.getDeclaredFields();                  for(Field field :fields){             if(field.isAnnotationPresent(FruitName.class)){                 FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);                 strFruitName=strFruitName+fruitName.value();                 System.out.println(strFruitName);             }             else if(field.isAnnotationPresent(FruitColor.class)){                 FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);                 strFruitColor=strFruitColor+fruitColor.fruitColor().toString();                 System.out.println(strFruitColor);             }             else if(field.isAnnotationPresent(FruitProvider.class)){                 FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);                 strFruitProvicer=" 供应商编号:"+fruitProvider.id()+" 供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address();                 System.out.println(strFruitProvicer);             }         }     } }  /***********输出结果***************/ public class FruitRun {      /**      * @param args      */     public static void main(String[] args) {                  FruitInfoUtil.getFruitInfo(Apple.class);              }  }  ====================================  水果名称:Apple  水果颜色:RED  供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦
复制代码

   Java注解的基础知识点(见下面导图)基本都过了一遍,下一篇我们通过设计一个基于注解的简单的ORM框架,来综合应用和进一步加深对注解的各个知识点的理解和运用。

 

 


分类: java
posted @ 2017-08-02 11:07 小马歌 阅读(19) | 评论 (0)编辑 收藏

2017年7月28日

http://blog.csdn.net/coslay/article/details/43458907
  1. 我们需要知道的是找到默认值的方法,掌握默认值的大概量级,在不同的版本下哪些是常用的默认参数,哪些是必须设置的参数,哪些是可以选择的、尽量不要去碰的设置。  
  1. 参数设置同一个类型会有多种参数,而且都有默认值,大家不要混用噢,混用的结果很多时候难以预料,虽然在某种情况下可能得到了一个测试结果,但如果你没有真正了解JVM的内核源码,是不可能知道所有细节的,即测试结果不能当成任何场景下的一个结论,只能作为一种参考。  

本篇文章基于Java 6update 21oder 21之后)版本, HotSpot JVM 提供给了两个新的参数,在JVM启动后,在命令行中可以输出所有XX参数和值。

  1. -XX:+PrintFlagsFinal and -XX:+PrintFlagsInitial  

让我们现在就了解一下新参数的输出。以 -client 作为参数的 -XX:+PrintFlagsFinal   的结果是一个按字母排序的590个参数表格(注意,每个release版本参数的数量会不一样)

  1. $ java -client -XX:+PrintFlagsFinal Benchmark  
  2. [Global flags]  
  3. uintx AdaptivePermSizeWeight               = 20               {product}  
  4. uintx AdaptiveSizeDecrementScaleFactor     = 4                {product}  
  5. uintx AdaptiveSizeMajorGCDecayTimeScale    = 10               {product}  
  6. uintx AdaptiveSizePausePolicy              = 0                {product}[...]  
  7. uintx YoungGenerationSizeSupplementDecay   = 8                {product}  
  8. uintx YoungPLABSize                        = 4096             {product}  
  9.  bool ZeroTLAB                             = false            {product}  
  10.  intx hashCode                             = 0                {product}  

(校对注:你可以尝试在命令行输入上面的命令,亲自实现下)

表格的每一行包括五列,来表示一个XX参数。第一列表示参数的数据类型,第二列是名称,第四列为值,第五列是参数的类别。第三列”=”表示第四列是参数的默认值,而”:=” 表明了参数被用户或者JVM赋值了。

注意对于这个例子我只是用了Benchmark类,因为这个系列前面的章节也是用的这个类。甚至没有一个主类的情况下你能得到相同的输出,通过运行Java 带另外的参数 -version.现在让我们检查下 server VM提供了多少个参数。我们也能指定参数-XX:+UnlockExperimentalVMOptions 和-XX:+UnlockDiagnosticVMOptions ;来解锁任何额外的隐藏参数。

  1. $ java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Benchmark  
724个参数,让我们看一眼那些已经被赋值的参数。
  1. $ java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Benchmark | grep ":"  
  2. uintx InitialHeapSize                     := 57505088         {product}  
  3. uintx MaxHeapSize                         := 920649728        {product}  
  4. uintx ParallelGCThreads                   := 4                {product}  
  5.  bool PrintFlagsFinal                     := true             {product}  
  6.  bool UseParallelGC                       := true             {product}  

(校对注:这个命令非常有用)我们仅设置一个自己的参数 -XX:+PrintFlagsFinal。其他参数通过server VM基于系统设置的,以便以合适的堆大小和GC设置运行。

如果我们只想看下所有XX参数的默认值,能够用一个相关的参数,-XX:+PrintFlagsInitial  。 用 -XX:+PrintFlagsInitial, 只是展示了第三列为“=”的数据(也包括那些被设置其他值的参数)。

然而,注意当与-XX:+PrintFlagsFinal 对比的时候,一些参数会丢失,大概因为这些参数是动态创建的。

研究表格的内容是很有意思的,通过比较client和server VM的行为,很明显了解哪些参数会影响其他的参数。有兴趣的读者,可以看一下这篇不错文章Inspecting HotSpot JVM Options。这个文章主要解释了第五列的参数类别。

-XX:+PrintCommandLineFlags

让我们看下另外一个参数,事实上这个参数非常有用: -XX:+PrintCommandLineFlags。这个参数让JVM打印出那些已经被用户或者JVM设置过的详细的XX参数的名称和值。

换句话说,它列举出 -XX:+PrintFlagsFinal的结果中第三列有":="的参数。以这种方式,我们可以用-XX:+PrintCommandLineFlags作为快捷方式来查看修改过的参数。看下面的例子。

  1. $ java -server -XX:+PrintCommandLineFlags Benchmark   

  1. -XX:InitialHeapSize=57505088 -XX:MaxHeapSize=920081408 -XX:ParallelGCThreads=4 -XX:+PrintCommandLineFlags -XX:+UseParallelGC  

现在如果我们每次启动java 程序的时候设置 -XX:+PrintCommandLineFlags 并且输出到日志文件上,这样会记录下我们设置的JVM 参数对应用程序性能的影响。类似于 -showversion(见 Part1),我建议 –XX:+PrintCommandLineFlags 这个参数应该总是设置在JVM启动的配置项里。因为你从不知道你什么时候会需要这些信息。

奇怪的是在这个例子中,通过 -XX:+PrintCommandLineFlags 列出堆的最大值会比通过-XX:+PrintFlagsFinal列举出的相应值小一点。如果谁知道两者之间不同的原因,请告诉我。

转载自:http://ifeve.com/useful-jvm-flags-part-3-printing-all-xx-flags-and-their-values/



   Java程序员有时候需要了解JVM相关的参数,不管是出于好奇或者工作需要。Oracle的文档中列出了一些,(点击这里),单并不是全部,而且有些参数的设置会默认启用或者关闭其他一些参数,而在某些情况下设置某个参数是不会生效的。还有些时候你想让JVM做某些事情,但是你不知道那个参数可以用。下面介绍一些办法用以列出所有参数,这样你在研究或者Google的时候也比较有明确的目标。

    如果你想查看一下线上正在运行的JVM到底设置了那些参数,生效的是那些,可能用到的方法:

    1. 在Linux下用ps命令找到启动Java应用时的参数

  1. ps -ef | grep "your java app name"    

    这个命令会打出你启动Java应用时传给java命令的所有参数,你可以看到里面的JVM参数。

    2.直接看启动脚本,或者参数配置

    你未必能找到所有设置这JVM参数的地方,容易遗漏。

    一般来讲以上两种办法都需要对JVM了如指掌或者非常熟悉,至少对特定的参数。

    

    其实JVM中有一个参数-XX:+PrintFlagsFinal,可以打印出几乎所有的JVM支持的参数以及他们的默认值。如果你想要查看你的Java应用到底使用了那些参数,只要在启动的时候加上这个参数就可以了。

    1.查看你使用的JDK支持的参数

  1. java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal -version    

    2.打印Java应用启用的JVM参数

  1. java -XX:+PrintFlagsFinal -classpath=/path/to/your/libs MainClass    

    3.如果你的Java应用已经是运行状态了,你想查看某个JVM参数生效没有可以使用jinfo这个工具。比如说大名鼎鼎的G1垃圾回收器,在JDK7update3中不论是客户端(-client)还是服务器(-server)模式下都不是默认启动的。

    jinfo是随JDK一起发布的,使用时先用jps找到Java应用的pid。直接运行jinfo可以查看使用说明。

  1. jinfo -flag UseParallelOldGC 31072    

    3.如果你的Java应用已经是运行状态了,你想查看某个JVM参数生效没有可以使用jinfo这个工具。比如说大名鼎鼎的G1垃圾回收器,在JDK7update3中不论是客户端(-client)还是服务器(-server)模式下都不是默认启动的。

    jinfo是随JDK一起发布的,使用时先用jps找到Java应用的pid。直接运行jinfo可以查看使用说明。

  1. jinfo -flag UseParallelOldGC 31072  <span style="color: rgb(0, 204, 0); line-height: 18px; font-family: Consolas, 'Courier New', Courier, mono, serif; background-color: inherit;"> </span>  
  1. -XX:+UseParallelOldGC    
    JDK中实用的工具还很多,可以逐个的体验一下${JAVA_HOME}/bin目录中的每个命令,有惊喜。


参考:http://blog.csdn.net/redhat456/article/details/7360249

0
posted @ 2017-07-28 11:01 小马歌 阅读(28) | 评论 (0)编辑 收藏

2017年7月27日

from:http://www.jianshu.com/p/2750c7c202ef

上周有幸给部门的小伙伴分享了一些JVM相关的知识,在整个做PPT的过程中,也是对一个领域的碎片知识的整理,本文将针对虚拟机GC相关的一些内容进行整理,本文不会涉及到G1收集器。

在Hotspot VM实现中,主要有两大类GC

  1. Partial GC:并不会堆整个GC堆进行收集
    • young gc:只收集 young gen 的GC
    • old gc:只收集 old gen 的GC,只有CMS的 concurrent collection
    • mixed GC:收集整个 young gen 以及部分 old gen 的GC,只有G1
  2. Full GC:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)等

其实在各种文章或书上还可以看到Minor GC、Major GC的字眼,其中minor GC和young gc对应,而Major GC通常是和Full GC是等价的,由于HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,所以Major GC有时也可能是指old gc,在下定论之前一定要先问清楚。

单线程、并行、并发

在GC收集器实现中,分为了单线程、并行和并发。
单线程收集器:如 Serial GC,这个比较好理解,即垃圾收集过程中只有单一线程在进行收集工作,实现也最简单。

并行收集器:如Parallel GC,每次运行时,不管是YGC,还是FGC,会 stop-the-world,暂停所有的用户线程,并采用多个线程同时进行垃圾收集。

并发收集器:如CMS GC,在新生代进行垃圾收集时和并行收集器类似,都是并行收集(当然具体算法中,你也可以设置成采用单线程进行收集),而且都会stop-the-world,主要的区别在于老年代的收集上,CMS在老年代进行垃圾收集时,大部分时间可以和用户线程并发执行的,只有小部分的时间stop-the-world,这就是它的优势,可以大大降低应用的暂停时间,当然也是有劣势的。

算法组合

Hotspot VM实现的几种GC算法组合中,其中CMS GC使用最广,因为现在都是大内存时代。

1、Serial GC

Serial generational collector (-XX:+UseSerialGC)
是全局范围的Full GC,这种算法组合是最早出现的,当年的Java堆内存大小都还不大,使用Serial GC进行单线程收集,还感觉不出来GC耗时导致应用暂停的问题

2、Parallel GC

Parallel for young space, serial for old space generational collector (-XX:+UseParallelGC).
Parallel for young and old space generational collector (-XX:+UseParallelOldGC)
当Java堆慢慢变大时,发现已经无法忍受GC耗时带来的应用暂停了,出现了Parallel GC,采用多线程的方式进行垃圾收集,很明显可以提升垃圾收集效率。

3、CMS GC

Concurrent mark sweep with serial young space collector (-XX:+UseConcMarkSweepGC
–XX:-UseParNewGC)
Concurrent mark sweep with parallel young space collector (-XX:+UseConcMarkSweepGC)
当Java堆达到更大时,比如8G,使用Parallel GC带来的应用暂停已经很明显了,所有又出现了 CMS GC,这是目前我看到线上环境使用的比较多的GC策略,在参数中添加-XX:+UseConcMarkSweepGC,对于 young gen,会自动选用 ParNewGC,不需要额外添加 -XX:+UseParNewGC

CMS虽然好,因为它的特殊算法,大部分的收集过程可以和用户线程并发执行,大大降低应用的暂停时间,不过也会带来负面影响,在收集完 old gen 之后,CMS并不会做整理过程,会产生空间碎片,如果这些碎片空间得不到利用,就会造成空间的浪费,整个过程中可能发生 concurrent mode failure,导致一次真正意义的 full gc,采用单线程对整个堆(young+old+perm) 使用MSC(Mark-Sweep-Compact)进行收集,这个过程意味着很慢很慢很慢,而且这个碎片问题是无法预测的.

4、G1 GC

G1 garbage collector (-XX:+UseG1GC),本文不对G1进行介绍

触发条件

young gc

对于 young gc,触发条件似乎要简单很多,当 eden 区的内存不够时,就会触发young gc,我们看看在 eden 区给对象分配一块内存是怎样一个过程,画了一个简单的流程图,我一直觉得一个好的示意图可以让一个枯燥的过程变得更有意思。

在 eden 区分配空间内存不足时有两种情况,为对象分配内存、为TLAB分配内存,总之就是内存不够,需要进行一次 young gc 为eden区腾出空间为后续的内存申请做准备,然后由一个用户线程通知VM Thread,接下去要执行一次 young gc。

full gc

1、old gen 空间不足

当创建一个大对象、大数组时,eden 区不足以分配这么大的空间,会尝试在old gen 中分配,如果这时 old gen 空间也不足时,会触发 full gc,为了避免上述导致的 full gc,调优时应尽量让对象在 young gc 时就能够被回收,还有不要创建过大的对象和数组。

2、统计得到的 young gc 晋升到 old gen的对象平均总大小大于old gen 的剩余空间

当准备触发一次 young gc时,会判断这次 young gc 是否安全,这里所谓的安全是当前老年代的剩余空间可以容纳之前 young gc 晋升对象的平均大小,或者可以容纳 young gen 的全部对象,如果结果是不安全的,就不会执行这次 young gc,转而执行一次 full gc

3、perm gen 空间不足

如果有perm gen的话,当系统中要加载的类、反射的类和调用的方法较多,而且perm gen没有足够空间时,也会触发一次 full gc

4、ygc出现 promotion failure

promotion failure 发生在 young gc 阶段,即 cms 的 ParNewGC,当对象的gc年龄达到阈值时,或者 eden 的 to 区放不下时,会把该对象复制到 old gen,如果 old gen 空间不足时,会发生 promotion failure,并接下去触发full gc

在GC日志中,有时会看到 concurrent mode failure 关键字,这是因为什么原因导致的问题呢? 对这一块的理解,很多文章都是说因为 concurrent mode failure 导致触发full gc,其实应该反过来,是full gc 导致的 concurrent mode failure,在cms gc的算法实现中,通常说的cms是由一个后台线程定时触发的,默认每2秒检查一次old gen的内存使用率,当 old gen 的内存使用率达到-XX:CMSInitiatingOccupancyFraction设置的值时,会触发一次 cms gc,对 old gen 进行并发收集,而真正的 full gc 是通过 vm thread线程触发的,而且在判断当前ygc会失败的情况下触发full gc,如上一次ygc出现了promotion failure,如果执行 full gc 时,发现后台线程正在执行 cms gc,就会导致 concurrent mode failure。

对于以上这些情况,CMSInitiatingOccupancyFraction参数的设置就显得尤为重要,设置的太大的话,发生CMS时的剩余空间太小,在ygc的时候容易发生promotion failure,导致 concurrent mode failure 发生的概率就增大,如果设置太小的话,会导致 cms gc 的频率会增加,所以需要根据应用的需求对该参数进行调优。

5、执行 System.gc()jmap -histo:live <pid>jmap -dump ...

参考资料
Major GC和Full GC的区别是什么?触发条件呢

个人公众号

posted @ 2017-07-27 14:33 小马歌 阅读(25) | 评论 (0)编辑 收藏

2017年7月18日

from:http://www.cnblogs.com/foohack/p/5627163.html

Cassandra note:

依赖:需要java 8 (http://www.oracle.com/technetwork/java/javase/downloads/index.html)

数据模型: 与Hbase同样是属于列式数据库,Key-Value存储系统。(http://www.ibm.com/developerworks/cn/opensource/os-cn-cassandra/)
http://www.datastax.com/dev/blog/basic-rules-of-cassandra-data-modeling

集群中的数据是靠partion key的hash code均匀映射到不同的节点上去的。partionkey是primary key的第一个元素,所以选一个好的主
key才能使数据更好的均匀存储在不同的节点上。


Cassandra的节点实例叫Cluster,里面可以包含一个或多个键空间(KeysSpace).键空间是存放列族(Column Family)的容器,相当于
关系数据中的database,schema。列族是存放列(column)的容器,类似与关系数据库中的table。超级列(Super column)是一种
特殊的列,它的value值可以包含多个column。 columns是cassandra的最基本单位,有name,value,timestamp构成。

 

列式数据库的优点: 
1.适合存储大量数据,而不是小量数据。因为数据是是基于列存储的,所以可以忽略不需要的列的数据,提高查找效率。
与之对应的是行数据库。
2.高压缩比。节省存储空间,也节省CPU和内存。
3.高装载速度。

列式数据库的缺点:
1.不适合扫描小量数据。
2.不适合随机更新数据。
3.不适合做含有删除的更新的实时操作。


查询数据:
Cassandra有自己的一套查询语言CQL(类似SQL),在数据访问方式上亦是如此。客户端可以与集群中的任意节点相连,并访问任意的数据。

cassandra在写入数据之前需要记录日志(CommitLog),然后数据开始写入到 Column Family 对应的 Memtable 中,
Memtable 是一种按照 key 排序数据的内存结构,在满足一定条件时,再把 Memtable 的数据批量的刷新到磁盘上,存储为 SSTable 。

存储二进制大文件(不推荐存储):http://wiki.apache.org/cassandra/FAQ#large_file_and_blob_storage

Cassandra的GUI管理工具整理:http://wiki.apache.org/cassandra/FAQ#gui 也有自带的CLI工具连接Cassandra

Cassandra集群种子的概念(很重要):http://wiki.apache.org/cassandra/FAQ#seed
类似与Cassandra集群的初始化节点(集线器),各个节点通过种子节点互相学习(交换)各自的数据(状态),所以新加入的Cassandra节点都需要给它
指定种子节点,下次启动的时候就不需要了。


Cassandra 的C++ 接口:
Cassandra的各种编程语言的接口是有Thrift这个开源工具生成的,语言无关的Thrift输入文件(cassandra.thrift)Cassandra已经自带
,安装thrift运行 thrift.exe -gen cpp cassandra.thrift生成就可以了。 cpp的接口依赖thrift的核心库叫libthrift,libthrift依赖boost1.53.0
版本和openssl


Cassandra windows 安装配置:
解压,配置好CASSANDRA_HOME环境变量的路径(也就是你解压的cassandra根目录),然后运行bin下的cassandra.bat,如果发现logs
目录底下的system.log文件中有INFO - Starting up server gossip,那么恭喜你,Cassandra已经在你的本机启动起来了。

 

 

*****************************************Cassandra的基本操作************************************************

数据模型:多维的hash表,每行可以有不同的列。每行都有个键. keyspace包含若干列族
(列族和表是同一个概念:http://stackoverflow.com/questions/18824390/whats-the-difference-between-creating-a-table-and-creating-a-columnfamily-in-ca),
keyspace在逻辑上是容纳列族和某些配置属性的命名空间。列族定义了相关的数据名字和它们的排序方式。

入门必读:http://wiki.apache.org/cassandra/GettingStarted

CQLSH中运行外部创建的cql脚本文件: SOURCE '[file_path]'

键空间的创建: 
CREATE KEYSPACE [keyspace_name] WITH REPLICATION = {'class' : 'NetworkTopologyStrategy', 'datacenter1' : 3};

----键空间的创建要附带副本属性,class可以指定NetworkTopologyStrategy或SimpleStrategy。SimpleStrategy只用于测试评估Cassandra
生产环境使用NetworkTopologyStrategy. 键空间类似关系型数据库中的数据库(database)

键空间的修改:
ALTER KEYSPACE [keyspace_name] WITH REPLICATION = {};

键空间的删除:
DROP KEYSPACE [keyspace_name]

键空间的使用:
USE [keyspace_name]


列出已存在的键空间:
DESCRIBE keyspaces;

列出某个键空间下的所有表:
USE [keyspace_name];
DESCRIBE tables;

列出某个键空间下的所有列族:
USE [keyspace_name];
DESCRIBE columnfamilies;

列出特定的表的基本信息:
DESCRIBE TABLE [keyspace_name].[table_name];

建表:
http://docs.datastax.com/en/cql/3.1/cql/cql_using/create_table_t.html
表的主键可以是复合的,就是多个列组成一个主键:
CREATE TABLE emp (
empID int,
deptID int,
first_name varchar,
last_name varchar,
PRIMARY KEY (empID, deptID)); //主键的第一个键就是分区键(empID),分区键的目的就是把表中的数据均分到集群中的
各个节点中

更改表:
ALERT TABLE [table_name] [some change]
https://docs.datastax.com/en/cql/3.0/cql/cql_reference/alter_table_r.html
没办法更改主键,因为主键涉及到数据的物理储存

给表的某列建立索引:
CREATE INDEX ON [table_name] (column_name);


查表:
SELCT [column_name] FROM [keyspace].[table_name] WHERE [column_name] = [value] //其中column_name必须是主键的其中部分,
如果有多个条件必须其中有一个是分区键

更新表中的值:
UPDATE [keyspace].[table_name] SET [column_name] = [new_value] WHERE [column_name]=[value]

删除表中列或行:
删除列中值:DELETE [column_name] FROM [table_name] WHERE [column_name] = [value] # 同查表
删除一整行:DELETE FROM [table_name] WHERE [column_name] = [value] #同上

自定义数据类型(http://docs.datastax.com/en/cql/3.1/cql/cql_using/cqlUseUDT.html):
CREATE TYPE [keyspace_name].[type_name] (
street text,
city text,
zip_code int,
phones set<text>
);
自定义的数据类型的字面值是json-style的风格。

内建的数据类型(http://docs.datastax.com/en/cql/3.1/cql/cql_reference/cql_data_types_c.html):
ascii,bigint,blob,boolean,counter,double,float,inet,int,list,map,set,text,uuid,
timestamp,tuple,varchar(UTF-8 encoded string) ,varint


查看集群信息:
SELECT * FROM system.peers;

本地帮助文档的查看:
HELP [COMMAND]
比如:查看创建键空间 HELP CREATE_KEYSPACE;


CQL语句支持多语句提交(Batch):
可以减少Node之间的流量交换,类似于事务,是原子的。
http://docs.datastax.com/en/cql/3.1/cql/cql_reference/batch_r.html#reference_ds_djf_xdd_xj__batch-conditional
http://docs.datastax.com/en/cql/3.1/cql/cql_using/use-batch-static.html


给数据设置存活期:
超过存活期的数据,将被销毁。
INSERT INTO [table_name]
([column_name1], [column_name2])
VALUES ([column_value1], [column_value2]) USING TTL 86400; # 86400 sec 大概是一天的存活期
是给column_name2设置的


UPDATE [table_name] USING TTL 432000 SET [column_name] = [column_value]
WHERE user_name = 'cbrown';

posted @ 2017-07-18 09:57 小马歌 阅读(32) | 评论 (0)编辑 收藏

2017年6月23日

from:http://blog.csdn.net/MoreWindows/article/category/859207 

【白话经典算法系列之十七】 数组中只出现一次的数

数组A中,除了某一个数字x之外,其他数字都出现了三次,而x出现了一次。请给出最快的方法找到x。 这个题目非常有意思,在本人博客中有《位操作基础篇之位操作全面总结》这篇文章介绍了使用位操作的异或来解决——数组中其他数字出现二次,而x出现一次,找出x。有《【白话经典算法系列之十二】数组中只出现1次的两个数字(百度面试题)》这边文章介绍了分组异或的方法来解决——数组中其他数字出现二次,而x和y出现一次,找出x和y。而这个题目则是其他数字出现3次,x出现一次。...
2013-10-21 11:49 阅读(32100) 评论(34)
首先看看题目要求: 给定一个无序的整数数组,怎么找到第一个大于0,并且不在此数组的整数。比如[1,2,0]返回3,[3,4,-1,1]返回2,[1, 5, 3, 4, 2]返回6,[100, 3, 2, 1, 6,8, 5]返回4。要求使用O(1)空间和O(n)时间。 这道题目初看没有太好的思路,但是借鉴下《白话经典算法系列之十一道有趣的GOOGLE面试题》这篇文章,我们不发现使用“基数排序”正好可以用来解决这道题目...
2013-10-15 10:17 阅读(13580) 评论(11)
【白话经典算法系列之十五】“一步千里”之数组找数 有这样一个数组A,大小为n,相邻元素差的绝对值都是1。如:A={4,5,6,5,6,7,8,9,10,9}。现在,给定A和目标整数t,请找到t在A中的位置。除了依次遍历,还有更好的方法么?...
2013-09-02 12:57 阅读(25918) 评论(39)
【白话经典算法系列之十三】随机生成和为S的N个正整数——投影法      随机生成和为S的N个正整数有很多种解法。下面讲解一种比较高效且比较有趣味性的解法——投影法。    以生成和为20的4个数为例,可以先生成随机生成0到20之间的三个数字再排序,假设得到了4,7,18。然后在X-Y数轴上画出这三个数,如下图:然后将这些数值投影到Y轴上,可得下图:由图很容易看出AB,BC,CD,DE这四段的长度...
2013-01-04 13:46 阅读(15710) 评论(46)
微博http://weibo.com/MoreWindows已开通,欢迎关注。本系列文章地址:http://blog.csdn.net/MoreWindows/article/category/859207首先来看题目要求:在一个数组中除两个数字只出现1次外,其它数字都出现了2次, 要求尽快找出这两个数字。    考虑下这个题目的简化版——数组中除一个数字只出现1次外,其它数字都成对出现,要求尽快...
2012-11-27 09:17 阅读(35498) 评论(51)
微博http://weibo.com/MoreWindows已开通,欢迎关注。本系列文章地址:http://blog.csdn.net/MoreWindows/article/category/859207 上一篇《白话经典算法系列之十一道有趣的GOOGLE面试题》中对一道有趣的GOOGLE面试题进行了详细的讲解,使用了类似于基数排序的做法在O(N)的时间复杂度和O(1)的空间复杂度完成了题目的要...
2012-11-23 07:57 阅读(24806) 评论(52)
微博http://weibo.com/MoreWindows已开通,欢迎关注。最近在微博上看到一道有趣的GOOGLE面试题,见下图:文字版:一个大小为n的数组,里面的数都属于范围[0, n-1],有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间。     这个题目要求用O(n)的时间复杂度,这意味着只能遍历数组一次。同时还要寻找重复元素,很容易想到建立哈希表来完成,遍历数组...
2012-11-21 09:03 阅读(47907) 评论(87)
首先来看看原题 微软2010年笔试题在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序数对。一个排列中逆序的总数就称为这个排列的逆序数。如{2,4,3,1}中,2和1,4和3,4和1,3和1是逆序数对,因此整个数组的逆序数对个数为4,现在给定一数组,要求统计出该数组的逆序数对个数。 计算数列的逆序数对个数最简单的方便就最从前向后依次统计每个数字与它后面...
2012-10-15 09:15 阅读(30367) 评论(36)
在我的博客对冒泡排序,直接插入排序,直接选择排序,希尔排序,归并排序,快速排序和堆排序这七种常用的排序方法进行了详细的讲解,并做成了电子书以供大家下载。下载地址为:http://download.csdn.net/detail/morewindows/4443208。       有网友提议到这本《MoreWindows白话经典算法之七大排序》电子书讲解细致用来平时学习是非常好的,但是页数有22页...
2012-09-10 10:08 阅读(42997) 评论(26)
堆排序与快速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法。学习堆排序前,先讲解下什么是数据结构中的二叉堆。二叉堆的定义二叉堆是完全二叉树或者是近似完全二叉树。二叉堆满足二个特性:1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父...
2011-08-22 20:04 阅读(338481) 评论(188)
快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,再加上快速排序思想----分治法也确实实用,因此很多软件公司的笔试面试,包括像腾讯,微软等知名IT公司都喜欢考这个,还有大大小的程序方面的考试如软考,考研中也常常出现快速排序的身影。总的说来,要直接默写出快速排序还是有一定难度的,因为本人就自己的理解对快速排序作了下白话解释,希望对大家理解有帮助,达到快速排序,快...
2011-08-13 17:19 阅读(418202) 评论(284)
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。//将有序数组a[]和b[]合并到c[]中 void MemeryArra...
2011-08-11 11:01 阅读(275147) 评论(154)
直接选择排序和直接插入排序类似,都将数据分为有序区和无序区,所不同的是直接播放排序是将无序区的第一个元素直接插入到有序区以形成一个更大的有序区,而直接选择排序是从无序区选一个最小的元素直接放到有序区的最后。   设数组为a[0…n-1]。 1.      初始时,数组全为无...
2011-08-09 11:15 阅读(29055) 评论(38)
希尔排序的实质就是分组插入排序,该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。   该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增...
2011-08-08 11:41 阅读(148913) 评论(82)
直接插入排序(Insertion Sort)的基本思想是:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中的适当位置,直到全部记录插入完成为止。   设数组为a[0…n-1]。 1.      初始时,a[0]自成1个有序区,无序区为a[1..n-1]。...
2011-08-06 19:27 阅读(118661) 评论(81)
冒泡排序是非常容易理解和实现,,以从小到大排序举例: 设数组长度为N。 1.比较相邻的前后二个数据,如果前面数据大于后面的数据,就将二个数据交换。 2.这样对数组的第0个数据到N-1个数据进行一次遍历后,最大的一个数据就“沉”到数组第N-1个位置。 3.N=N-1,如果N...
2011-08-06 19:20 阅读(166505) 评论(94)
posted @ 2017-06-23 11:17 小马歌 阅读(88) | 评论 (0)编辑 收藏

2017年6月8日

from:http://blog.csdn.net/liaodehong/article/details/51605457

本文将深入浅出地讲解 JIT 编译器在 JVM 中的运作原理,使读者能够更好的理解 Java 底层机制并且为读者在 Java 性能优化领域打开更广的视野。

JIT 简介

JIT 是 just in time 的缩写, 也就是即时编译编译器。使用即时编译器技术,能够加速 Java 程序的执行速度。下面,就对该编译器技术做个简单的讲解。

首先,我们大家都知道,通常通过 javac 将程序源代码编译,转换成 java 字节码,JVM 通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译。很显然,经过解释执行,其执行速度必然会比可执行的二进制字节码程序慢很多。为了提高执行速度,引入了 JIT 技术。

在运行时 JIT 会把翻译过的机器码保存起来,以备下次使用,因此从理论上来说,采用该 JIT 技术可以接近以前纯编译技术。下面我们看看,JIT 的工作过程。

JIT 编译过程

当 JIT 编译启用时(默认是启用的),JVM 读入.class 文件解释后,将其发给 JIT 编译器。JIT 编译器将字节码编译成本机机器代码,下图展示了该过程。

图 1. JIT 工作原理图
图 1. JIT 工作原理图

回页首

Hot Spot 编译

当 JVM 执行代码时,它并不立即开始编译代码。这主要有两个原因:

首先,如果这段代码本身在将来只会被执行一次,那么从本质上看,编译就是在浪费精力。因为将代码翻译成 java 字节码相对于编译这段代码并执行代码来说,要快很多。

当然,如果一段代码频繁的调用方法,或是一个循环,也就是这段代码被多次执行,那么编译就非常值得了。因此,编译器具有的这种权衡能力会首先执行解释后的代码,然后再去分辨哪些方法会被频繁调用来保证其本身的编译。其实说简单点,就是 JIT 在起作用,我们知道,对于 Java 代码,刚开始都是被编译器编译成字节码文件,然后字节码文件会被交由 JVM 解释执行,所以可以说 Java 本身是一种半编译半解释执行的语言。Hot Spot VM 采用了 JIT compile 技术,将运行频率很高的字节码直接编译为机器指令执行以提高性能,所以当字节码被 JIT 编译为机器码的时候,要说它是编译执行的也可以。也就是说,运行时,部分代码可能由 JIT 翻译为目标机器指令(以 method 为翻译单位,还会保存起来,第二次执行就不用翻译了)直接执行。

第二个原因是最优化,当 JVM 执行某一方法或遍历循环的次数越多,就会更加了解代码结构,那么 JVM 在编译代码的时候就做出相应的优化。

我们将在后面讲解这些优化策略,这里,先举一个简单的例子:我们知道 equals() 这个方法存在于每一个 Java Object 中(因为是从 Object class 继承而来)而且经常被覆写。当解释器遇到 b = obj1.equals(obj2) 这样一句代码,它则会查询 obj1 的类型从而得知到底运行哪一个 equals() 方法。而这个动态查询的过程从某种程度上说是很耗时的。

寄存器和主存

其中一个最重要的优化策略是编译器可以决定何时从主存取值,何时向寄存器存值。考虑下面这段代码:

清单 1. 主存 or 寄存器测试代码
public class RegisterTest {  private int sum;   public void calculateSum(int n) {  for (int i = 0; i < n; ++i) {  sum += i;  }  } }

在某些时刻,sum 变量居于主存之中,但是从主存中检索值是开销很大的操作,需要多次循环才可以完成操作。正如上面的例子,如果循环的每一次都是从主存取值,性能是非常低的。相反,编译器加载一个寄存器给 sum 并赋予其初始值,利用寄存器里的值来执行循环,并将最终的结果从寄存器返回给主存。这样的优化策略则是非常高效的。但是线程的同步对于这种操作来说是至关重要的,因为一个线程无法得知另一个线程所使用的寄存器里变量的值,线程同步可以很好的解决这一问题,有关于线程同步的知识,我们将在后续文章中进行讲解。

寄存器的使用是编译器的一个非常普遍的优化。

回到之前的例子,JVM 注意到每次运行代码时,obj1 都是 java.lang.String 这种类型,那么 JVM 生成的被编译后的代码则是直接调用 String.equals() 方法。这样代码的执行将变得非常快,因为不仅它是被编译过的,而且它会跳过查找该调用哪个方法的步骤。

当然过程并不是上面所述这样简单,如果下次执行代码时,obj1 不再是 String 类型了,JVM 将不得不再生成新的字节码。尽管如此,之后执行的过程中,还是会变的更快,因为同样会跳过查找该调用哪个方法的步骤。这种优化只会在代码被运行和观察一段时间之后发生。这也就是为什么 JIT 编译器不会理解编译代码而是选择等待然后再去编译某些代码片段的第二个原因。

回页首

初级调优:客户模式或服务器模式

JIT 编译器在运行程序时有两种编译模式可以选择,并且其会在运行时决定使用哪一种以达到最优性能。这两种编译模式的命名源自于命令行参数(eg: -client 或者 -server)。JVM Server 模式与 client 模式启动,最主要的差别在于:-server 模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升。原因是:当虚拟机运行在-client 模式的时候,使用的是一个代号为 C1 的轻量级编译器,而-server 模式启动的虚拟机采用相对重量级代号为 C2 的编译器。C2 比 C1 编译器编译的相对彻底,服务起来之后,性能更高。

通过 java -version 命令行可以直接查看当前系统使用的是 client 还是 server 模式。例如:

图 2. 查看编译模式
图 2. 查看编译模式

回页首

中级编译器调优

大多数情况下,优化编译器其实只是选择合适的 JVM 以及为目标主机选择合适的编译器(-cient,-server 或是-xx:+TieredCompilation)。多层编译经常是长时运行应用程序的最佳选择,短暂应用程序则选择毫秒级性能的 client 编译器。

优化代码缓存

当 JVM 编译代码时,它会将汇编指令集保存在代码缓存。代码缓存具有固定的大小,并且一旦它被填满,JVM 则不能再编译更多的代码。

我们可以很容易地看到如果代码缓存很小所具有的潜在问题。有些热点代码将会被编译,而其他的则不会被编译,这个应用程序将会以运行大量的解释代码来结束。

这是当使用 client 编译器模式或分层编译时很频繁的一个问题。当使用普通 server 编译器模式时,编译合格的类的数量将被填入代码缓存,通常只有少量的类会被编译。但是当使用 client 编译器模式时,编译合格的类的数量将会高很多。

在 Java 7 版本,分层编译默认的代码缓存大小经常是不够的,需要经常提高代码缓存大小。大型项目若使用 client 编译器模式,则也需要提高代码缓存大小。

现在并没有一个好的机制可以确定一个特定的应用到底需要多大的代码缓存。因此,当需要提高代码缓存时,这将是一种凑巧的操作,一个通常的做法是将代码缓存变成默认大小的两倍或四倍。

可以通过 –XX:ReservedCodeCacheSize=Nflag(N 就是之前提到的默认大小)来最大化代码缓存大小。代码缓存的管理类似于 JVM 中的内存管理:有一个初始大小(用-XX:InitialCodeCacheSize=N 来声明)。代码缓存的大小从初始大小开始,随着缓存被填满而逐渐扩大。代码缓存的初始大小是基于芯片架构(例如 Intel 系列机器,client 编译器模式下代码缓存大小起始于 160KB,server 编译器模式下代码缓存大小则起始于 2496KB)以及使用的编译器的。重定义代码缓存的大小并不会真正影响性能,所以设置 ReservedCodeCacheSize 的大小一般是必要的。

再者,如果 JVM 是 32 位的,那么运行过程大小不能超过 4GB。这包括了 Java 堆,JVM 自身所有的代码空间(包括其本身的库和线程栈),应用程序分配的任何的本地内存,当然还有代码缓存。

所以说代码缓存并不是无限的,很多时候需要为大型应用程序来调优(或者甚至是使用分层编译的中型应用程序)。比如 64 位机器,为代码缓存设置一个很大的值并不会对应用程序本身造成影响,应用程序并不会内存溢出,这些额外的内存预定一般都是被操作系统所接受的。

编译阈值

在 JVM 中,编译是基于两个计数器的:一个是方法被调用的次数,另一个是方法中循环被回弹执行的次数。回弹可以有效的被认为是循环被执行完成的次数,不仅因为它是循环的结尾,也可能是因为它执行到了一个分支语句,例如 continue。

当 JVM 执行一个 Java 方法,它会检查这两个计数器的总和以决定这个方法是否有资格被编译。如果有,则这个方法将排队等待编译。这种编译形式并没有一个官方的名字,但是一般被叫做标准编译。

但是如果方法里有一个很长的循环或者是一个永远都不会退出并提供了所有逻辑的程序会怎么样呢?这种情况下,JVM 需要编译循环而并不等待方法被调用。所以每执行完一次循环,分支计数器都会自增和自检。如果分支计数器计数超出其自身阈值,那么这个循环(并不是整个方法)将具有被编译资格。

这种编译叫做栈上替换(OSR),因为即使循环被编译了,这也是不够的:JVM 必须有能力当循环正在运行时,开始执行此循环已被编译的版本。换句话说,当循环的代码被编译完成,若 JVM 替换了代码(前栈),那么循环的下个迭代执行最新的被编译版本则会更加快。

标准编译是被-XX:CompileThreshold=Nflag 的值所触发。Client 编译器模式下,N 默认的值 1500,而 Server 编译器模式下,N 默认的值则是 10000。改变 CompileThreshold 标志的值将会使编译器相对正常情况下提前(或推迟)编译代码。在性能领域,改变 CompileThreshold 标志是很被推荐且流行的方法。事实上,您可能知道 Java 基准经常使用此标志(比如:对于很多 server 编译器来说,经常在经过 8000 次迭代后改变次标志)。

我们已经知道 client 编译器和 server 编译器在最终的性能上有很大的差别,很大程度上是因为编译器在编译一个特定的方法时,对于两种编译器可用的信息并不一样。降低编译阈值,尤其是对于 server 编译器,承担着不能使应用程序运行达到最佳性能的风险,但是经过测试应用程序我们也发现,将阈值从 8000 变成 10000,其实有着非常小的区别和影响。

检查编译过程

中级优化的最后一点其实并不是优化本身,而是它们并不能提高应用程序的性能。它们是 JVM(以及其他工具)的各个标志,并可以给出编译工作的可见性。它们中最重要的就是--XX:+PrintCompilation(默认状态下是 false)。

如果 PrintCompilation 被启用,每次一个方法(或循环)被编译,JVM 都会打印出刚刚编译过的相关信息。不同的 Java 版本输出形式不一样,我们这里所说的是基于 Java 7 版本的。

编译日志中大部分的行信息都是下面的形式:

清单 2. 日志形式
timestamp compilation_id attributes (tiered_level) method_name size depot

这里 timestamp 是编译完成时的时间戳,compilation_id 是一个内部的任务 ID,且通常情况下这个数字是单调递增的,但有时候对于 server 编译器(或任何增加编译阈值的时候),您可能会看到失序的编译 ID。这表明编译线程之间有些快有些慢,但请不要随意推断认为是某个编译器任务莫名其妙的非常慢。

用 jstat 命令检查编译

要想看到编译日志,则需要程序以-XX:+PrintCompilation flag 启动。如果程序启动时没有 flag,您可以通过 jstat 命令得到有限的可见性信息。

Jstat 有两个选项可以提供编译器信息。其中,-compile 选项提供总共有多少方法被编译的总结信息(下面 6006 是要被检查的程序的进程 ID):

清单 3 进程详情
% jstat -compiler 6006 CompiledFailedInvalid TimeFailedTypeFailedMethod 206 0 0 1.97 0

注意,这里也列出了编译失败的方法的个数信息,以及编译失败的最后一个方法的名称。

另一种选择,您可以使用-printcompilation 选项得到最后一个被编译的方法的编译信息。因为 jstat 命令有一个参数选项用来重复其操作,您可以观察每一次方法被编译的情况。举个例子:

Jstat 对 6006 号 ID 进程每 1000 毫秒执行一次: %jstat –printcompilation 6006 1000,具体的输出信息在此不再描述。

回页首

高级编译器调优

这一节我们将介绍编译工作剩下的细节,并且过程中我们会探讨一些额外的调优策略。调优的存在很大程度上帮助了 JVM 工程师诊断 JVM 自身的行为。如果您对编译器的工作原理很感兴趣,这一节您一定会喜欢。

编译线程

从前文中我们知道,当一个方法(或循环)拥有编译资格时,它就会排队并等待编译。这个队列是由一个或很多个后台线程组成。这也就是说编译是一个异步的过程。它允许程序在代码正在编译时被继续执行。如果一个方法被标准编译方式所编译,那么下一个方法调用则会执行已编译的方法。如果一个循环被栈上替换方式所编译,那么下一次循环迭代则会执行新编译的代码。

这些队列并不会严格的遵守先进先出原则:哪一个方法的调用计数器计数更高,哪一个就拥有优先权。所以即使当一个程序开始执行,并且有大量的代码需要编译,这个优先权顺序将帮助并保证最重要的代码被优先编译(这也是为什么编译 ID 在 PrintComilation 的输出结果中有时会失序的另一个原因)。

当使用 client 编译器时,JVM 启动一个编译线程,而 server 编译器有两个这样的线程。当分层编译生效时,JVM 会基于某些复杂方程式默认启动多个 client 和 server 线程,涉及双日志在目标平台上的 CPU 数量。如下图所示:

分层编译下 C1 和 C2 编译器线程默认数量:

图 3. C1 和 C2 编译器默认数量
图 3. C1 C2 编译器默认数量

编译器线程的数量可以通过-XX:CICompilerCount=N flag 进行调节设置。这个数量是 JVM 将要执行队列所用的线程总数。对于分层编译,三分之一的(至少一个)线程被用于执行 client 编译器队列,剩下的(也是至少一个)被用来执行 server 编译器队列。

在何时我们应该考虑调整这个值呢?如果一个程序被运行在单 CPU 机器上,那么只有一个编译线程会更好一些:因为对于某个线程来说,其对 CPU 的使用是有限的,并且在很多情况下越少的线程竞争资源会使其运行性能更高。然而,这个优势仅仅局限于初始预热阶段,之后,这些具有编译资格的方法并不会真的引起 CPU 争用。当一个股票批处理应用程序运行在单 CPU 机器上并且编译器线程被限制成只有一个,那么最初的计算过程将比一般情况下快 10%(因为它没有被其他线程进行 CPU 争用)。迭代运行的次数越多,最初的性能收益就相对越少,直到所有的热点方法被编译完性能收益也随之终止。

回页首

结束语

本文详细介绍了 JIT 编译器的工作原理。从优化的角度讲,最简单的选择就是使用 server 编译器的分层编译技术,这将解决大约 90%左右的与编译器直接相关的性能问题。最后,请保证代码缓存的大小设置的足够大,这样编译器将会提供最高的编译性能。

转载自点击打开链接

posted @ 2017-06-08 17:27 小马歌 阅读(57) | 评论 (0)编辑 收藏
仅列出标题  下一页