[翻译]现代java开发指南 第二局部

现代java开发指南 第二片

其次片段:部署、监控 & 管理,性能分析以及标准测试

首先部分,第二部分

欢迎来到现代 Java
开发指南第二有的。在先是有遭遇,我们都显示了有关
Java 新的言语特色,库与工具。这些新的家伙而 Java
变成了一对一轻量级的开支环境,这个开条件具有初的构建工具、更爱采取的文档、富有表现力的代码还有用户级线程的面世。而在及时片受到,我们用比较代码层次重复强一层,讨论
Java 的运维———— Java
的安排、监控&管理,性能分析以及标准化测试。尽管此的例证都见面用 Java
来举行示意,但是我们谈论的情与持有的 JVM 语言都不无关系,而不仅仅是 Java
语言。

在开始前,我怀念略地回答瞬间首先局部读者的题目,并且澄清一下说之匪晓的地方。第一片段被最好给争议之地方出现于构建工具就同一节省。在那么同样节省吃,我写及现代的 Java 开发者使用 Gradle。有些读者对是提出异议,并且举出了例子来证明
Maven 同样也是一个分外好的工具。我个人喜好 Gradle 漂亮 DSL
和力所能及应用指令式代码来编排非通用的构建操作,同时我哉会明白好了声明式的
Maven 的偏好,即使这样做用大量之插件。因此,我认同:现代的 Java
开发者可能再次爱好 Maven 而非是 Gradle 。我还想说,虽然下 Gradle
不用了解 Groovy ,甚至人们希望以未是那规范的作业受到吗毫不了解 Groovy
。但是我弗见面这么,我起 Gradle 的在线例子中就学习了过多有效之 Groovy
的话语。

微读者指出自身在率先部分的代码示例中采取 Junit 和 Guava
,意味着自己故意放大其。好吧,我委发这么的想法。Guava
是一个特别实用之库,而
JUnit
是一个分外好之单元测试框架。虽然 TestNG 也蛮好,但是 JUnit
非常广阔,很少有人会选取别的就有优势的测试框架。

同等,就示例代码中测试用
Hamcrest
,一个读者指出
AssertJ,可能是一个比
Hamcrest 更好之抉择。

亟待明白到比如系列指南并无打算盖至 Java
的周,能认识及当时同接触很关键。所以当会时有发生过多老好的库房因为没有以篇章被冒出,我们从来不去探讨它们。我勾勒这卖指南的本心就是叫大家表示一下现代
Java 开发或是哪的。

稍微读者发表了他们再也爱短的 Javadoc 注释,这种注释不必像 Javadoc
标准形式那样需要将拥有的字段都写上。如下面的例证:

/**
 * This method returns the result.
 * @return the result
 */
 int getResult();

更爱好这样:

/**
 * Returns the result
 */
 int getResult();

本人完全同意。我当例子中概括示范了交集 Markdown 和正式的 Javadoc
标签的使用。这只是用来显示什么下,并无是企图将这种应用方法当成指导方针。

末,关于 Android 我起有口舌使说。 Android
系统经过一样雨后春笋变换之后,能够实施用 java (还有可能是别的 JVM
语言)写的代码,但是 Android 不是 JVM,并且实际 Android
无论以标准场合跟骨子里运用中为无净是 Java
(造成这个题材之原委是鲜单跨国公司,这里指谷歌和甲骨文,没有就 Java
的运及一个许可协议)。正因为 Android 不净是 Java
,所以于率先组成部分中讨论的内容针对 Android
可能立竿见影或者也可能没因此,而且为 Android 没有包括 JVM
,所以于就一部分议论的始末好少会应用到 Android 上面。

吓了,现在受咱回去正文。

现代 Java 的卷入和配置

对于不熟悉 Java 生态体系之人头来说,Java(或者其他 JVM
语言)源文件,被编绎成 .class 文件(本质上是 Java
二进制文件),每一个近似一个文件。打包这些 class
文件之主导机制就算管这些文件包在一块儿(这项工作便由构建工具要IDE来就)放到JAR(Java存档)文件,JAR
文件为 Java 二向前制包。 JAR 文件只是 Zip 压缩文件,它包括 class
文件,还有一个增大的清单文件用来描述内容,清单中尚得包括其它的有关分发的信(如在叫签名的
JARs遭逢,清单可以概括数字签名)。如果您包装一个利用(与此相反是包装一个库房)到
JAR 中,清单文件应该指出运用的主类(也就算是 main
函数所在类),在这种情形下,应用通过命令java -jar app.jar起步,我们遂此
JAR 文件为可尽的 JAR 。

Java 库被由包改成 JAR 文件,然后部署到 Maven 仓库中(这个库房能为有着的
JVM 构建工具使用,不仅仅是 Maven )。 Maven
仓库管理这些库二进制文件之本子及因(当您犯一个告想从Maven仓库中加载一个储藏室,此外你请了该库所有的依)。开源
Java
库经常托管在这中央仓库受,或者其它类似之明白仓库着。并且组织机关通过
Artifactory
或者
Nexus
等工具,管理他们私有 Maven 仓库。你还会以 GitHub 上树友好的 Maven
仓库。但是
Maven 仓库以构建过程中该能够正常下,并且 Maven 仓库通常托管库形式 JAR
而不是可尽的 JAR 。

Java 网站应用传统上理应于应用服务器(或者 servlet
容器)中实行。这些器皿能运作多个网站以,能按照需要加载或卸载应用。 Java
网站应用为 WAR 的样式安排于 servlet 容器中。WAR 也是 JAR
文件,它的始末以某种标准形式免好,并且包括额外的布置信息。但是,正如我们用于第三片段来看同一,就现代
Java 开发而言,Java
应用服务器已很。

Java 桌面应用时为由包改成跟平台相关的二进制文件,还连一个阳台相关的
JVM。 JDK
工具确保着出一个装进工具来举行这个工作(这里凡说话的凡怎么样以
NetBeans 中利用其)。第三方工具
Packer
也提供了近似的效益。对于游戏与桌面应用来说,这种打包机非常好。但是于服务器软件来说,这种打包机制就非是自己怀念使的。此外,因为一旦包一个
JVM 的正片,这种机制不可知因为补丁形式安全和平滑地升级使用。

对劳务器端代码,我们纪念要之凡一样种植简单、轻量、能活动的卷入和布局之家伙。这个家伙最好能用而实行
JAR 的简练与平台无关性。但是只是实施 JAR
有几乎独不足之地方。每一个库房通常由包到个别的 JAR
文件被,然后同拥有的倚重一由自包改成单个 JAR
文件,这等同历程也许造成冲突,特别是早就打包的资源库(没有 class
文件的堆栈)一自自包时。还有,一个原本生库在卷入时不可知一直坐 JAR
中。打包中可能无限重点的凡, JVM 配置信息(如 heap
的高低)对用户来说是脱之,这个工作要以命令行下才能够开。像 Maven’s
Shade
plugin
和 Gradle’s Shadow
plugin
等工具,解决了资源撞的问题,而
One-Jar
支持原生的库,但是这些工具还或针对用来震慑,而且也未曾缓解 JVM
参数配置的题材。 Gradle 能把下由包改成一个 ZIP
文件,并且有一个同系统相关的启脚本去安排 JVM
,但是这种措施要求安装使用。我们得开的于这样还轻量级。同样,我们发无往不胜的、普遍存在的资源像
Maven 仓库任我们应用,如果不充分利用它们是件让人可耻的行。

立马同密密麻麻博客打算讲出口用现代 Java
工作是多简单和幽默(不欲牺牲其他性质),但是当我失去寻找相同种有趣、简单和轻量级的法门去包、分发及部署服务器端的
Java 应用时,我点儿手空空。所以
Capsule
诞生了(如果您掌握发生外更好的精选,请报告我)。

Capsule 以平台独立的可是尽 JAR
包,但是并未因,并且(可选的)能组成强大和便当的 Maven 仓库。一个
capsule 是一个 JAR 文件,它概括整个要么有的 Capsule 项目
class,和一个囊括布置安排的清单文件。当启动时(java -jar app.jar),
capsule 会依次执行以下的动作:解压缩 JAR
文件及一个缓存目录中,下载依赖,寻找一个当的 JVM
进行设置,然后配置以及运作应用在一个初的JVM进程遭到。

当今受咱把 Capsule
拿出去溜一溜。我们将第一部的
JModern 项目举行吗发端之花色。这是咱的 build.gradle 文件:

apply plugin: 'java'
apply plugin: 'application'

sourceCompatibility = '1.8'

mainClassName = 'jmodern.Main'

repositories {
    mavenCentral()
}

configurations {
    quasar
}

dependencies {
    compile "co.paralleluniverse:quasar-core:0.5.0:jdk8"
    compile "co.paralleluniverse:quasar-actors:0.5.0"
    quasar "co.paralleluniverse:quasar-core:0.5.0:jdk8"

    testCompile 'junit:junit:4.11'
}

run {
    jvmArgs "-javaagent:${configurations.quasar.iterator().next()}"
}

此地是咱们的 jmodern.Main 类:

package jmodern;

import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.strands.Strand;
import co.paralleluniverse.strands.channels.Channel;
import co.paralleluniverse.strands.channels.Channels;

public class Main {
    public static void main(String[] args) throws Exception {
        final Channel<Integer> ch = Channels.newChannel(0);

        new Fiber<Void>(() -> {
            for (int i = 0; i < 10; i++) {
                Strand.sleep(100);
                ch.send(i);
            }
            ch.close();
        }).start();

        new Fiber<Void>(() -> {
            Integer x;
            while((x = ch.receive()) != null)
                System.out.println("--> " + x);
        }).start().join(); // join waits for this fiber to finish
    }
}

以测试一下咱的顺序工作是正常的,我们运行一下gradle run

今日,我们来将这动用由包改成一个 capsule 。在构建文件被,我们将追加
capsule 配置。然后,我们多依赖包:

capsule "co.paralleluniverse:capsule:0.3.1"

即 Capsule 有个别栽方法来创造 capsule
(虽然您啊得勾兑使用)。第一种艺术是开创以时拿具有的赖还参加到
capsule 中;第二种植办法是率先坏启动 capsule
时给其失去下载依赖。我来试试看一下先是种植—— “full”
模式。我们抬高下面的天职及构建文件中:

task capsule(type: Jar, dependsOn: jar) {
    archiveName = "jmodern-capsule.jar"

    from jar // embed our application jar
    from { configurations.runtime } // embed dependencies

    from(configurations.capsule.collect { zipTree(it) }) { include 'Capsule.class' } // we just need the single Capsule class

    manifest {
        attributes(
            'Main-Class'  : 'Capsule',
            'Application-Class' : mainClassName,
            'Min-Java-Version' : '1.8.0',
            'JVM-Args' : run.jvmArgs.join(' '), // copy JVM args from the run task
            'System-Properties' : run.systemProperties.collect { k,v -> "$k=$v" }.join(' '), // copy system properties
            'Java-Agents' : configurations.quasar.iterator().next().getName()
        )
    }
}

好了,现在我们输入gradle capsule构建 capsule ,然后运行:

java -jar build/libs/jmodern-capsule.jar

倘若你想准确之知 Capsule
现在于召开什么,可以管-jar换成-Dcapsule.log=verbose,但是盖它们是一个包括因的
capsule ,第一不善运行时, Capsule 会解压 JAR 文件及一个缓存目录下
(这个目录是以当下用户的根文件夹着产.capsule/apps/jmodern.Main),然后启动一个初通过
capsule 清单文件配置好的 JVM 。如果你早已装好了 Java7 ,你可以下
Java7 启动 capsule (通过安装 JAVA_HOME 环境变量)。虽然 capsule 能于
java7 下启动,但是盖 capsule 指定了无与伦比小之 Java 版本是 Java8 (或者是
1.8,同样的意思), capsule 会寻找 Java8 并且为此她来跑我们的下。

现今开腔说第二艺术。我们拿创设一个有表面依赖之 capsule
。为了使创办工作简单点,我们先在构建文件中追加一个函数(你无欲懂得他;做成
Gradle 的插件会又好,欢迎贡献。但是今我们手动创建是 capsule ):

// converts Gradle dependencies to Capsule dependencies
def getDependencies(config) {
    return config.getAllDependencies().collect {
        def res = it.group + ':' + it.name + ':' + it.version +
            (!it.artifacts.isEmpty() ? ':' + it.artifacts.iterator().next().classifier : '')
        if(!it.excludeRules.isEmpty()) {
            res += "(" + it.excludeRules.collect { it.group + ':' + it.module }.join(',') + ")"
        }
        return res
    }
}

然后,我们反构建文件被capsule职责,让它们亦可念:

task capsule(type: Jar, dependsOn: classes) {
    archiveName = "jmodern-capsule.jar"
    from sourceSets.main.output // this way we don't need to extract
    from { configurations.capsule.collect { zipTree(it) } }

    manifest {
        attributes(
            'Main-Class'  :   'Capsule',
            'Application-Class'   : mainClassName,
            'Extract-Capsule' : 'false', // no need to extract the capsule
            'Min-Java-Version' : '1.8.0',
            'JVM-Args' : run.jvmArgs.join(' '),
            'System-Properties' : run.systemProperties.collect { k,v -> "$k=$v" }.join(' '),
            'Java-Agents' : getDependencies(configurations.quasar).iterator().next(),
            'Dependencies': getDependencies(configurations.runtime).join(' ')
        )
    }
}

运行gradle capsule,再次运行:

java -jar build/libs/jmodern-capsule.jar

首赖运行, capsule 将会下蛋充斥我们种之拥有乘到一个缓存目录下。其他的
capsule 共享斯目录。 相反你莫欲拿依列在 JAR
清单文件中,取而代之,你可以拿项目依赖列在 pom 文件被(如果你使用
Maven 做呢构建工具,这将专门实用),然后在 capsule
的根目录。详细信息可以查看 Capsule
文档。

说到底,因为及时首稿子的始末对其余 JVM
语言都是卓有成效的,所以这里出一个略例子用来表示把一个
Node.js 的采用由包改成一个 capsule 。这个微应用使用了
Avatar
,该型会以 JVM 上运行 javascript 应用
,就如 Nodejs 一样。代码如下:

var http = require('http');

var server = http.createServer(function (request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end("Hello World\n");
});
server.listen(8000);
console.log("Server running at http://127.0.0.1:8000/");

用还有个别单 Gradle
构建文件。一个所以来创造full模式的
capsule
,另外一个因而来创造external模式之
capsule 。这个例子示范了包原生库依赖。创建该 capsule ,运行:

gradle -b build1.gradle capsule

不畏获得一个席卷所有因的 capsule 。或者运行下面的一声令下:

gradle -b build2.gradle capsule

就算获取一个不包借助之 capsule (里面连 Gradle
wrapper,所以你无待设置
Gradle ,简单的输入./gradlew不怕能够构建利用)。

运转它们,输入下面的吩咐:

java -jar build/libs/hello-nodejs.jar

Jigsaw,原计划在包括在
Java9 中。该项目之意向是釜底抽薪 Java
部署及有别的题材,例如:一个给抽的JVM发行版,减少启动日(这里发生一个幽默演讲关于
Jigsaw )。同时,对于当代 Java 开发打包和布署,Capsule
是一个异常恰当的家伙。Capsule 是凭状态和未用装的。

日志

每当我们进 Java 先进的督察特性之前,让咱们管日志搞定。据我所知,Java
有恢宏的日志库,它们都是成立于 JDK
标准库之上。如果你得日志,用不着想最多,直接运用
slf4j
做也日志 API 。它成为了实在日志 API
的规范,而且已绑定几乎拥有的日志引擎。一不过若用
SLF4J,你可以缓选择日志引擎时机(你居然能在配备之时节决定利用谁日志引擎)。
SLF4J
在运转时精选日志引擎,这个日志引擎可以是其他一个如果做吧依赖添加的库。大部分仓库现在都动SLF4J,如果出被产生一个仓房没有下SLF4J,它会吃你把这个库房的日志导回SLF4J,然后您便可还摘你的日志引擎。谈谈选择日志引擎事,如果您想选一个简便的,那就是
JDK
的java.util.logging。如果你想选一个重型的、高性能的日记引擎,就挑
Log4j2
(除了您发的确发生必要尝试一下旁的日志引擎)。

现行我们来补加日志到我们的采用中。在因部分,我们加:

compile "org.slf4j:slf4j-api:1.7.7"    // the SLF4J API
runtime "org.slf4j:slf4j-jdk14:1.7.7"  // SLF4J binding for java.util.logging

如果运行gradle dependencies一声令下,我们可以看出眼前底运来怎么样依赖。就目前来说,我们负了
Log4j
,这不是咱们怀念如果的。因此好得在build.gradle的布部分增加一行代码:

all*.exclude group: "org.apache.logging.log4j", module: "*"

好了,我们来吃我们的使用添加一些日记:

package jmodern;

import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.strands.Strand;
import co.paralleluniverse.strands.channels.Channel;
import co.paralleluniverse.strands.channels.Channels;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    static final Logger log = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) throws Exception {
        final Channel<Integer> ch = Channels.newChannel(0);

        new Fiber<Void>(() -> {
            for (int i = 0; i < 100000; i++) {
                Strand.sleep(100);
                log.info("Sending {}", i); // log something
                ch.send(i);
                if (i % 10 == 0)
                    log.warn("Sent {} messages", i + 1); // log something
            }
            ch.close();
        }).start();

        new Fiber<Void>(() -> {
            Integer x;
            while ((x = ch.receive()) != null)
                System.out.println("--> " + x);
        }).start().join(); // join waits for this fiber to finish
    }
}

接下来运行应用(gradle run),你见面映入眼帘日志打印至正规输出(这个默认设置;我们无打算深入安排日志引擎,你想做的话,可以参见想关文档)。infowarn级的日记都默认输出。日志的输出等可以当配备文件被安(现在咱们无打算改变了),或者一会可以见到,我们于运行时开展改动设置,

为此jcmd和jstat进行监察以及治本

JDK
中都席卷了几乎单用于监控与管理之工具,而这里我们唯有会略介绍中的一律针对工具:jcmd

jstat

以演示其,我们设而我们的应用程序别那么尽快之住。所以我们拿for循环次数从10改成1000000,然后于终点下运作应用gradle run。在另外一个终端中,我们运行jcmd。如果您的JDK安装是而jcmd于公的目中,你见面相下的音信:

22177 jmodern.Main
21029 org.gradle.launcher.daemon.bootstrap.GradleDaemon 1.11 /Users/pron/.gradle/daemon 10800000 86d63e7b-9a18-43e8-840c-649e25c329fc -XX:MaxPermSize=256m -XX:+HeapDumpOnOutOfMemoryError -Xmx1024m -Dfile.encoding=UTF-8
22182 sun.tools.jcmd.JCmd

面信息列有了富有方JVM上运行的先后。再远行下面的吩咐:

jcmd jmodern.Main help

公会看打印出了特定 JVM 程序的 jcmd 支持之命令列表。我们来试一下:

jcmd jmodern.Main Thread.print

打印出了 JVM 中负有线程的脚下堆栈信息。试一下是:

jcmd jmodern.Main PerfCounter.print

立将打印出同丰富串各种 JVM
性能计数器(你问问问谷歌这些参数的意思)。你可以试行一下其它的指令(如GC.class_histogram)。

jstat对此 JVM 来说就像 Linux 中之 top ,只有她能查关于 GC 和 JIT
的倒消息。假设我们用之 pid 是95098(可以用 jcmdjps
找到这价值)。现在我们运行:

jstat -gc 95098 1000

她将会见各 1000 毫秒打印 GC 的音信。看起像这么:

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
80384.0 10752.0  0.0   10494.9 139776.0 16974.0   148480.0   125105.4    ?      ?        65    1.227   8      3.238    4.465
80384.0 10752.0  0.0   10494.9 139776.0 16985.1   148480.0   125105.4    ?      ?        65    1.227   8      3.238    4.465
80384.0 10752.0  0.0   10494.9 139776.0 16985.1   148480.0   125105.4    ?      ?        65    1.227   8      3.238    4.465
80384.0 10752.0  0.0   10494.9 139776.0 16985.1   148480.0   125105.4    ?      ?        65    1.227   8      3.238    4.465

这些数字代表各种 GC 区域时的容量。想了解各个一个之意思,查看 jsata
文档。

采用JMX进行监控和保管

JVM
最要命之一个独到之处就是是其会当运转时监控以及治本时,暴露每一个操作的详细信息。JMX(Java
Management Extensions),是 JVM 运行时管理与监理之正规。 JMX 详细说明了
MBeans ,该目标用来暴露有关 JVM 、 JDK 库和 JVM
应用的监督和保管操作方法。 JMX 还定义了连续 JVM
实例的正统方法,包括本地连接和长距离连接的艺术。还有定义了安和 MBeans
交互。实际上, jcmd 就是以 JMX
获得有关的音之。在本文后面,我们呢刻画一个温馨之 MBeans
,但是还是率先来探视内置的 MBeans 如何采取。

当我们的下运行在一个终端,运行 jvisualvm 命令(该工具是 JDK
的相同有些)在其它一个极端。这会启动
VisualVM
。在咱们开始采取前,还得伪装一些插件。打开 Tools->Plugins
菜单,选择可以可以动用的插件。当前之演示,我们只有待VisualVM-MBeans,但是若也许除了
VisualVM-Glassfish 和 BTrace Workbench
,其他的插件都装上。现在以左侧面板选择 jmodern.Main
,然后选择监控页。你晤面看到如下信:

图片 1

figure

该监督页把 JMX-MBeans 暴露的采用信息之所以图片的型式表达出来。我们也可以通过
Mbeans 选项卡选择有 MBeans
(有些要设置到位插件后才能够应用),我们能够查和互相已注册的 MBeans
。例如有只常因此底堆图,就以 java.lang/Memory 中(双击属性值展开它):

图片 2

figure2

当今咱们选择 java.util.logging/Logging MBean 。在右手面板中,属性
LoggerNames 会列出所有已注册的 logger ,包括我们抬高到 jmodern.Main
(双击属性值展开它):

图片 3

figure3

MBeans
使我们不光会探测到监测值,还好转这些价值,然后调用各种管理操作。选择
Operations
选项卡(在右侧面板中,位于属性选项卡的右手)。我们本于运行时经过
JMX-MBean 改变日志等级。在 setLoggerLevel 属性中,第一个地方填上
jmodern.Main ,第二单地方填上 WARNING ,载图如下:

图片 4

figure4

现在,点击 setLoggerLevel 按钮, info
级的日志信息不再会打印出来。如果调整成 SEVERE ,就从未音讯打印。
VisualVM 对 MBean 都见面转简单的 GUI,不用费力的夺形容界面。

我们呢得以于远距离应用 VisualVM
访问我们的用,只所以长部分网的设置。在构建文件被的run有的受益如下代码:

systemProperty "com.sun.management.jmxremote", ""
systemProperty "com.sun.management.jmxremote.port", "9999"
systemProperty "com.sun.management.jmxremote.authenticate", "false"
systemProperty "com.sun.management.jmxremote.ssl", "false"

(在生育环境遭受,你应当打开安全选项)

正要使我辈所盼的,除了 MBean 探测, VisualVM 也得以应用 JMX
提供的多少创建于定义监控视图:监控线程状态和目前拥有线程的堆栈情况,查看
GC
和通用内存以状况,执行堆转储和核心转储操作,分析转储堆和中坚堆,还有更多之别力量。因此,在现世
Java 开发被, VisualVM 是最要之家伙之一。这是 VisualVM
跟踪插件提供的监控信息截图:

图片 5

figure5

现代 Java 开发人员有时可能会见欣赏一个 CLI 而休是尽善尽美的 GUI 。
jmxterm
提供了一个 CLI 形式之 JMX-MBeans 。不幸之凡,它还免支持 Java7 和 Java8
,但开发人员表示用快速到(如果无,我们以宣布一个补丁,我们早就来一个支行在开就片办事了)。

但是,有平等起事是自然之。现代 Java 开发人员喜欢 REST-API
(如果没外的原故,因为它无处不在,并且颇易构建 web-GUI )。虽然 JMX
标准支持部分不比的本地与长途连接器,但是正式中尚无包括 HTTP
连接器(应该会在 Java9 中)。现在,有一个死好的类
Jolokia,填补这个空白。它能够于我们使用
RESTful 的不二法门访 MBeans
。让咱来试试一试。将以下代码合并及build.gradle文件中:

configurations {
    jolokia
}

dependencies {
    runtime "org.jolokia:jolokia-core:1.2.1"
    jolokia "org.jolokia:jolokia-jvm:1.2.1:agent"
}

run {
    jvmArgs "-javaagent:${configurations.jolokia.iterator().next()}=port=7777,host=localhost"
}

(我发现 Gradle 总是要求于各级一个依还安 Java
agent,这个题材直接困扰自己。)

更改构建文件 capsule 任务的 Java-Agents特性,可以给 Jolokia 在
capsule 中可用。代码如下:

'Java-Agents' : getDependencies(configurations.quasar).iterator().next() +
               + " ${getDependencies(configurations.jolokia).iterator().next()}=port=7777,host=localhost",

通过 gradle run 或者
gradle capsule; java -jar build/libs/jmodern-capsule.jar
运行应用,然后打开浏览器输入 http://localhost:7777/jolokia/version
。如果 Jolokia
正常工作,会返回一个JSON。现在咱们若查看转动的积用状态,可以这样做:

curl http://localhost:7777/jolokia/read/java.lang:type\=Memory/HeapMemoryUsage

安装日志等级,你得这么做:

curl http://localhost:7777/jolokia/exec/java.util.logging:type\=Logging/setLoggerLevel\(java.lang.String,java.lang.String\)/jmodern.Main/WARNING

Jolokia 提供了 Http API ,这就是即以 GET 和 POST
方法进行操作。同时还提供安全访问的点子。需要再行多的信,请查看文档。

有矣 JolokiaHttpAPI
就能够通过Web进行保管。这里发生一个例子,它使用Cubism否
GUI 进行 JMX MBeans进行管制。还有如
hawtio , JBoss
创建的型,它以 JolokiaHttpAPI 构建了一个咸职能的网页版的保管采用。与
VisualVM 静态分析效益各异之是, hawatio
意图是也生育环境提供一个连监控与管理之工具。

写一个自定义的MBeans

形容一个 Mbeans 并登记很容易:

package jmodern;

import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.strands.Strand;
import co.paralleluniverse.strands.channels.*;
import java.lang.management.ManagementFactory;
import java.util.concurrent.atomic.AtomicInteger;
import javax.management.MXBean;
import javax.management.ObjectName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    static final Logger log = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) throws Exception {
        final AtomicInteger counter = new AtomicInteger();
        final Channel<Object> ch = Channels.newChannel(0);

        // create and register MBean
        ManagementFactory.getPlatformMBeanServer().registerMBean(new JModernInfo() {
            @Override
            public void send(String message) {
                try {
                    ch.send(message);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public int getNumMessagesReceived() {
                return counter.get();
            }
        }, new ObjectName("jmodern:type=Info"));

        new Fiber<Void>(() -> {
            for (int i = 0; i < 100000; i++) {
                Strand.sleep(100);
                log.info("Sending {}", i); // log something
                ch.send(i);
                if (i % 10 == 0)
                    log.warn("Sent {} messages", i + 1); // log something
            }
            ch.close();
        }).start();

        new Fiber<Void>(() -> {
            Object x;
            while ((x = ch.receive()) != null) {
                counter.incrementAndGet();
                System.out.println("--> " + x);
            }
        }).start().join(); // join waits for this fiber to finish

    }

    @MXBean
    public interface JModernInfo {
        void send(String message);
        int getNumMessagesReceived();
    }
}

咱俩上加了一个 JMX-MBean ,让咱们监视第二独 fiber
收到信之数量,也展露了一个殡葬操作,能用一如既往久信息进入 channel
。当我们运行应用程序时,我们可以在 VisualVM 中视监控的属性:

图片 6

figure6

双击,绘图:

图片 7

figure8

Operations 选项卡中,使用我们定义在MBean的操作,来发个信息:

图片 8

figure9

运用Metrics进行正常和特性监控

Metrics
一个简单之督查 JVM 应用性与例行的当代库,由 Coda Hale 在 Yammer
时开立的。 Metrics
库中蕴藏有通用的指标集和发布类,如直方图,计时器,统计议表盘等。现在咱们来探视哪些运用。

率先,我们无欲采用 Jolokia
,把其于构建文件中易除掉,然后上加下面的代码:

compile "com.codahale.metrics:metrics-core:3.0.2"

Metrics 通过 JMX-MBeans 发布指标,你可拿这些指标值写副 CSV
文件,或者做成 RESTful 接口,还足以颁发到 Graphite 和 Ganglia
备受。在这里只是简单发布暨 JMX (第三有中讨论到 Dropwizard 时,会使用
HTTP )。这是我们修改后的 Main.class

package jmodern;

import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.strands.Strand;
import co.paralleluniverse.strands.channels.*;
import com.codahale.metrics.*;
import static com.codahale.metrics.MetricRegistry.name;
import java.util.concurrent.ThreadLocalRandom;
import static java.util.concurrent.TimeUnit.*;

public class Main {
    public static void main(String[] args) throws Exception {
        final MetricRegistry metrics = new MetricRegistry();
        JmxReporter.forRegistry(metrics).build().start(); // starts reporting via JMX

        final Channel<Object> ch = Channels.newChannel(0);

        new Fiber<Void>(() -> {
            Meter meter = metrics.meter(name(Main.class, "messages" , "send", "rate"));
            for (int i = 0; i < 100000; i++) {
                Strand.sleep(ThreadLocalRandom.current().nextInt(50, 500)); // random sleep
                meter.mark(); // measures event rate

                ch.send(i);
            }
            ch.close();
        }).start();

        new Fiber<Void>(() -> {
            Counter counter = metrics.counter(name(Main.class, "messages", "received"));
            Timer timer = metrics.timer(name(Main.class, "messages", "duration"));

            Object x;
            long lastReceived = System.nanoTime();
            while ((x = ch.receive()) != null) {
                final long now = System.nanoTime();
                timer.update(now - lastReceived, NANOSECONDS); // creates duration histogram
                lastReceived = now;
                counter.inc(); // counts

                System.out.println("--> " + x);
            }
        }).start().join(); // join waits for this fiber to finish

    }
}

以例子中,使用了 Metrics 记数器。现在运行应用,启动 VisualVM :

图片 9

figure9

属性分析

特性分析是一个用到是否满足我们针对性能要求的主要措施。只有由此性能分析我们才能够明了呀一部分代码影响了整体实施进度,然后集中精力只改进就无异有的代码。一直以来,Java
都发出老好之性质分析工具,它们有些在 IDE 中,有的是一个单独的工具。而新近
Java 的特性分析工具变得又可靠和轻量级,这使得益于 HotSpot 把 JRcokit
JVM
中之代码合并自己的代码中。在即时有议论的家伙不是开源的,在此处讨论她是以这些家伙都席卷以规范的
OracleJDK
中,你可以在开条件受到擅自使用(但是当生养环境被而需要一个买卖特许)。

起来一个测试程序,修改后的代码:

package jmodern;

import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.strands.Strand;
import co.paralleluniverse.strands.channels.*;
import com.codahale.metrics.*;
import static com.codahale.metrics.MetricRegistry.name;
import java.util.concurrent.ThreadLocalRandom;
import static java.util.concurrent.TimeUnit.*;

public class Main {
    public static void main(String[] args) throws Exception {
        final MetricRegistry metrics = new MetricRegistry();
        JmxReporter.forRegistry(metrics).build().start(); // starts reporting via JMX

        final Channel<Object> ch = Channels.newChannel(0);

        new Fiber<Void>(() -> {
            Meter meter = metrics.meter(name(Main.class, "messages", "send", "rate"));
            for (int i = 0; i < 100000; i++) {
                Strand.sleep(ThreadLocalRandom.current().nextInt(50, 500)); // random sleep
                meter.mark();

                ch.send(i);
            }
            ch.close();
        }).start();

        new Fiber<Void>(() -> {
            Counter counter = metrics.counter(name(Main.class, "messages", "received"));
            Timer timer = metrics.timer(name(Main.class, "messages", "duration"));

            Object x;
            long lastReceived = System.nanoTime();
            while ((x = ch.receive()) != null) {
                final long now = System.nanoTime();
                timer.update(now - lastReceived, NANOSECONDS);
                lastReceived = now;
                counter.inc();

                double y = foo(x);
                System.out.println("--> " + x + " " + y);
            }
        }).start().join();
    }

    static double foo(Object x) { // do crazy work
        if (!(x instanceof Integer))
            return 0.0;

        double y = (Integer)x % 2723;
        for(int i=0; i<10000; i++) {
            String rstr = randomString('A', 'Z', 1000);
            y *= rstr.matches("ABA") ? 0.5 : 2.0;
            y = Math.sqrt(y);
        }
        return y;
    }

    public static String randomString(char from, char to, int length) {
        return ThreadLocalRandom.current().ints(from, to + 1).limit(length)
                .mapToObj(x -> Character.toString((char)x)).collect(Collectors.joining());
    }
}

foo
方法进行了有的无意思之测算,不用管她。当运行应用(gradle run)时,你见面注意到
Quasar 发出了警戒,警告说发一个 fiber 占用了过多的 CPU
时间。为了搞明白产生了哟,我们初步展开性能分析:

咱俩运用的分析器能够统计非常纯粹的音,同时所有特别低的付出。该工具包括个别个零部件:第一单凡是
Java Flight
Recorder
已经嵌入至 HotSpotVM 中。它会记录 JVM 中产生的轩然大波,可以跟 jcmd
配合动用,在这有些咱经过第二只器来控制其。第二独器是
JMC (Java Mission
Control),也于
JDK 中。它的意图一样于 VisualVM ,只是它们比较难用。在这边我们所以 JMC
来控制 Java Flight Recorder ,分析记录之信息(我盼望 Oracle
能管及时片效果转移到 VisualVM 中)。

Flight Recorder
在默认已经参加到应用中,只是不见面记录任何信息为无会见潜移默化性。先住以,然后将当时行代码加到
build.gradle 中的 run

jvmArgs "-XX:+UnlockCommercialFeatures", "-XX:+FlightRecorder"

UnlockCommercialFeatures 标志是必须的,因为 Flight Recorder
是商版的效用,不过可以在开发被随机使用。现在,我们再度开动以。

每当旁一个极端中,我们采取 jmc 打开 Mission Control
。在左的面板中,右击 jmodern.Main ,选择 Start Flight Recording…
。在带窗口被甄选 Event settings 下拉框,点击 Profiling - on server
,然后 Next > ,注意勿是 Finish

图片 10

figure12

接下来,选择 Heap StatisticsAllocation Profiling ,点击 Finish

图片 11

figure14

JMC 会等 Flight Recorder
记录了晚,打开记录文件进行剖析,在那时候您可以合你的以。

Code 部分的 Hot Methods 选项卡中,可以见到 randomString
是罪魁祸首,它占用了程序执行时间的 90%:

图片 12

figure15

Memory 部分的 Garbage Collection
选项卡中,展示了在记录中堆的使用状况:

图片 13

figure16

每当 GC 时间选项卡中,显示了GC的回收情况:

图片 14

figure17

啊得翻内存分配的状:

图片 15

figure18

应用堆的情节:

图片 16

figure19

Java Flight Recorder
还有一个非叫支持的API,能记录下事件。

高等话题:使用Byteman进行性能分析与调剂

例如第一部分雷同,我们就此高档话题来终止本期话题。首先讨论的凡为此 Byteman
进行性分析以及调剂。我在率先局部涉及, JVM
最有力的特色有即是当运转时动态加载代码(这个特性远超本地原生应用加载动态链接库)。不只这,JVM
还被了我们来回变换运行时代码的能力。

JBoss 开发的
Byteman
工具能充分利用 JVM 的此特点。 Byteman
能于咱们当运转应用时注入跟踪、调试和性测试相关代码。这个话题之所以是一个高级话题,是坐脚下
Byteman 只支持 Java7 ,对 Java8
的支撑还不保险,需要打补丁才能够工作。这个类型时出活跃,但是在退化。因此于此处用一些
Byteman 非常基础之代码。

马上是主类:

package jmodern;

import java.util.concurrent.ThreadLocalRandom;

public class Main {
    public static void main(String[] args) throws Exception {
        for (int i = 0;; i++) {
            System.out.println("Calling foo");
            foo(i);
        }
    }

    private static String foo(int x) throws InterruptedException {
        long pause = ThreadLocalRandom.current().nextInt(50, 500);
        Thread.sleep(pause);
        return "aaa" + pause;
    }
}

foo 模拟调用服务器操作,这些操作而费自然时间展开。

对接下,把下的代码合并到构建文件中:

configurations {
    byteman
}

dependencies {
  byteman "org.jboss.byteman:byteman:2.1.4.1"
}

run {
    jvmArgs "-javaagent:${configurations.byteman.iterator().next()}=listener:true,port:9977"
    // remove the quasar agent
}

怀念在 capsule 中试一试跳 Byteman 使用,在构建文件中改一下 Java-Agents
属性:

'Java-Agents' : "${getDependencies(configurations.byteman).iterator().next()}=listener:true,port:9977",

现在,从这里下载
Byteman ,因为待利用 Byteman 中之命令行工具,解压文件,设置环境变量
BYTEMAN_HOME 指向 Byteman 的目录。

启航以gradle run。打印结果如下:

Calling foo
Calling foo
Calling foo
Calling foo
Calling foo

咱怀念知道每次调用 foo
需要多添加出日,但是我们没测量并记下者信息。现在采取 Byteman
在运作时插入相关日志记录信息。

开辟编辑器,在项目目录中创造文件 jmodern.btm

RULE trace foo entry
CLASS jmodern.Main
METHOD foo
AT ENTRY
IF true
DO createTimer("timer")
ENDRULE

RULE trace foo exit
CLASS jmodern.Main
METHOD foo
AT EXIT
IF true
DO traceln("::::::: foo(" + $1 + ") -> " + $! + " : " + resetTimer("timer") + "ms")
ENDRULE

点列的凡 Byteman rules ,就是时我们怀念行使在次及之
rules。我们在其他一个极中运作命令:

$BYTEMAN_HOME/bin/bmsubmit.sh -p 9977 jmodern.btm

后来,运行面临的行使打印信息:

Calling foo
::::::: foo(152) -> aaa217 : 217ms
Calling foo
::::::: foo(153) -> aaa281 : 281ms
Calling foo
::::::: foo(154) -> aaa282 : 283ms
Calling foo
::::::: foo(155) -> aaa166 : 166ms
Calling foo
::::::: foo(156) -> aaa160 : 161ms

查哪个 rules 正在使用:

$BYTEMAN_HOME/bin/bmsubmit.sh -p 9977

卸载 Byteman 脚本:

$BYTEMAN_HOME/bin/bmsubmit.sh -p 9977 -u

运行该令下,注入的日记代码就给更换有。

Byteman 是于 JVM
灵活代码变换的底蕴及创造的一个相当强劲的工具。你可采取是家伙来检查变量和日志事件,插入延迟代码等操作,甚至还足以轻松设置有些由定义的
Byteman 行为。更多之信息,参考Byteman
documentation。

高等话题:使用JMH进行标准化测试

现代硬件构架和编译技术之迈入而考察代码性能的唯一方式就是是标准化测试。一方面,由于现代
CPU
和编译器非常聪明(可以看这里),它能为代码(可以是
c,甚至是汇编)自动地创建一个答辩及颇快速的运转环境,就比如 90
年代末一些游乐程序员做的那些老不可思议的转业一样。另一方面,正是以聪明的
CPU
和编译器,让微基准测试大不方便,因为这样的话,代码的实践进度非常靠具体的履环境(如:代码速度被
CPU 缓存状态的熏陶,而 CPU 缓存状态又吃外线程操作的影响)。而针对一个
Java 进行微基准测试又会更的孤苦,因为 JVM 有 JIT ,而 JIT
是一个因为性优化为导向的编绎器,它会当运行时影响代码执行的上下文环境。因此当
JVM
中,同一段代码在微基准测试与实际程序中施行时或未一样,有时可能尽快,有时也说不定慢。

JMH
是出于 Oracle 创建的 Java 基准测试工具。你可以信任由 JMH
测试出的数据(可以看看这由于 JMH 主要作者Aleksey
Shipilev的演讲,幻灯片)。
Google 也召开了一个尺度测试的工具叫
Caliper,但是是家伙十分不成熟,有时还会见发出错的结果。不要以其。

咱俩当下来运一下 JMH
,但是以当下之前率先来一个忠告:过早优化是万恶之源。在基测试中,两种算法或者数据结构中,一栽于任何一样栽快
100 倍,而这个算法就占而利用运行时之 1%
,这样测试是未曾意义的。因为尽管你拿这个算法改进的充分快行但为只能加速而的动
2%
时间。基准测试只能是都针对性用进行了性测试后,用来发现哪一个稍片段转会取得最好充分之加快成果。

充实依赖:

testCompile 'org.openjdk.jmh:jmh-core:0.8'
testCompile 'org.openjdk.jmh:jmh-generator-annprocess:0.8'

接下来搭bench任务:

task bench(type: JavaExec, dependsOn: [classes, testClasses]) {
    classpath = sourceSets.test.runtimeClasspath // we'll put jmodern.Benchamrk in the test directory
    main = "jmodern.Benchmark";
}

最后,把测试代码放到 src/test/java/jmodern/Benchmark.java
文件中。我前面涉嫌过 90
年代的游乐程序员,是为印证古的技能现在照旧发生因此,这里我们测试一个上马平方根的盘算,使用fast
inverse square root
algorithm(平方根倒数速算法,这是
90 年代的先后):

package jmodern;

import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.profile.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.parameters.TimeValue;

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class Benchmark {
    public static void main(String[] args) throws Exception {
        new Runner(new OptionsBuilder()
                .include(Benchmark.class.getName() + ".*")
                .forks(1)
                .warmupTime(TimeValue.seconds(5))
                .warmupIterations(3)
                .measurementTime(TimeValue.seconds(5))
                .measurementIterations(5)
                .build()).run();
    }

    private double x = 2.0; // prevent constant folding

    @GenerateMicroBenchmark
    public double standardInvSqrt() {
        return 1.0/Math.sqrt(x);
    }

    @GenerateMicroBenchmark
    public double fastInvSqrt() {
        return invSqrt(x);
    }

    static double invSqrt(double x) {
        double xhalf = 0.5d * x;
        long i = Double.doubleToLongBits(x);
        i = 0x5fe6ec85e7de30daL - (i >> 1);
        x = Double.longBitsToDouble(i);
        x = x * (1.5d - xhalf * x * x);
        return x;
    }
}

任说一下,像第一组成部分中讨论的 Checker 一样, JMH
使用使用注解处理器。但是差 Checker , JMH 做的正确性,你会当具备的 IDE
中动用它们。在下面的图中,我们好看看, NetBeans 中,一但忘记加 @State
注解, IDE 就见面报错:

图片 17

feature17

描绘副命令 gradle bench ,运行规范测试。会获以下结果:

Benchmark                       Mode   Samples         Mean   Mean error    Units
j.Benchmark.fastInvSqrt         avgt        10        2.708        0.019    ns/op
j.Benchmark.standardInvSqrt     avgt        10       12.824        0.065    ns/op

十分完美吧,但是若得知道 fast-inv-sqrt 结果是一个简单易行近似值,
只在用大量开端平方的地方适用(如图计算中)。

每当下面的例子中, JMH 用来报到 GC 使用的日与道栈的调用时间:

package jmodern;

import java.util.*;
import java.util.concurrent.*;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.profile.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.parameters.TimeValue;

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class Benchmark {
    public static void main(String[] args) throws Exception {
        new Runner(new OptionsBuilder()
                .include(Benchmark.class.getName() + ".*")
                .forks(2)
                .warmupTime(TimeValue.seconds(5))
                .warmupIterations(3)
                .measurementTime(TimeValue.seconds(5))
                .measurementIterations(5)
                .addProfiler(GCProfiler.class)    // report GC time
                .addProfiler(StackProfiler.class) // report method stack execution profile
                .build()).run();
    }

    @GenerateMicroBenchmark
    public Object arrayList() {
        return add(new ArrayList<>());
    }

    @GenerateMicroBenchmark
    public Object linkedList() {
        return add(new LinkedList<>());
    }

    static Object add(List<Integer> list) {
        for (int i = 0; i < 4000; i++)
            list.add(i);
        return list;
    }
}

随即是 JMH 的打印出来的音讯:

Iteration   3: 33783.296 ns/op
          GC | wall time = 5.000 secs,  GC time = 0.048 secs, GC% = 0.96%, GC count = +97
             |
       Stack |  96.9%   RUNNABLE jmodern.generated.Benchmark_arrayList.arrayList_AverageTime_measurementLoop
             |   1.8%   RUNNABLE java.lang.Integer.valueOf
             |   1.3%   RUNNABLE java.util.Arrays.copyOf
             |   0.0%            (other)
             |

JMH
是一个效能非常丰富的框架。不幸之凡,在文档方面略薄弱,不过出一个一定好代码示例教程,用来显示
Java
中微基测试的骗局。你吧足以读读这篇介绍
JMH 的入门文章。

目前为止我们既拟了什么?

于这首文章被,我们讨论了于 JVM 管理、监控与性测试点极好的几个器。
JVM
除了老大好的性质外,它还不行深思熟虑地提供了能深度洞察它运行状态的力,这就是自己未会见就此任何的艺来取代
JVM 做也重大之、长时运作的劳动器端应用平台的根本由。
除此以外,我们还见识到了当使用 Byteman 等工具修改运行时代码时, JVM
是何其强大。

俺们尚介绍了 Capsule
,一个轻量级的、单文件、无状态、不用装之配备工具。另外,通过一个当面或者组织之中的
Maven 仓库,它还支持一切Java应用自动升级,或者还是独升级一个依赖库。

在老三部分遭逢,我们用讨论如何利用
Dropwizard ,
Comsat
, Web
Actors
,和 DI 来形容一个轻量级、可扩大的http服务。

初稿地址:An Opinionated Guide to Modern Java, Part 2: Deployment,
Monitoring & Management, Profiling and
Benchmarking


水平有限,如果看不知底请直接扣英文版。

相关文章