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

形容一个自定义的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 中见到监控之习性:

葡京在线开户 1

figure6

双击,绘图:

葡京在线开户 2

figure8

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

葡京在线开户 3

figure9

**火上加油强调** 或者 __深化强调__ (示例:粗体)

日志

在咱们进入 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级的日志都默认输出。日志的出口等可以于布局文件被设置(现在咱们无打算改变了),或者一会好看看,我们在运行时展开改动设置,

第二种植艺术虽然是使用制表符或者至少4个空格进行缩进的履行

现代java开发指南 第二有些

![Alt text](/path/to/img.jpg)

高档话题:使用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。

下面是现身说法代码和职能

第二局部:部署、监控 & 管理,性能分析与条件测试

第一片段,仲组成部分

接来到现代 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 是无状态及无用装之。

(8)分割线

动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
,然后选取监控页。你会盼如下信:

葡京在线开户 4

figure

拖欠监控页把 JMX-MBeans 暴露的使用信息用图的型式表达出来。我们呢得以通过
Mbeans 选项卡选择一些 MBeans
(有些需要装完成插件后才会下),我们能查看和互已报之 MBeans
。例如有个常因此的堆图,就在 java.lang/Memory 中(双击属性值展开它):

葡京在线开户 5

figure2

而今我们选取 java.util.logging/Logging MBean 。在右侧面板中,属性
LoggerNames 会列出所有都报之 logger ,包括我们抬高到 jmodern.Main
(双击属性值展开它):

葡京在线开户 6

figure3

MBeans
使我们不但能够探测到监测值,还可以变动这些价值,然后调用各种管理操作。选择
Operations
选项卡(在右边面板中,位于属性选项卡的右侧)。我们现当运转时通过
JMX-MBean 改变日志等级。在 setLoggerLevel 属性中,第一只地方填上
jmodern.Main ,第二独地方填上 WARNING ,载图如下:

葡京在线开户 7

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
跟踪插件提供的监察信息截图:

葡京在线开户 8

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
意图是吧生产条件提供一个缕缕监控及管制的工具。

(9)列表标记

高等话题:使用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 就见面报错:

葡京在线开户 9

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 的入门文章。

继而一个屡见不鲜括号,里面放上图片的网址

性能分析

性能分析是一个采取是否满足我们本着性要求的主要办法。只有通过性能分析我们才会懂哪有代码影响了整实施进度,然后集中精力只改进就等同有代码。一直以来,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,能记录下事件。

因而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
文档。

假使我们以篇章中援了资料,那么我们得经一个右手尖括号”>”来代表即是同样段落引述内容。我们可以初步加一个,也得以各国一行的先头都加以一个。我们尚得于援里面嵌套其他的援

目前为止我们曾经拟了呀?

以就首文章被,我们讨论了于 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


水平有限,如果看无晓请直接看英文版。

下是现身说法代码和机能

应用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 :

葡京在线开户 17

figure9

首先种植办法是应用反引号(esc键下面的按钮)将代码包裹起来

俺们好采取下的道吃我们的文件添加强调的效应

假设我们文章中投入一个链接,那么我们通过下的不二法门丰富

(7)链接

*强调* 或者 _强调_  (示例:斜体)

继一个方括号,里面放上图片的代表文字

本身之自媒体博客blankj小站,欢迎来逛。

(6)引用

万一我们的情需开展标记,那么我们可以采取下的法门,注意中间产生一个空格,有了空格才会来效应。

(2)图片

而我们怀念以篇章中上加代码,我们发一定量种方法

吓了,这些既够了.

详尽描述如下:

[链接文字](链接地址)

下面是现身说法代码和效用

(5)换行

如果我们怀念用分割线对情节展开分,我们得以单独一行里输入3单或上述之短横线、星号或者下划线实现。短横线和星号之间可以输入任意空格。以下诸一行都生同样修水平分割线。

一个怪号 !

(4)代码

介绍一款Markdown在线编辑器http://mahua.jser.me/

脚是现身说法代码和成效

下是现身说法代码和效应

题目使用不同数量的”#”来标识是啊层级,可以对应为HTML里面的H1-H6

(1)标题

咱俩可使用下的语法,添加一个图形

(3)强调

要我们想拿一行文本进行换行,我们可在急需换行的地方输入至少少只空格,然后回车即可,注意,如果无回车,是尚未意义的,就如下这样

脚是现身说法代码和效果

下是现身说法代码和成效

***特别强调*** 或者 ___特别强调___ (示例:粗斜体)