[翻译]现代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();

本人完全同意。我在例子中简单示范了交集 马克down 和正式的 Javadoc
标签的接纳。那只是用来呈现什么使用,并不是企图把那种使用方法当成带领方针。

最后,关于 Android 我有一些话要说。 Android
系统经过一密密麻麻变换之后,可以实施用 java (还有可能是其他 JVM
语言)写的代码,可是 Android 不是 JVM,并且实际 Android
无论在正规场面和实际运用中也不完全是 Java
(造成那个难题的缘由是多少个民企,那里指谷歌(Google)和石籀文,没有就 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 开发而言,Oracle,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
,然后接纳监控页。你会晤到如下音讯:

Oracle 1

figure

该监督页把 JMX-MBeans 揭穿的行使音信用图形的型式表明出来。大家也足以因此Mbeans 选项卡拔取部分 MBeans
(有些须要安装落成插件后才能选用),大家能查看和互动已登记的 MBeans
。例如有个常用的堆图,就在 java.lang/Memory 中(双击属性值展开它):

Oracle 2

figure2

现在大家挑选 java.util.logging/Logging MBean 。在右边面板中,属性
LoggerNames 会列出所有已注册的 logger ,包涵我们抬高到 jmodern.Main
(双击属性值展开它):

Oracle 3

figure3

MBeans
使我们不光可以探测到监测值,仍是可以变更这几个值,然后调用各个管理操作。选用
Operations
选项卡(在右面板中,位于属性选项卡的左侧)。大家今日在运作时经过
JMX-MBean 改变日志等级。在 setLoggerLevel 属性中,第二个地点填上
jmodern.Main ,第一个地方填上 WARNING ,载图如下:

Oracle 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
跟踪插件提供的督察新闻截图:

Oracle 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 中见到监控的属性:

Oracle 6

figure6

双击,绘图:

Oracle 7

figure8

Operations 选项卡中,使用大家定义在MBean的操作,来发个音讯:

Oracle 8

figure9

动用Metrics举办常规和总体性监控

Metrics
一个简洁的监察 JVM 应用质量和常规的现世库,由 Coda 哈尔e 在 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 :

Oracle 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

Oracle 10

figure12

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

Oracle 11

figure14

JMC 会等 Flight Recorder
记录停止后,打开记录文件进行解析,在当年您可以关掉你的施用。

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

Oracle 12

figure15

Memory 部分的 Garbage Collection
选项卡中,突显了在笔录期间堆的采取景况:

Oracle 13

figure16

在 GC 时间选项卡中,突显了GC的回收处境:

Oracle 14

figure17

也得以查阅内存分配的景色:

Oracle 15

figure18

应用堆的内容:

Oracle 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的演讲幻灯片)。
谷歌 也做了一个规范测试的工具叫
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 就会报错:

Oracle 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


水平有限,如若看不懂请直接看英文版。

相关文章