<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; JVM</title>
	<atom:link href="https://blog.gmem.cc/tag/jvm/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Fri, 17 Apr 2026 09:20:32 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>使用Eclipse Memory Analyzer分析JVM堆Dump</title>
		<link>https://blog.gmem.cc/jvm-dump-analysis-with-mat</link>
		<comments>https://blog.gmem.cc/jvm-dump-analysis-with-mat#comments</comments>
		<pubDate>Mon, 05 Mar 2018 05:15:38 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[性能剖析]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=19351</guid>
		<description><![CDATA[<p>简介 Eclipse Memory Analyzer（MAT）是一个图形化的Java堆分析工具，速度快且特性丰富，可以用于取代JDK自带的堆Dump浏览器（jhat）。使用MAT，你可以快速分析包含上亿对象的生产环境Dump文件，快速计算某种对象导致的内存占用量，进而评估内存泄漏风险。 你可以将MAT安装为独立运行的RCP应用，也可以作为插件安装到现有的Eclipse中。 jhat jhat不是本文的主题，这里做一个简单的介绍。 简介 这是JVM自带的堆Dump分析工具，执行如下命令即可使用： [crayon-69e29aad36636421209913/] jhat默认在7000端口暴露HTTP服务，你可以通过浏览器进行以下查询： 查询 说明 All classes including platform 显示堆中所有类型（Java类）的列表，这些类被加载到JVM中 All Classes excluding platform 类似上面，但是不包括平台（即JDK内部）的类 <a class="read-more" href="https://blog.gmem.cc/jvm-dump-analysis-with-mat">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/jvm-dump-analysis-with-mat">使用Eclipse Memory Analyzer分析JVM堆Dump</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p><a href="https://www.eclipse.org/mat/">Eclipse Memory Analyzer</a>（MAT）是一个图形化的Java堆分析工具，速度快且特性丰富，可以用于取代JDK自带的堆Dump浏览器（jhat）。使用MAT，你可以快速分析包含上亿对象的生产环境Dump文件，快速计算某种对象导致的内存占用量，进而评估内存泄漏风险。</p>
<p>你可以将MAT安装为独立运行的RCP应用，也可以作为插件安装到现有的Eclipse中。</p>
<div class="blog_h1"><span class="graybg">jhat</span></div>
<p>jhat不是本文的主题，这里做一个简单的介绍。</p>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>这是JVM自带的堆Dump分析工具，执行如下命令即可使用：</p>
<pre class="crayon-plain-tag">jhat a.map  # a.map为Dump文件路径</pre>
<p>jhat默认在7000端口暴露HTTP服务，你可以通过浏览器进行以下查询：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">查询</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>All classes including platform</td>
<td>显示堆中所有类型（Java类）的列表，这些类被加载到JVM中</td>
</tr>
<tr>
<td>All Classes excluding platform</td>
<td>类似上面，但是不包括平台（即JDK内部）的类</td>
</tr>
<tr>
<td>Show all members of the rootset</td>
<td>
<p>显示所有GC Root的对外引用，按照以下几类GC Root，分段展示：</p>
<ol>
<li>Java类的静态成员</li>
<li>Java局部变量（显示持有这些本地变量的线程）</li>
<li>Native全局变量</li>
<li>Native局部变量</li>
</ol>
</td>
</tr>
<tr>
<td>Show instance counts for all classes (including platform)</td>
<td>显示所有类型的实例计数（包含平台类）</td>
</tr>
<tr>
<td>Show instance counts for all classes (excluding platform)</td>
<td>显示所有类型的实例计数（不包含平台类）</td>
</tr>
<tr>
<td>Show heap histogram</td>
<td>
<p>显示一个表格，统计各类型的实例数量、内存占用，按内存占用降序排列</p>
<p>内存占用显示为Shallow size，即对象自身的内存占用。除了数组、字符串等之外的对象Shallow size通常很小，通过引用（指针）关联的其它对象不计入Shallow size，仅仅计入固定尺寸的指针大小</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">示例应用</span></div>
<div class="blog_h3"><span class="graybg">代码</span></div>
<p>下面是一段急剧消耗内存的示例代码：</p>
<pre class="crayon-plain-tag">package com.dangdang.digital.test;

import org.apache.commons.lang.math.RandomUtils;

import java.util.concurrent.TimeUnit;

public class BusyApplication {

    public static void main( String[] args ) {
        new MemoryPredator( Integer.parseInt( args[0] ), Integer.parseInt( args[1] ) ).start();
    }

}

class MemoryPredator extends Thread {

    private final int consumeInterval;

    private int retainTime;

    public MemoryPredator( int consumeInterval, int retainTime ) {
        this.consumeInterval = consumeInterval;
        this.retainTime = retainTime;
    }

    @Override
    public void run() {
        for ( ; ; ) {
            consumeMemory( retainTime );
            try {
                TimeUnit.MILLISECONDS.sleep( consumeInterval );
            } catch ( InterruptedException e ) {
            }
        }
    }

    private void consumeMemory( final int retainTime ) {
        new MemoryPreyThread( retainTime ).start();
    }

}

class MemoryPreyThread extends Thread {

    private final int retainTime;

    public MemoryPreyThread( int retainTime ) {
        this.retainTime = retainTime;
    }

    @Override
    public void run() {
        int n = ( RandomUtils.nextInt( 100 ) + 1 );
        Object bigObj = createBigObject( n );
        System.out.println( String.format( "%d MB consumed", n ) );
        try {
            TimeUnit.MILLISECONDS.sleep( retainTime );
        } catch ( InterruptedException e ) {
        }
    }

    private Object createBigObject( int n ) {
        Object big = new byte[n * 1024 * 1024];
        return big;
    }
}</pre>
<p>让上述程序执行一段时间（应用程序参数使用500 7000），然后使用下面的命令获取堆Dump：</p>
<pre class="crayon-plain-tag"># 只Dump可达对象，使用二进制格式，输出为bzapp.hprof
jmap -dump:live,format=b,file=bzapp.hprof  `jps | grep BusyApplication | cut -d' ' -f1`</pre>
<div class="blog_h3"><span class="graybg">分析</span></div>
<p>通过jhat开启服务后，打开浏览器，首先看到的是堆中包含的<span style="background-color: #c0c0c0;">类的列表</span>（不包含平台类）：</p>
<pre class="crayon-plain-tag">All Classes (excluding platform)

Package com.dangdang.digital.test
class com.dangdang.digital.test.BusyApplication [0xc0005048]
class com.dangdang.digital.test.MemoryPredator [0xc0004ff0]
class com.dangdang.digital.test.MemoryPreyThread [0xc0004f98]

Package com.jarego.jayatana
class com.jarego.jayatana.Agent [0xc0005200]

Package org.apache.commons.lang.math
class org.apache.commons.lang.math.JVMRandom [0xc0004e60]
class org.apache.commons.lang.math.RandomUtils [0xc0004f00]</pre>
<p>点击界面下方的Show heap histogram连接，可以看到Shallow size最大的类型：</p>
<pre class="crayon-plain-tag">Class                                    Instance Count        Total Size

class [B                                 1063                  788662183
class [I                                 512                   6789172
class [C                                 2552                  349224
class java.lang.Class                    566                   54336
class [Ljava.lang.Object;                467                   48496
class [S                                 770                   44518
class java.lang.String                   2494                  39904
class java.net.URL                       346                   30448
class [Ljava.util.WeakHashMap$Entry;     156                   22464
class java.util.HashMap$Entry            771                   21588</pre>
<p>可以看到byte[]类型，即[B，占用绝大部分内存，接近800MB。</p>
<p>点击class [B上的链接，进入byte[]<span style="background-color: #c0c0c0;">类型的主页面</span>“Class 0xc009a858”，后面的是类对象的内存地址，和Dump分析关系不大。留意“References to this object”这一段内容：</p>
<pre class="crayon-plain-tag">[B@0xbb0b6e18 (240 bytes) : ??
[B@0xc00bad20 (30 bytes) : ??
[B@0xbae503c0 (29 bytes) : ??
[B@0xbb091708 (115 bytes) : ??
[B@0xbb057350 (29 bytes) : ??

... 

[B@0xc87c6368 (15728656 bytes) : ??       # 大对象</pre>
<p>任何类型的示例，都会持有该类型的Class对象的引用，byte[]也不例外。所有存活的byte[]对象因此列于“References to this object”中。我们需要关心的通常（取决于应用场景）是大对象，简单的滚动页面，留意那些更长的行，就可以定位到大的byte[]了。</p>
<p>可以看到对象[B@0xc87c6368占据了15M的内存空间，点击其上的链接，进入[B@0xc87c6368<span style="background-color: #c0c0c0;">对象的主页面</span>。在页面的下方，可以看到Other Queries - Reference Chains from Rootset - Exclude weak refs，点击后，可以查看从GC Root到此对象的<span style="background-color: #c0c0c0;">引用链页面</span>：</p>
<pre class="crayon-plain-tag">References to [B@0xc87c6368 (15728656 bytes)

Java Local References

Java Local Reference (from com.dangdang.digital.test.MemoryPreyThread@0xc4ec6208) : # 这是GCRoot

--&gt; [B@0xc87c6368 (15728656 bytes)</pre>
<p>至此，我们可以大概判定，引发内存问题的代码，位于类MemoryPreyThread中。</p>
<div class="blog_h1"><span class="graybg">MAT界面</span></div>
<div class="blog_h2"><span class="graybg">首页</span></div>
<p>点击菜单项 File ⇨  Open Heap Dump ...，看到如下界面：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/03/mat-ui.png"><img class="aligncenter  wp-image-19415" src="https://blog.gmem.cc/wp-content/uploads/2018/03/mat-ui.png" alt="mat-ui" width="917" height="649" /></a></p>
<div class="blog_h3"><span class="graybg">Inspector面板</span></div>
<p>上半部分显示当前正在分析的对象的基本信息，以上面的截图为例：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;"><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="font-weight: normal; line-height: 19px;">值 </span></span></td>
<td style="text-align: center;"><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="font-weight: normal; line-height: 19px;">意义 </span></span></td>
</tr>
</thead>
<tbody>
<tr>
<td><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="line-height: 19px;">0xcb4c69f8<br /></span></span></td>
<td><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="line-height: 19px;">对象的地址</span></span></td>
</tr>
<tr>
<td><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="line-height: 19px;">MemoryPreyThread</span></span></td>
<td><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="line-height: 19px;">对象的类型</span></span></td>
</tr>
<tr>
<td><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="line-height: 19px;">com.dangdang.digital.test</span></span></td>
<td><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="line-height: 19px;">对象的包名</span></span></td>
</tr>
<tr>
<td><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="line-height: 19px;">class com.dangdang.digital.test.MemoryPreyThread @ 0xc0004f98<br /></span></span></td>
<td><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="line-height: 19px;">对象的类型，以及类对象的地址</span></span></td>
</tr>
<tr>
<td><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="line-height: 19px;">java.lang.Thread</span></span></td>
<td><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="line-height: 19px;">对象的父类型</span></span></td>
</tr>
<tr>
<td>sun.misc.Launcher$AppClassLoader @ 0xc0000bd8</td>
<td>加载对象的类的ClassLoader</td>
</tr>
<tr>
<td>112(shadlow size)</td>
<td>此对象本身的大小</td>
</tr>
<tr>
<td>104,857,784(retained size)</td>
<td>此对象本身的大小 + 此对象引用（包括间接）对象的大小</td>
</tr>
<tr>
<td>GC root: Native Stack,Thread</td>
<td>提示此对象是否GC Root</td>
</tr>
</tbody>
</table>
<p>下半部分包括4个选项卡：</p>
<ol>
<li>Statics：此对象的类变量列表</li>
<li>Attributes：此对象的实例变量列表</li>
<li>Class Hierarchy：类层次图</li>
<li>Value：对象的值</li>
</ol>
<div class="blog_h3"><span class="graybg">Overview面板</span></div>
<p>此面板使用饼图显示Retained Size最大的对象，并列出其导致的内存驻留的尺寸。这些对象往往就是问题所在。</p>
<p>饼图上方的按钮区、和饼图下方的Actions/Reports/Step By Step，功能是对应的。</p>
<p>其中Actions包括：</p>
<ol>
<li>Histogram：类似jhat的Histogram类似，但是支持统计Retained Size，支持排序</li>
<li>Dominator Tree：显示那些导致内存驻留最严重的对象，按对象导致驻留的比例，降序排列。可以展开这些对象的引用链</li>
<li>Top Consumers：以饼图、列表方式显示导致内存驻留最严重的对象、类、类加载器、包</li>
<li>Duplicate Classes：显示不同类加载器重复加载的类</li>
</ol>
<p>其中Reports包括：</p>
<ol>
<li>Leak Suspects：可以的内存泄漏报告，更加细粒度的、辅助人工分析的报告</li>
</ol>
<div class="blog_h1"><span class="graybg">使用MAT分析示例应用</span></div>
<p>使用jhat，我们就可以获取堆Dump中的全部信息了。 但是其UI非常简陋，缺乏排序、统计类功能，这些功能正是MAT能提供的。下面我们使用MAT来分析上一章的示例应用。</p>
<p>打开Dump后，在Overview面板的饼图上，可以立即看到所有大内存驻留都是由com.dangdang.digital.test.MemoryPreyThread类型的对象所引发。</p>
<p>单击任何一个饼图切片，点击菜单List Objects ⇨ With Outgoing References，可以看到对象引用的哪些对象的Shallow Size较大。这些对象就是内存驻留的根源：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/03/mat-list-object.png"><img class="aligncenter size-full wp-image-19427" src="https://blog.gmem.cc/wp-content/uploads/2018/03/mat-list-object.png" alt="mat-list-object" width="857" height="364" /></a></p>
<p>很明显， MemoryPreyThread所引用的、byte[]类型的局部变量，其Shallow Size占据了全部内存份额。注意：如果<span style="background-color: #c0c0c0;">上级对象的Outgoing引用特别多，则问题所在的子级对象可能看不到，这种情况下可以点击Retained Heap列排序或者手工展开</span>。</p>
<p>对于像示例应用这样简单的代码，分析到这里就直接能定位到问题代码所在行号了。生产环境中的情况要复杂的多，单纯基于堆内存Dump进行静态分析不一定能奏效，需要结合线程栈Dump，甚至是剖析工具（JMC、JProfiler等）录制的内存分配历史。</p>
<div class="blog_h1"><span class="graybg">实例</span></div>
<div class="blog_h2"><span class="graybg">单个线程消耗6G内存</span></div>
<p>概要情况如下图：</p>
<p><img class="aligncenter size-full wp-image-20035" src="https://blog.gmem.cc/wp-content/uploads/2018/03/mat-5g-1.png" alt="mat-5g-1" width="849" height="354" /></p>
<p>某个线程引用了接近6G的数据。从线程名字上来看，它是一个普通的工作线程，持有的6G内存应该是某种临时性对象。</p>
<p>分析此线程的Outgoing引用，排序Retained Heap列，将大内存条目显示出来：</p>
<p>可以看到，内存由一个局部变量（&lt;Java Local&gt;）导致。需要注意，<span style="background-color: #c0c0c0;">局部变量的GC Root都是当前执行线程</span>。出现问题的变量引用一个ArrayList对象，进一步展开可以猜测到此对象是数据库查询的结果集：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/03/mat-5g-2.png"><img class="aligncenter size-full wp-image-20037" src="https://blog.gmem.cc/wp-content/uploads/2018/03/mat-5g-2.png" alt="mat-5g-2" width="855" height="789" /></a></p>
<p>可以看到，此ArrayList的元素多达1000万。很可能是错误的SQL语句把大量行甚至整个表都读取过来，一下子把JVM内存撑爆了。</p>
<p>展开一个ByteArrayRow，查看行中的元素，进一步定位到所操作的表。配合JMC查看Hot Methods，定位到了存在问题的方法。</p>
<div class="blog_h2"><span class="graybg">Dubbo消费者内存溢出</span></div>
<div class="blog_h3"><span class="graybg">概要情况</span></div>
<p>线上环境某服务，因为内存溢出、连续FGC被监控进程杀死，我们获得的堆Dump，内存占用总计1.7GB，Netty 3.x中的类NioClientSocketPipelineSink导致了绝大部分的内存驻留。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/03/nio-client-socket-pipeline-sink.png"><img class="aligncenter size-full wp-image-19697" src="https://blog.gmem.cc/wp-content/uploads/2018/03/nio-client-socket-pipeline-sink.png" alt="nio-client-socket-pipeline-sink" width="852" height="420" /></a></p>
<div class="blog_h3"><span class="graybg">IncomingRef分析</span></div>
<p>点击饼图中最大的切片NioClientSocketPipelineSink进行分析，打开菜单项List Objects ⇨ With Incoming References，展开：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/03/nio-client-socket-pipeline-sink-ir.png"><img class="aligncenter size-full wp-image-19703" src="https://blog.gmem.cc/wp-content/uploads/2018/03/nio-client-socket-pipeline-sink-ir.png" alt="nio-client-socket-pipeline-sink-ir" width="858" height="814" /></a></p>
<p>可以看到NioClientSocketPipelineSink是Netty处理管线（Pipeline）的一部分。在很多框架中，管线常常和SRC、SINK等术语一起出现，SRC常常表示管线的输入、SINK则表示输出。</p>
<p>一个全局性的ChannelFactory、<span style="background-color: #c0c0c0;">大量DefaultChannelPipeline</span>通过sink属性引用了NioClientSocketPipelineSink。这些DefaultChannelPipeline应该是某种一次性对象，不熟悉代码的话只能猜测到这个程度，可以考虑通过JMC来录制对象分配的调用栈，来证实这种猜测。</p>
<p>那么，谁创建了这些DefaultChannelPipeline呢？从上面的截图中可以看到Dubbo的NettyClient间接引用了NioClientSocketPipelineSink，再随机抽取几个DefaultChannelPipeline展开：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/03/nio-client-socket-pipeline-sink-ir2.png"><img class="aligncenter size-full wp-image-19707" src="https://blog.gmem.cc/wp-content/uploads/2018/03/nio-client-socket-pipeline-sink-ir2.png" alt="nio-client-socket-pipeline-sink-ir2" width="861" height="819" /></a></p>
<p>可以看到来自Dubbo的NettyClient通过channel ⇨ pipeline ⇨ sink 引用了DefaultChannelPipeline。com.alibaba.dubbo上面引用链上唯一的非Netty包，我们有理由猜测Dubbo和内存溢出有关。</p>
<p>我们可以看一下相关对象的计数。对于希望统计数量的对象，在调用链上点选它，左下面板切换到Class Hierarchy，在类名上右击，选择List Object  ⇨ With Incoming References：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/03/incoming-ref-of-class.png"><img class="aligncenter size-full wp-image-19721" src="https://blog.gmem.cc/wp-content/uploads/2018/03/incoming-ref-of-class.png" alt="incoming-ref-of-class" width="565" height="374" /></a></p>
<p>&nbsp;</p>
<p>由于类的每个对象都会引用类对象，因此展开右侧窗口的根节点，即可知晓对象的大概数量。在本例中，DefaultChannelPipeline、NioClientSocketChannel的数量大概是130万，NettyClient的数量是33。</p>
<div class="blog_h3"><span class="graybg">DominatorTree分析</span></div>
<p>MAT的支配树功能，可以帮助你追踪实际（Shallow）消耗内存的对象：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/03/pipelinesink-dominator-tree.png"><img class="aligncenter  wp-image-19713" src="https://blog.gmem.cc/wp-content/uploads/2018/03/pipelinesink-dominator-tree.png" alt="pipelinesink-dominator-tree" width="911" height="635" /></a></p>
<p>可以看到，Netty内部的某种链表结构（LinkedTransferQueue）的每个元素的RegisterTask对象占用了1KB的内存，但是链表长度非常大，因此这个链表消耗了大量的内存。</p>
<p>从名字LinkedTransferQueue可以猜测，这是Netty用来存储待处理的网络数据的。</p>
<div class="blog_h3"><span class="graybg">代码分析</span></div>
<p>从MAT分析的结果来看，NioClientSocketPipelineSink的内部类Boss的实例变量registerTaskQueue，占据了高达1.64GB的内存。我们来看一下这个类是做什么的：</p>
<pre class="crayon-plain-tag">// 代码中不相关的部分做了删减
class NioClientSocketPipelineSink extends AbstractChannelSink {

    final Executor bossExecutor;

    private final Boss[] bosses;
    private final NioWorker[] workers;

    private final AtomicInteger bossIndex = new AtomicInteger();
    private final AtomicInteger workerIndex = new AtomicInteger();

    // 这是外部能影响队列registerTaskQueue的唯一入口
    public void eventSunk( ChannelPipeline pipeline, ChannelEvent e) throws Exception {
        if (e instanceof ChannelStateEvent) {

            ChannelState state = event.getState();
            Object value = event.getValue();

            switch (state) {
            case CONNECTED:
                if (value != null) {
                    // 连接到客户端通道
                    connect(channel, future, (SocketAddress) value);
                } else {
                    channel.worker.close(channel, future);
                }
                break;
        } else if (e instanceof MessageEvent) {}
    }


    private void connect(  final NioClientSocketChannel channel, final ChannelFuture cf, SocketAddress remoteAddress) {
        try {
            if (channel.socket.connect(remoteAddress)) {
                channel.worker.register(channel, cf);
            } else {
                // 通道是非阻塞模式，因此会走到这个分支
                nextBoss().register(channel);
            }

        } catch (Throwable t) {}
    }
    Boss nextBoss() {
        return bosses[Math.abs( bossIndex.getAndIncrement() % bosses.length)];
    }


    private final class Boss implements Runnable {

        private final AtomicBoolean wakenUp = new AtomicBoolean();
        private final Object startStopLock = new Object();
        private final Queue&lt;Runnable&gt; registerTaskQueue = new LinkedTransferQueue&lt;Runnable&gt;();

        // 这个方法直接导致registerTaskQueue增大
        void register(NioClientSocketChannel channel) {
            Runnable registerTask = new RegisterTask(this, channel);

            synchronized (startStopLock) {
                // 如果尚未启动，则使用Executor启动Client Boss线程
                // 每当有新的连接请求到达，都会尝试启动Client Boss线程
                if (!started) {
                    DeadLockProofWorker.start(
                                bossExecutor,
                                new ThreadRenamingRunnable(
                                        this, "New I/O client boss #" + id + '-' + subId));
                }
                // 添加一个RegisterTask
                boolean offered = registerTaskQueue.offer(registerTask);
            }

            if (wakenUp.compareAndSet(false, true)) {
                // 唤醒选择器
                selector.wakeup();
            }
        }

        public void run() {
            Selector selector = this.selector;
            long lastConnectTimeoutCheckTimeNanos = System.nanoTime();
            for (;;) {
                wakenUp.set(false);

                try {
                    // 最多阻塞500ms
                    int selectedKeyCount = selector.select(500);
                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                    // 消费registerTaskQueue队列
                    processRegisterTaskQueue();

                    
                    if (selector.keys().isEmpty()) {
                        if (shutdown ||
                            bossExecutor instanceof ExecutorService &amp;&amp; ((ExecutorService) bossExecutor).isShutdown()) {

                            synchronized (startStopLock) {
                                if (registerTaskQueue.isEmpty() &amp;&amp; selector.keys().isEmpty()) {
                                    // 标记Client Boss线程为停止状态，下次再有连接请求到达，会再次启动
                                    started = false;
                                    try {
                                        selector.close();
                                    } catch (IOException e) {
                                        logger.warn(
                                                "Failed to close a selector.", e);
                                    } finally {
                                        this.selector = null;
                                    }
                                    // 退出条件
                                    break;
                                } else {
                                    shutdown = false;
                                }
                            }
                        } else {
                            shutdown = true;
                        }
                    } else {
                        shutdown = false;
                    }

                } catch (Throwable t) {}
            }
        }

        private void processRegisterTaskQueue() {
            for (;;) {
                final Runnable task = registerTaskQueue.poll();
                if (task == null) {
                    break;
                }

                task.run();
            }
        }

    private static final class RegisterTask implements Runnable {

        RegisterTask(Boss boss, NioClientSocketChannel channel) {
            this.boss = boss;
            this.channel = channel;
        }

        public void run() {
            try {
                // 在指定的选择器上注册通道的事件
                channel.socket.register( boss.selector, SelectionKey.OP_CONNECT, channel);
            } catch (ClosedChannelException e) {
                channel.worker.close(channel, succeededFuture(channel));
            }

            int connectTimeout = channel.getConfig().getConnectTimeoutMillis();
            if (connectTimeout &gt; 0) {
                channel.connectDeadlineNanos = System.nanoTime() + connectTimeout * 1000000L;
            }
        }
    }
}</pre>
<p>可以看到：</p>
<ol>
<li>每当有新的连接请求时，都会调用Boss.register，从而唤醒Boss.run线程</li>
<li>一旦Boss.run线程线程启动，它就会消费掉所有堆积的registerTaskQueue元素</li>
</ol>
<p>也就是说，正常情况下，registerTaskQueue队列不应该出现堆积的。由于发现问题时没有开启Dubbo或Netty的日志，准备等待下次问题重现后继续深入分析。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/jvm-dump-analysis-with-mat">使用Eclipse Memory Analyzer分析JVM堆Dump</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/jvm-dump-analysis-with-mat/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用Oracle Java Mission Control监控JVM运行状态</title>
		<link>https://blog.gmem.cc/jvm-monitoring-with-oracle-jmc</link>
		<comments>https://blog.gmem.cc/jvm-monitoring-with-oracle-jmc#comments</comments>
		<pubDate>Thu, 06 Nov 2014 03:11:25 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[JMC]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[性能剖析]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=2525</guid>
		<description><![CDATA[<p>简介 Oracle  Java Mission Control（以下称JMC）是一个集成到JVM（jdk7u40+）的性能剖析和诊断工具，相比起JProfiler之类的性能剖析工具，JMC更加简单易用，界面友好。 JMC使用了JVM内部特定的基于事件的接口，不同于一般性能剖析工具使用JVMPI/JVMTI方式实现。因此JMC几乎不会给应用造成额外的压力，可以用在负载很高的生产环境中： JFR在默认设置continuous下运行几乎对性能没有影响 JFR在默认设置profiling下运行通常造成小于2%的性能下降 在JDK 7 Update 40及其以后的版本中已经集成了JMC（%JDK_HOME%\bin\jmc.exe），打开此程序，会发现它是一个典型的Eclipse RCP应用。本文简单的介绍如何使用JMC来监控、记录JVM的运行过程。 目标JVM配置 注意：你看到的基于Eclipse的JMC用户界面，是一个前端的工具，在被监控的JVM（目标JVM）上需要开启以下Java Options才能对其进行监控、进行黑匣子记录。 [crayon-69e29aad36cfe281575204/] 如果要在目标JVM上启动持续不断的黑匣子记录，并在JVM正常退出时Dump记录为文件，可以参考下面的脚本设置JVM参数： [crayon-69e29aad36d02335497258/] 连接到目标JVM 配置并重启目标JVM后，打开JMC，点击 File <a class="read-more" href="https://blog.gmem.cc/jvm-monitoring-with-oracle-jmc">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/jvm-monitoring-with-oracle-jmc">使用Oracle Java Mission Control监控JVM运行状态</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Oracle  Java Mission Control（以下称JMC）是一个集成到JVM（jdk7u40+）的性能剖析和诊断工具，相比起JProfiler之类的性能剖析工具，JMC更加简单易用，界面友好。</p>
<p>JMC使用了JVM内部特定的基于事件的接口，不同于一般性能剖析工具使用JVMPI/JVMTI方式实现。因此JMC几乎不会给应用造成额外的压力，可以用在负载很高的生产环境中：</p>
<ol>
<li>JFR在默认设置continuous下运行几乎对性能没有影响</li>
<li>JFR在默认设置profiling下运行通常造成小于2%的性能下降</li>
</ol>
<p>在<a href="http://www.oracle.com/technetwork/articles/javase/index-jsp-138363.html" target="_blank">JDK 7 Update 40</a>及其以后的版本中已经集成了JMC（%JDK_HOME%\bin\jmc.exe），打开此程序，会发现它是一个典型的Eclipse RCP应用。本文简单的介绍如何使用JMC来监控、记录JVM的运行过程。</p>
<div class="blog_h1"><span class="graybg">目标JVM配置</span></div>
<div>注意：你看到的基于Eclipse的JMC用户界面，是一个前端的工具，在被监控的JVM（目标JVM）上需要开启以下Java Options才能对其进行监控、进行黑匣子记录。</div>
<pre class="crayon-plain-tag">-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.port=7777
-Dcom.sun.management.jmxremote
-XX:+UnlockCommercialFeatures
-XX:+FlightRecorder</pre>
<p>如果要在目标JVM上启动持续不断的黑匣子记录，并在JVM正常退出时Dump记录为文件，可以参考下面的脚本设置JVM参数：</p>
<pre class="crayon-plain-tag">for /f "tokens=1-3 delims=/ " %%a in ('date /t') do (set md=%%a-%%b-%%c)
for /f "tokens=1-3 delims=/:." %%a in ("%TIME%") do (set mt=%%a%%b%%c)
set CUR_TIME=%md%-%mt%

rem 启用黑匣子默认记录行为。官方文档注明dumponexitpath支持目录，但我在7u71版本下测试失败
set JAVA_OPTS=%JAVA_OPTS% -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=%LOG_DIR%\jfr\dump-on-exit-%CUR_TIME%.jfr,maxage=3600s,settings=%JRE_HOME%\lib\jfr\profile.jfc</pre>
<div class="blog_h1"><span class="graybg">连接到目标JVM</span></div>
<p>配置并重启目标JVM后，打开JMC，点击<span style="background-color: #c0c0c0;"> File - Connect... - Create New connection</span>，参考下图设置，完毕后点击Test connection确认可以连通，最后点击Finish结束配置。</p>
<p><img src="https://blog.gmem.cc/wp-content/uploads/2014/11/jmc-1.jpg" alt="" width="100%" /></p>
<div class="blog_h1"><span class="graybg">MBean Server</span></div>
<p>在左侧窗格JVM Browser视图中，双击刚刚创建的JMC连接，点击MBean Server，即可在右侧主窗体查看实时的JVM状态信息，供6个选项卡，下面依次描述。</p>
<div class="blog_h2"><span class="graybg">总览</span></div>
<p>包含JVM概要性的实时图表，包含3个面板：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;">面板 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>仪表盘（Dashboard）</td>
<td>
<p>以仪表方式显示系统运行参数，默认显示堆内存占用、JVM处理器占用、垃圾回收前后的堆内存情况。注意仪表上有有两个指针，灰色的通常显示最大值，黑色的显示当前值。</p>
<p>面板右侧的+号按钮，点击后可以添加目标JVM支持的任何运行参数到仪表盘中</p>
</td>
</tr>
<tr>
<td>处理器（Processor）</td>
<td>显示JVM、操作系统的动态CPU占用曲线</td>
</tr>
<tr>
<td>内存（Memory）</td>
<td>默认显示Java堆内存占用情况，可以选择查看物理内存占用等项目</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">MBean浏览器</span></div>
<p>以树形结构显示目标JVM支持的所有MBean列表和属性、操作集。</p>
<div class="blog_h2"><span class="graybg">触发器</span></div>
<p>包含若干预定义的触发器，可以在<span style="background-color: #c0c0c0;">特定条件下，执行预定义的动作</span>。支持的触发器包括：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;"> 触发器</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>CPU Usage - JVM Process (Too High)</td>
<td>当JVM的进程的CPU占用过高时</td>
</tr>
<tr>
<td>CPU Usage - JVM Process (Too Low)</td>
<td>当JVM的进程的CPU占用过低时</td>
</tr>
<tr>
<td>CPU Usage - Machine (Too High)</td>
<td>当OS的CPU占用过高时</td>
</tr>
<tr>
<td>CPU Usage - Machine (Too Low)</td>
<td>当OS的CPU占用过低时</td>
</tr>
<tr>
<td>Deadlocked Threads</td>
<td>当发生JVM线程死锁时</td>
</tr>
<tr>
<td>Live Set (Too Large)</td>
<td>当垃圾回收之后，存活对象占堆大小百分比过大时</td>
</tr>
<tr>
<td>Thread Count (Too High)</td>
<td>当JVM线程总数过大时</td>
</tr>
</tbody>
</table>
<p>此外，任何MBean属性均可以作为触发器使用，中间件（例如Tomcat、Weblogic、ActiveMQ）都有自己的扩展。 双击触发器后，右侧即显示规则详情（Rule Details），可以定义触发的阈值（到达时，或者从异常值恢复时）、执行的动作，可用的动作包括：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;"> 动作</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Application alert</td>
<td>在应用程序上显示一个警告对话框</td>
</tr>
<tr>
<td>Console output</td>
<td>在控制台（stdout）上输出警告信息</td>
</tr>
<tr>
<td>Dump Flight Recording</td>
<td>保存黑匣子记录内容</td>
</tr>
<tr>
<td>HPROF Dump</td>
<td>保存堆镜像</td>
</tr>
<tr>
<td>Log to file</td>
<td>在日志文件中输出警告信息</td>
</tr>
<tr>
<td>Send e-mail</td>
<td>通过电子邮件发送警告信息</td>
</tr>
<tr>
<td>Start Continuous Flight Recording</td>
<td>启动不间断的黑匣子记录</td>
</tr>
<tr>
<td>Start Time Limited Flight Recording</td>
<td>启动限制时间长度的黑匣子记录</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">LiveSet</span></div>
<p>所谓LiveSet，是指在一次<span style="background-color: #c0c0c0;">垃圾回收之后，堆内存的占用比率</span>，取值在0-1之间。</p>
<p>如果GC后，可用的内存空间不足，则LiveSet接近100%，这预示可能存在OOM风险。在生产环境下，我们可以监控LiveSet，并在其过大时，把堆内存映像Dump出来供后续分析。</p>
<p>触发器配置示例：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2014/11/liveset-trigger-1.png"><img class="aligncenter  wp-image-19045" src="https://blog.gmem.cc/wp-content/uploads/2014/11/liveset-trigger-1.png" alt="liveset-trigger-1" width="924" height="372" /></a></p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2014/11/liveset-trigger-2.png"><img class="aligncenter wp-image-19047" src="https://blog.gmem.cc/wp-content/uploads/2014/11/liveset-trigger-2.png" alt="liveset-trigger-2" width="925" height="295" /></a></p>
<p>其中Condition面板：</p>
<ol>
<li>Current Value为指标的当前值，当发生GC后，自动刷新</li>
<li>Max trigger value为触发器激活时的指标值</li>
<li>Sustained period，指标必须到达Max trigger value至少多长时间，触发器才可能激活</li>
<li>Limit period，连续两次激活触发器的最小间隔时间</li>
</ol>
<p>其中Action面板：</p>
<ol>
<li>Only live，如果勾选，仅仅GCRoot可达的对象才被Dump出来</li>
<li>hprof路径为服务器的路径</li>
</ol>
<p>其中Constraints面板：可以指定此触发器在什么日期、什么时间段启用。</p>
<p>要分析内存映像，你可以使用如下命令启动一个Web UI：</p>
<pre class="crayon-plain-tag">jhat -port 7770 -J-Xmx4G default.hprof</pre>
<div class="blog_h2"><span class="graybg">系统</span></div>
<p>显示JVM和系统的概要信息，包含3个面板：</p>
<ol>
<li>系统信息：例如OS类型、硬件架构、系统内存、JVM的PID、JVM版本、Classpath、JVM启动时间、JVM参数（Java Options）</li>
<li>JVM统计：例如已加载的类数量、已运行时间</li>
<li>系统属性：显示JVM的系统属性</li>
</ol>
<div class="blog_h2"><span class="graybg">内存</span></div>
<p>显示JVM内存占用情况，分为上下两个面板。</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;">面板</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>GC Table</td>
<td>
<p>显示垃圾回收器（ParNew：新生代的并行回收器，ConcurrentMarkSweep：并发垃圾回收器…）的工作情况，包括：GC总计时间、GC总次数、最近GC开始结束时间、GC标识符、GC线程数</p>
</td>
</tr>
<tr>
<td>Active Memory Pools</td>
<td>
<p>显示当前的内存池的大小、已用大小、类型（堆/非堆），可能有以下内存池：</p>
<ol>
<li>Code Cache：代码缓存</li>
<li>Par Eden Space：并行（-XX:+UseParNewGC）垃圾回收的新生代Eden区</li>
<li>Par Survivor Space：并行垃圾回收的新生代Survivor区</li>
<li>CMS Perm Gen：并发垃圾回收永久代</li>
<li>CMS Old Gen：并发垃圾收集年老代</li>
<li>PS Old Gen：并行（-XX:+UseParallelGC）垃圾收集年老代</li>
</ol>
<p>关于垃圾回收器内存分代，参考：<a href="/jvm-options-and-performance-tuning" target="_blank">JVM参数与性能调优</a></p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">线程</span></div>
<p>显示JVM线程活动情况，分为三个面板。</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;">面板</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Live Thread Graph</td>
<td>
<p>显示当前存活线程的数量、守护线程的数量、线程数量峰值的实时曲线</p>
</td>
</tr>
<tr>
<td>Live Threads</td>
<td>
<p>显示当前存活线程的列表，可以进行CPU占用分析、死锁检测、线程分配内存量的分析</p>
</td>
</tr>
<tr>
<td>Stack traces for selected threads</td>
<td>显示瞬时的线程调用栈</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">启动黑匣子</span></div>
<p>正如民航的黑匣子一样，JMC的这个功能，可以用于事后分析系统性能低下，甚至崩溃的原因。JMC支持两种方式来启动黑匣子记录：通过触发器自动启动， 或者在左侧面板，双击Flight Recorder，手工启动。手工启动可以参考下图设置：<a href="/wp-content/uploads/2014/11/jmc-2.jpg"><img class="size-full wp-image-2552 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2014/11/jmc-2.jpg" alt="jmc-2" width="793" height="626" /></a></p>
<p>其中：Time fixed recording表示<span style="background-color: #c0c0c0;">记录定长时间</span>，时间到达后，自动停止记录；Continuous recording表示进行<span style="background-color: #c0c0c0;">持续不断的记录</span>，直到JVM退出或者接收到停止指令，这种记录方式下可以随时进行dump操作。</p>
<p>虽然黑匣子在JVM本地记录数据，但不在Java堆中记录，因此它并不会影响内存特性和垃圾收集。</p>
<p>黑匣子录制完成后，会在你选择的目录中生成一个.jfr文件，该文件包含录制期间JVM中发生的各种事件。</p>
<div class="blog_h2"><span class="graybg">工作机制</span></div>
<p>黑匣子通过JVM内部API来收集数据，并存放在一个线程本地（thread-local）的缓冲中，并最终刷入全局内存缓冲（global in-memory buffer），内存缓冲中的数据，会被刷入磁盘，磁盘文件时经过压缩的。每个记录数据块，要么在内存中，要么在磁盘中，这意味着——进程被停止或者宕机时，部分数据会丢失，JVM崩溃会也可能导致一部分数据丢失。</p>
<p>可以配置黑匣子不使用磁盘空间，这种场景下，全局内存缓冲被循环使用，<span style="background-color: #c0c0c0;">旧的数据自动丢弃</span>。</p>
<div class="blog_h2"><span class="graybg">黑匣子记录模板</span></div>
<p>在JMC界面上，点击 Windows - Flight Recording Template Manager，看到类似如下界面。可以导入、导出、编辑JFR的设置文件。</p>
<p><a href="/wp-content/uploads/2014/11/jmc-5.jpg"><img class="size-full wp-image-2658 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2014/11/jmc-5.jpg" alt="jmc-5" width="768" height="600" /></a></p>
<p>我们可以导入%JDK_HOME%\jre\lib\jfr下的两个自带模板：default.jfc（Continuous）、profile.jfc（Profiling）。这两个模板的对比如下：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 模板</td>
<td style="text-align: center;">特性 </td>
</tr>
</thead>
<tbody>
<tr>
<td> Continuous</td>
<td>
<p>Garbage Collector:正常<br />Compiler:正常<br />Method Sampling: 正常<br />Thread Dump: 最少一次<br />Exceptions:仅Error<br />Synchronization Threshold: 20 ms<br />File I/O Threshold: 20 ms<br />Socket I/O Threshold: 20 ms<br />Heap Statistics: 禁用<br />Class Loading:禁用<br />Allocation Profiling:禁用</p>
<p>未启用记录的事件类型：Exception Throw、<span style="background-color: #c0c0c0;">Object Alloc In New TLAB、Object Alloc Outside TLAB</span>、Class Loading、<span style="background-color: #c0c0c0;">Compile/Failure</span>、GC/Object Count、GC/Object Count After GC、GC/Phases/Phase Level 3、Profiling/Execution Sample Info、</p>
</td>
</tr>
<tr>
<td> Profiling</td>
<td>
<p>Garbage Collector:正常<br />Compiler:详细<br /><span style="background-color: #c0c0c0;">Method Sampling: 最大化</span><br /><span style="background-color: #c0c0c0;">Thread Dump: 每分钟</span><br />Exceptions:仅Error<br /><span style="background-color: #c0c0c0;">Synchronization Threshold: 10 ms</span><br /><span style="background-color: #c0c0c0;">File I/O Threshold: 10 ms</span><br /><span style="background-color: #c0c0c0;">Socket I/O Threshold: 10 ms</span><br />Heap Statistics: 禁用<br />Class Loading:禁用<br /><span style="background-color: #c0c0c0;">Allocation Profiling:启用</span></p>
<p>未启用记录的事件类型：Exception Throw、Class Loading、GC/Object Count、GC/Object Count After GC、GC/Phases/Phase Level 3、Profiling/Execution Sample Info、</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">黑匣子诊断命令</span></div>
<p>利用诊断命令，可以通过脚本控制黑匣子的行为，命令的示例如下：</p>
<pre class="crayon-plain-tag">rem 获取进程号为5361的JVM关于JFR.start命令的帮助
jcmd 5361 help JFR.start

rem 检查进程的JFR运行状态
jcmd 7648 JFR.check
rem 输出如下。注意recording=0表示正在运行的记录的标识符，后续操作需要用到这个标识符
rem 7648:
rem Recording: recording=0 name="HotSpot default" maxage=1h (running)

rem 启动一个15分钟的黑匣子记录，延迟5分钟启动，使用JDK自带的默认设置（default.jfc），保存Dump文件到D盘
jcmd 7648 JFR.start name=SampleRcd delay=300 duration=900s filename=D:\temp\SampleRcd.jfr 
     \ settings=D:\JavaEE\jdk\x64\jre\lib\jfr\default.jfc

rem 相对路径取决于目标JVM
jcmd 1234 JFR.start delay=0s duration=${jfr_du}s name=$name filename=$PWD/$name.jfr settings=profile

rem 下面的脚本，可以用于定期进行记录，按记录时间保存为不同的文件
setlocal
set DUMP_DIR=D:\temp
set SERVICE_NAME=Tomcat7
set JAVA_HOME=D:\JavaEE\jdk\x64\1.7
pushd %JAVA_HOME%\bin
for /f "tokens=1-3 delims=/ " %%a in ('date /t') do (set md=%%a-%%b-%%c)
for /f "tokens=1-3 delims=/:." %%a in ("%TIME%") do (set mt=%%a%%b%%c)
set CUR_TIME=%md%-%mt%
for /F "tokens=1,3" %%a in ('sc queryex %SERVICE_NAME%') do (
    if "%%a" == "PID" set "JPID=%%b"
)

rem 启动飞行记录，如果目标JVM是以SYSTEM身份启动的NT Service，可以使用PSTools发起jcmd命令，否则会报错：insufficient memory or insufficient privileges to attach
rem psexec -s jcmd %JPID% JFR.start ...
jcmd %JPID% JFR.start name=rcd-%CUR_TIME% duration=900s compress=true filename=%DUMP_DIR%\rcd-%CUR_TIME%.jfr settings=%JAVA_HOME%\jre\lib\jfr\profile.jfc
popd
endlocal

rem 导出JFR的Dump文件
rem 如果目标JVM正在在运行持续不断的黑匣子记录，使用该命令，再结合定时任务机制，可以定期保存黑匣子记录的Dump文件
jcmd 7648 JFR.dump recording=0 filename=F:\temp\dump.jfr</pre>
<div class="blog_h2"><span class="graybg">命令列表</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 命令</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>JFR.start</td>
<td>
<p>启动一个JFR（黑匣子记录）实例，参数：</p>
<p>name 本次记录的名称<br />settings 服务器上的设置模板<br />defaultrecording 是否启动默认录制<br />delay 录制延迟时间，默认0s<br />duration 录制持续时间，默认0s，表示无限<br />filename 录制文件的名称<br />compress 是否使用GZip压缩结果，默认否<br />maxage 数据最大生命周期，默认0s，表示无限制<br />maxsize 数据最大字节数，默认0，表示无限制</p>
</td>
</tr>
<tr>
<td>JFR.check</td>
<td>
<p>显示运行中的JFR的状态，参数：</p>
<p>name 记录名称<br />recording 记录标识符<br />verbose 打印冗余信息，默认否</p>
</td>
</tr>
<tr>
<td>JFR.stop</td>
<td>
<p>停止运行中的JFR，参数：</p>
<p>name 记录名称<br />recording 记录标识符<br />discard 是否丢弃录制数据<br />filename 拷贝录制数据到文件</p>
</td>
</tr>
<tr>
<td>JFR.dump</td>
<td>
<p>将录制中的JFR保存到文件，参数：</p>
<p>name 记录名称<br />recording 记录标识符<br />filename 拷贝录制数据到文件</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">黑匣子分析</span></div>
<p>打开黑匣子记录的.jfr文件，即可看到类似下面的界面：左侧是记录的不同视图，可以点击切换；右侧主面板，顶部是事件的时间轴，<span style="background-color: #c0c0c0;">拖动滑块，可以改变聚焦的时间范围（勾选右边的同步选择可以在切换视图时保持聚集范围）</span>，底部有若干选项卡。</p>
<p><a href="/wp-content/uploads/2014/11/jmc-3.jpg"><img class="alignnone  wp-image-2582" src="https://blog.gmem.cc/wp-content/uploads/2014/11/jmc-3.jpg" alt="jmc-3" width="917" height="653" /></a></p>
<p>下面，按照视图分别描述。</p>
<div class="blog_h2"><span class="graybg">基本信息</span></div>
<p>包含4个选项卡：</p>
<div class="blog_h3"><span class="graybg">总览</span></div>
<p style="padding-left: 30px;">可以看到聚焦时间范围内的实时仪表（堆占用、CPU占用、GC停顿时间），以及CPU、堆使用情况的曲线图。</p>
<div class="blog_h3"><span class="graybg">JVM信息</span></div>
<p style="padding-left: 30px;">可以看到JVM的一些基本信息，包括启动时间、JVM参数等。</p>
<div class="blog_h3"><span class="graybg">系统属性</span></div>
<p style="padding-left: 30px;">可以看到JVM的系统属性列表</p>
<div class="blog_h3"><span class="graybg">记录</span></div>
<p style="padding-left: 30px;">可以显示记录的事件的列表</p>
<div class="blog_h3"><span class="graybg">内存</span></div>
<p>包含6个选项卡</p>
<div class="blog_h3"><span class="graybg">总览</span></div>
<p style="padding-left: 30px;">Memory Usage：可以显示聚焦时间范围内OS总内存、OS已使用内存、提交的堆、使用的堆的大小曲线图。</p>
<p style="padding-left: 30px;">GC Configuration：显示简单的GC配置</p>
<p style="padding-left: 30px;">GC Statistics：显示简单的GC统计，包括垃圾回收次数、停顿时间</p>
<div class="blog_h3"><span class="graybg">垃圾收集</span></div>
<p style="padding-left: 30px;">上面的面板，包含4个选项卡：</p>
<p style="padding-left: 60px;">Heap：堆的使用情况曲线，红色小柱形显示了GC停顿的持续时间（刻度在右边）</p>
<p style="padding-left: 60px;">Reference Objects：包含软引用、弱引用、幻引用、一般引用的数量变化区域图</p>
<p style="padding-left: 60px;">Failed Promotions：失败的晋升列表，所谓晋升是指：年轻代对象向年老代转移的GC动作，包含对象个数和大小等信息</p>
<p style="padding-left: 60px;">Failed Evacuations：</p>
<p style="padding-left: 30px;">下面的面板，左侧显示历次GC的列表，右侧显示单次GC的详细信息，如下：</p>
<p style="padding-left: 60px;">General：显示GC类型、触发原因、ID、开始结束时间、暂停时间</p>
<p style="padding-left: 60px;">GC Phase：GC每个步骤的开始和持续时间</p>
<p style="padding-left: 60px;">Reference Objects：本次GC涉及的各类引用的数量</p>
<p style="padding-left: 60px;">Heap：堆在GC前后的大小对比</p>
<p style="padding-left: 60px;">Perm Gen：永久代在GC前后的大小对比</p>
<div class="blog_h3"><span class="graybg">垃圾回收时间</span></div>
<p style="padding-left: 30px;">显示各代、各次GC所消耗的时间</p>
<div class="blog_h3"><span class="graybg">垃圾回收配置</span></div>
<p style="padding-left: 30px;">显示GC的详细配置，包括GC基本配置、堆的配置、年轻代的配置</p>
<div class="blog_h3"><span class="graybg"> 内存分配</span></div>
<p>显示聚焦时间范围内的内存分配统计信息</p>
<p style="padding-left: 30px;">General：在线程<span style="background-color: #c0c0c0;">本地分配缓冲（TLAB）内、外（即Eden区域）</span>中分配的对象个数、内存大小。面板下部是内存分配大小按时间的柱形图</p>
<p style="padding-left: 30px;">Allocation in new TLAB：在TLAB中分配的内存，按对象类型、或者按执行分配的线程的占比饼图；Allocation Profile可以剖析分配内存热点所在的调用栈</p>
<p style="padding-left: 30px;">Allocation outside TLAB：在Eden区域的内存分配情况，包含的子选项卡同上</p>
<p>示例：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2014/11/jfr-memory-alloc.png"><img class="aligncenter wp-image-19061" src="https://blog.gmem.cc/wp-content/uploads/2014/11/jfr-memory-alloc.png" alt="jfr-memory-alloc" width="927" height="709" /></a></p>
<p>在Allocation By Class选项卡中：</p>
<ol>
<li>上面板Allocation Pressure，按对象类型，显示内存占用最大的对象</li>
<li>下面板Stack Trace，选中上面板的某个对象类型后，在此显示分配对象的调用栈。第一行为直接分配对象的代码</li>
</ol>
<div class="blog_h3"><span class="graybg">对象统计</span></div>
<p style="padding-left: 30px;">显示对象数量和内存占用的统计信息，包含上下两部分</p>
<p style="padding-left: 30px;">上面的面板：显示占有堆内存百分比大于0.5%的类型的列表</p>
<p style="padding-left: 30px;">下面的面板：显示增长数量最快的类型的列表</p>
<div class="blog_h2"><span class="graybg">代码</span></div>
<p>包含6个选项卡：</p>
<div class="blog_h3"><span class="graybg">总览</span></div>
<p style="padding-left: 30px;">显示占用CPU时间最多的代码，分为上下两部分</p>
<p style="padding-left: 30px;">上面的面板：显示CPU时间占用最多的包，包括列表和饼图</p>
<p style="padding-left: 30px;">下面的面板：显示CPU占用最多的类，按占比降序排列</p>
<div class="blog_h3"><span class="graybg">热点方法</span></div>
<p style="padding-left: 30px;">显示热点方法（CPU占用时间最多），按占比降序排列，点击左侧小箭头可以向下钻取（找到哪些代码调用了这个方法导致的热点）</p>
<div class="blog_h3"><span class="graybg">调用树</span></div>
<p style="padding-left: 30px;">类似于热点方法选项卡，但是会显示所有热点方法的完整调用栈</p>
<div class="blog_h3"><span class="graybg">异常</span></div>
<p style="padding-left: 30px;">包含3个选项卡，显示异常（包括错误）的统计信息</p>
<p style="padding-left: 30px;">Overview：显示异常和错误的总数、最频繁发生异常的调用栈</p>
<p style="padding-left: 30px;">Exceptions、Error：单独显示异常或者错误的总数、频繁调用栈，注意，异常默认是不记录的</p>
<div class="blog_h3"><span class="graybg">编译</span></div>
<p style="padding-left: 30px;">显示即时编译的次数、编译大小、失败的日志</p>
<div class="blog_h3"><span class="graybg">类加载</span></div>
<p style="padding-left: 30px;">显示按时间的类加载、卸载的数量曲线图</p>
<div class="blog_h2"><span class="graybg">线程</span></div>
<div class="blog_h3"><span class="graybg">总览</span></div>
<p style="padding-left: 30px;">分为上下两部分：</p>
<p style="padding-left: 30px;">上面的面板：显示OS、JVM的CPU占用率的按时间统计的区域图</p>
<p style="padding-left: 30px;">下面的面板：显示守护线程、活动线程数量的曲线图</p>
<div class="blog_h3"><span class="graybg">热点线程</span></div>
<p style="padding-left: 30px;">显示CPU占用最多的线程，及选中线程的热点方法，分为上下两部分</p>
<p style="padding-left: 30px;">上面的面板：显示CPU占用最多的线程，按占比降序排列</p>
<p style="padding-left: 30px;">下面的面板：显示上面板选中线程的热点方法、调用栈 </p>
<div class="blog_h3"><span class="graybg">资源争用</span></div>
<p style="padding-left: 30px;">显示因为锁争用导致的线程阻塞的统计信息，分为3个选项卡：</p>
<p style="padding-left: 60px;">Top Blocking Locks：发生阻塞最多的监视器（锁）对象的列表，包含了锁定次数、最长时间</p>
<p style="padding-left: 60px;">Top Blocked Threads：因为等待锁被阻塞时间最长的线程的列表</p>
<p style="padding-left: 60px;">Top Blocking Threads：因为占用锁导致其他线程等待时间最长的线程的列表</p>
<div class="blog_h3"><span class="graybg">延迟</span></div>
<p style="padding-left: 30px;">显示因为：Java线程休眠、监视器等待、Socket读取、监视器阻塞等原因导致的线程执行延迟的情况，包括延迟信息的列表和饼图，已经导致延迟的线程调用栈。延迟相关的事件包括：Java Thread Park（Waiting）、Java Monitor Wait（等待被唤醒）、Java Monitor  Blocked（积极进入同步区被阻塞）等。</p>
<div class="blog_h3"><span class="graybg">线程栈Dump</span></div>
<p style="padding-left: 30px;">包含定时捕获的JVM中所有线程的瞬时调用栈镜像</p>
<div class="blog_h3"><span class="graybg">监视器实例</span></div>
<p style="padding-left: 30px;">Lock Instances：监视器对象的列表，包含锁定次数、总计持续时间</p>
<p style="padding-left: 30px;">Trace Tree：选中监视器对象的锁定调用栈，每个调用栈锁定的次数</p>
<p style="padding-left: 30px;">Top Threads：占用监视器次数最多的线程列表</p>
<div class="blog_h2"><span class="graybg">输入输出</span></div>
<div class="blog_h3"><span class="graybg">总览</span></div>
<p style="padding-left: 30px;">分为两个选项卡</p>
<p style="padding-left: 30px;">File Overview：</p>
<p style="padding-left: 60px;">File Reads and Writes：显示按时间读写文件字节数的曲线</p>
<p style="padding-left: 60px;">File Read、File Write：显示针对具体文件的读写字节数、次数的列表</p>
<p style="padding-left: 30px;">Socket Overview：</p>
<p style="padding-left: 60px;">Socket Read and Writes：显示按时间读写Socket字节数的曲线</p>
<p style="padding-left: 60px;">Socket  Read、Socket Writes：显示针对具体目标主机、端口读写字节数、次数的列表</p>
<div class="blog_h3"><span class="graybg">读文件</span></div>
<p>按文件、线程、事件统计文件读操作的列表，以及每个文件读取按时间的变化、线程调用栈</p>
<div class="blog_h3"><span class="graybg">写文件</span></div>
<p>按文件、线程、事件统计文件写操作的列表，以及每个文件写入按时间的变化、线程调用栈</p>
<div class="blog_h3"><span class="graybg">读Socket</span></div>
<p>按目标主机、线程、事件统计文Socket读操作的列表，以及每个Socket读取按时间的变化、线程调用栈</p>
<div class="blog_h3"><span class="graybg">写Socket</span></div>
<p>按目标主机、线程、事件统计文Socket写操作的列表，以及每个Socket写入按时间的变化、线程调用栈</p>
<div class="blog_h2"><span class="graybg">系统</span></div>
<div>包含操作系统和硬件的基本信息</div>
<div class="blog_h2"><span class="graybg">事件</span></div>
<p>显示黑匣子记录的事件的相关信息，<span style="background-color: #c0c0c0;">左侧面板Event Types可以过滤</span>关注的事件类型。总体来说，事件可以分为三类：<span style="background-color: #c0c0c0;">持续事件</span>（duration，持续一段时间，例如GC事件）；<span style="background-color: #c0c0c0;">瞬时事件</span>（instant，立即发生和结束，例如内存溢出）；<span style="background-color: #c0c0c0;">样本事件</span>（sample，通过定期采样获取，例如栈dump）。</p>
<div class="blog_h3"><span class="graybg">总览</span></div>
<p>Producers：显示事件生产者的列表，以及制造事件的占比饼图</p>
<p style="padding-left: 30px;">Event Types：显示各类型事件持续总时间、次数，占比饼图</p>
<div class="blog_h3"><span class="graybg">日志</span></div>
<p style="padding-left: 30px;">显示每一个事件的记录，按时间排列，为每个事件显示：开始时间、结束时间、持续时间、产生事件的线程</p>
<div class="blog_h3"><span class="graybg">图表</span></div>
<p style="padding-left: 30px;">以时间轴的形式展示事件历史</p>
<div class="blog_h3"><span class="graybg">线程</span></div>
<p style="padding-left: 30px;">以列表形式展示各线程产生事件的数量、持续时间</p>
<div class="blog_h3"><span class="graybg">调用栈跟踪</span></div>
<p style="padding-left: 30px;">按产生事件持续时间长短，降序排列相关的调用栈</p>
<div class="blog_h3"><span class="graybg">事件统计</span></div>
<p style="padding-left: 30px;">可以按单个事件类型进行简单的统计分析，支持不同的分组方式，支持总数、平均数、次数等指标，选中单个统计结果，可以显示调用栈。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/jvm-monitoring-with-oracle-jmc">使用Oracle Java Mission Control监控JVM运行状态</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/jvm-monitoring-with-oracle-jmc/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JVM参数与性能调优</title>
		<link>https://blog.gmem.cc/jvm-options-and-performance-tuning</link>
		<comments>https://blog.gmem.cc/jvm-options-and-performance-tuning#comments</comments>
		<pubDate>Fri, 13 Jun 2014 06:28:08 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[性能调优]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=2330</guid>
		<description><![CDATA[<p>本文主要适用于HotSpot JVM的参数设置与调优。 性能监控与故障处理工具 第三方工具 工具类型 说明 堆Dump分析 推荐使用MAT，参考使用Eclipse Memory Analyzer分析JVM堆Dump 栈Dump分析 推荐使用在线工具：http://fastthread.io/ 低成本剖析 推荐使用JMC，参考使用Oracle Java Mission Control监控JVM运行状态 JDK命令  命令  说明 jps JVM <a class="read-more" href="https://blog.gmem.cc/jvm-options-and-performance-tuning">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/jvm-options-and-performance-tuning">JVM参数与性能调优</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>本文主要适用于HotSpot JVM的参数设置与调优。</p>
<div class="blog_h1"><span class="graybg">性能监控与故障处理工具</span></div>
<div class="blog_h2"><span class="graybg">第三方工具</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">工具类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>堆Dump分析</td>
<td>推荐使用MAT，参考<a href="/jvm-dump-analysis-with-mat">使用Eclipse Memory Analyzer分析JVM堆Dump</a></td>
</tr>
<tr>
<td>栈Dump分析</td>
<td>推荐使用在线工具：<a href="http://fastthread.io/">http://fastthread.io/</a></td>
</tr>
<tr>
<td>低成本剖析</td>
<td>推荐使用JMC，参考<a href="/jvm-monitoring-with-oracle-jmc">使用Oracle Java Mission Control监控JVM运行状态</a></td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">JDK命令</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;"> 命令</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3"><a id="jps"></a>jps</td>
<td>
<p>JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程</p>
<p><span style="background-color: #c0c0c0;">格式：</span><br />jps [ options ] [ hostid ]</p>
<p><span style="background-color: #c0c0c0;">选项：</span><br />-q 只输出LVMID,省略略主类的名称<br />-m 输出虚拟机进程启动时传递给主类main()函数的参数<br />-l 输出主类的全名.如果进程执行的是Jar包，输出Jar路径 <br />-v 输出虚拟机进程启动时JVM参数</p>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jstat"></a>jstat</td>
<td>
<p>JVM Statistics Monitoring Tool，用干收集HotSpot虚拟机各方面的运行数据（类装载、内存、垃圾收集、JIT编译等）</p>
<p><span style="background-color: #c0c0c0;">格式：</span><br />jstat [ option vmid [interval[s|ms] [count]] ]</p>
<p><span style="background-color: #c0c0c0;">选项：</span><br />-class 监视类装载、卸载数、总空间及类装载所耗费的时间<br />-gc 监视Java堆状况，包括Eden区、2个survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息<br />-gccapacity 监视内容与-gc基本相同，但输出主要关注Java堆各个区域使用到的最大和最小空间<br />-gcutil 监视内容与-gc基本相同，但输出主要关注已使用空间占总空间的百分比<br />-gccause 与-gcutil功能一样，但是会额外输出导致上一次GC产生的原因<br />-gcnew 监视新生代GC的状况<br />-gcnewcapacity 监视内容与-gcnew基本相同，输出主要关注使用到的最大和最小空间<br />-gcold 监视老年代GC的状况<br />-gcoldcapacity 监视内容与-gcold基本相同，输出主要关注使用到的最大和最小空间<br />-gcpermcapacity 输出永久代使用到的最大和最小空间<br />-compiler 输出JIT编译器编译过的方法、耗时等信息<br />-printcompilation 输出已经被JIT编译的方法</p>
<p><span style="background-color: #c0c0c0;">举例：</span></p>
<pre class="crayon-plain-tag">#每250毫秒査询一次进程2764垃圾收集的状况，一共査询20次
jstat -gc 2764 250 20</pre>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jinfo"></a>jinfo</td>
<td>
<p>Configuration Info for Java，实时地査看和调整虚拟机的各项参数。
<p><span style="background-color: #c0c0c0;">格式：</span><br />jinfo [ option ] pid</p>
<p><span style="background-color: #c0c0c0;">举例：</span></p>
<pre class="crayon-plain-tag">jinfo -flag CMSInitiatingOccupancyFraction 1444 
#输出-XX：CMSInitiatingOccupancyFraction=85</pre>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jmap"></a>jmap</td>
<td>
<p>Memory Map for Java，生成虚拟机的内存转储快照（heapdump文件）
<p><span style="background-color: #c0c0c0;">格式：</span><br />jmap [ option ] vmid</p>
<p><span style="background-color: #c0c0c0;">选项：</span><br />-dump 生成Java堆转储快照。格式为：-dump:[live,]format=b，file= ，live子参数说明是否只dump出存活的对象<br />-finalizerinfo 显示在F-Queue中等待Finalizer线程执行finalize方法的对象。只在Linux / Solaris平台下有效<br />-heap 显示Java堆详细信息，如使用哪种回收器、参数配置、分代状况等，只在Linux/ Solaris平台下有效<br />-histo 显示堆中对象统计信息.包括类、实例数量和合计容量<br />-permstat 以ClassLoadcr为统计口径显示永久代内存状态，只在Linux/ Solaris平台下有效<br />-F 当虚拟机进程对-dump选项没有响应时，可使用这个选项强制生成dump快照，只 在Linux / Solaris平台下有效</p>
<p><span style="background-color: #c0c0c0;">举例：</span></p>
<pre class="crayon-plain-tag">jmap -dump:format=b,file=eclipse.dump 3500
#Dumping heap to ...
#Heap dump file created

# 显示进程5732的GC可达对象直方图
jmap -histo:live 5732</pre>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jhat"></a>jhat</td>
<td>
<p>JVM Heap Dump Browser,用于分析 heapdump 文件，它会建立一个 HTTP/HTML 服务器，让用户可以在浏览器上査看分析结果
<p>注意<a href="/jvm-study-note#type-id-in-jvm">JVM内部的类型表示方式</a>。</p>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jstack"></a>jstack</td>
<td>
<p>Stack Trace for Java，显示虚拟机的线程快照</p>
<p><span style="background-color: #c0c0c0;">格式：</span><br />jstack [ option ] vmid</p>
<p><span style="background-color: #c0c0c0;">选项：</span><br />-F 当正常输出的请求不被响应时，强制输出线程堆栈<br />-l 除堆栈外，显示关于锁的附加信息<br />-m 如果调用到本地方法的话，可以显示C/C++的堆栈</p>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jcmd"></a>jcmd</td>
<td>
<p>不带任何参数运行jcmd，显示所有JVM进程的PID和Main类名</p>
<p>运行<pre class="crayon-plain-tag">jcmd 0 help</pre>，显示每个JVM支持的命令</p>
<p>命令示例：</p>
<pre class="crayon-plain-tag"># 打印JVM参数
jcmd 25464 VM.flags

# 打印所有系统属性
jcmd 25464 VM.system_properties

# Dump出线程栈
jcmd 25464 Thread.print

# Dump出堆
jcmd 25464 GC.heap_dump

# 强制GC
jcmd 25464 GC.run</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">JVM调用命令格式</span></div>
<p><pre class="crayon-plain-tag"># options：JVM参数
# class：main方法所在类
java [ options ] class [ arguments ]
# -jar 被调用的jar包路径
java [ options ] -jar file.jar [ arguments ]
#用于GUI
javaw [ options ] class [ arguments ]
javaw [ options ] -jar file.jar [ arguments ]</pre>
<div class="blog_h1"><span class="graybg">标准参数</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 参数</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 200px;">-client</td>
<td>使用 Java HotSpot Client VM，64bit的JDK忽略此选项，使用-server</td>
</tr>
<tr>
<td>-server</td>
<td>使用Java HotSpot Server VM，64bit的JDK只能是这种模式，不需要指定</td>
</tr>
<tr>
<td>
<p>-agentlib:libname[=options]</p>
</td>
<td>
<p>根据名称加载本地（native）JVM代理，例如：</p>
<p>-agentlib:hprof<br />-agentlib:jdwp=help<br />-agentlib:hprof=help</p>
</td>
</tr>
<tr>
<td>
<p>-agentpath:pathname[=options]</p>
</td>
<td>
<p>根据路径全名加载本地JVM代理，例如：</p>
<p>-agentpath:D:\JavaEE\jprofiler\7.2.2\bin\windows-x64\jprofilerti.dll=offline</p>
</td>
</tr>
<tr>
<td>
<p>-classpath classpath<br />-cp classpath</p>
</td>
<td>指定一系列分号;分隔的目录、JAR、ZIP文件路径，用于从中寻找类文件，注意，此参数覆盖CLASSPATH环境变量。如果没指定，也没有CLASSPATH环境变量，当前目录（.）为类路径</td>
</tr>
<tr>
<td>-Dproperty=value</td>
<td>设置JVM系统属性</td>
</tr>
<tr>
<td>
<p>-da[:pkgname | :clsname ]</p>
</td>
<td> 禁用断言，默认是禁用的</td>
</tr>
<tr>
<td>-ea[:pkgname | :clsname ]</td>
<td>
<p> 启用断言：例如：</p>
<p>-ea:cc.gmem.demo... ，启用demo包及其子包的断言</p>
</td>
</tr>
<tr>
<td>-esa，-dsa</td>
<td>启用、禁用所有system classes的断言</td>
</tr>
<tr>
<td>-help  -?</td>
<td>显示帮助</td>
</tr>
<tr>
<td>-jar</td>
<td>执行包装在jar中的类</td>
</tr>
<tr>
<td>
<p>-javaagent:jarpath[=options]</p>
</td>
<td>加载基于Java语言的JVM代理</td>
</tr>
<tr>
<td>-splash:imagepath </td>
<td>显示启动画面</td>
</tr>
<tr>
<td>-verbose:class<br />-verbose:gc<br />-verbose:jni</td>
<td>显示类加载信息<br />显示垃圾回收信息<br />显示JNI接口活动信息 </td>
</tr>
<tr>
<td>-version<br />-showversion</td>
<td>
<p>显示版本信息并退出<br />显示版本信息并继续</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">非标准参数</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">参数 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>-X</td>
<td>显示非标准参数并退出</td>
</tr>
<tr>
<td>-Xint</td>
<td>以解释模式运行，字节码不会编译成native代码</td>
</tr>
<tr>
<td>-Xbatch</td>
<td>禁止后台编译，默认情况下，后台编译字节码，编译完成前，解释执行，禁止后，必须编译完毕后才能执行方法</td>
</tr>
<tr>
<td>-Xbootclasspath:bootclasspath</td>
<td>设置boot class的寻找路径（JAR、ZIP或者目录）</td>
</tr>
<tr>
<td>-Xbootclasspath/a:path</td>
<td>附加boot class的寻找路径</td>
</tr>
<tr>
<td>-Xbootclasspath/p:path</td>
<td>前缀boot class的寻找路径</td>
</tr>
<tr>
<td>-Xcheck:jni</td>
<td>检查JNI函数的调用，比如验证参数有效性，如果失败，则退出JVM </td>
</tr>
<tr>
<td>-Xfuture</td>
<td>进行严格的字节码格式检查</td>
</tr>
<tr>
<td>-Xnoclassgc</td>
<td>禁止类的垃圾回收</td>
</tr>
<tr>
<td>-Xincgc</td>
<td>启用增量的垃圾回收，默认禁用，减少垃圾回收导致的长时间系统停顿，但是会时时工作，影响程序效率</td>
</tr>
<tr>
<td>-Xloggc:file</td>
<td>记录垃圾回收的每个事件</td>
</tr>
<tr>
<td>-XX:AllocationPrefetchStyle=n</td>
<td>设置内存分配预读取风格，默认2</td>
</tr>
<tr>
<td style="width: 250px;">-XX:+|-DisableAttachMechanism</td>
<td>设置类似jmap、jconsole之类的命令是否可以附到（attach）到运行中的JVM，默认这个特性是禁用的</td>
</tr>
<tr>
<td>-XX:+UnlockCommercialFeatures</td>
<td>解锁商业特性</td>
</tr>
<tr>
<td>-XX:+|-FlightRecorder</td>
<td>商业特性。启用黑匣子功能</td>
</tr>
<tr>
<td>
<p>-XX:FlightRecorderOptions<br />=p=v,p1=v1...</p>
</td>
<td>
<p>配置黑匣子的选项，包括：</p>
<p>defaultrecording=true|false 黑匣子是持续运行，还是运行限定的时间，默认false<br />disk=true|false 黑匣子是否持续的写入磁盘，如果为true，defaultrecording也应该为true<br />dumponexit=true|false 如果JVM退出，是否生成黑匣子的dump文件<br />dumponexitpath=path 黑匣子dump文件的路径，同名文件被覆盖，如果指定目录，则依据时间作为文件名（7u41版本在Windows下不支持指定目录，可能是BUG）<br />globalbuffersize=size 数据存留占有主内存的最大空间，默认10M<br />loglevel=quiet|terror|warning|info|debug|trace 日志记录级别，默认info<br />maxage=time 磁盘数据存活最大时间，默认15分钟，只有disk=true才有意义<br />maxchunksize=size 数据块的最大尺寸，默认12M<br />maxsize=size 磁盘数据的最大尺寸，默认无限制<br />repository=path 临时磁盘目录的位置<br />samplethreads=true|false 是否启用线程样本，默认启用<br />settings=path 默认JAVA_HOME/jre/lib/jfr/default.jfs，包含事件的设置<br />stackdepth=depth 堆栈跟踪的最大深度<br />threadbuffersize=size 默认5kb，线程的本地缓冲大小</p>
</td>
</tr>
<tr>
<td>
<p>-XX:StartFlightRecording<br />=p=v,p1=v1...</p>
</td>
<td>
<p>启动黑匣子</p>
<p>compress=true|false 是否压缩黑匣子日志文件<br />defaultrecording=true|false<br />delay=time JVM启动后，延迟黑匣子记录的毫秒数<br />duration=time 黑匣子记录的持续时间，默认无限<br />filename=path 日志文件路径<br />name=identifier 本次记录的标识符<br />maxage=time<br />maxsize=size<br />settings=path</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常用-XX选项</span></div>
<div class="blog_h2"><span class="graybg">行为选项</span></div>
<table style="width: 100%;" border="1" width="100%" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 30%; text-align: center;" width="32%">
<p><strong>参数与默认值</strong></p>
</td>
<td style="width: 70%; text-align: center;" width="67%">
<p><strong>说明</strong></p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:-AllowUserSignalHandlers</p>
</td>
<td width="67%">
<p>允许用户定义的信号处理器（Solaris/Linux）</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:AltStackSize=16384</p>
</td>
<td width="67%">
<p>备选信号栈大小（Solaris，5.0-）</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:+FailOverToOldVerifier</p>
</td>
<td width="67%">
<p>如果验证失败，使用旧版本的验证启（6.0+）</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:PreBlockSpin=10</p>
</td>
<td width="67%">
<p> -XX:+UseSpinning使用的自旋计数器，进入系统线程同步代码前，自旋的次数 (1.4.2+)</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:-RelaxAccessControlCheck</p>
</td>
<td width="67%">
<p>放松验证器的访问控制检查 ( 6.0+)</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:-UseSpinning</p>
</td>
<td width="67%">
<p>进入系统同步化代码前，在Java监视器上启用native自旋( 1.4.2 and 5.0 )</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:+UseThreadPriorities</p>
</td>
<td width="67%">使用系统native的线程优先级</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">性能调优选项</span></div>
<table style="width: 100%;" border="1" width="100%" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 30%; text-align: center;" width="45%">
<p><strong>参数与默认值</strong></p>
</td>
<td style="text-align: center;" width="55%">
<p><strong>说明</strong></p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XssN</strong></span></p>
</td>
<td>设置线程栈大小。JDK5.0+为1M，以前为256K。减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数限制在3000~5000左右</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-</strong><strong>XmsN</strong></span></td>
<td>设置初始内存大小（字节），必须是1024的倍数、大于1MB，可以后缀k/K、m/M、g/G</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-XmxN </strong></span></td>
<td>设置最大内存大小（字节），必须是1024的倍数、大于2MB，可以后缀k/K、m/M、g/G</td>
</tr>
<tr>
<td><span style="color: #000000;">-XX:PermSize</span></td>
<td>设置永久代初始占用内存大小</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:MaxPermSize=64m</strong></span></td>
<td>
<p>设置永久代最大占用内存大小</p>
</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-XmnN</strong></span></td>
<td>年轻代的内存大小（Eden区+2个Survivor区）</td>
</tr>
<tr>
<td>-XX:NewRatio=2</td>
<td>年老代/年轻代占用空间比率</td>
</tr>
<tr>
<td>-XX:SurvivorRatio=8</td>
<td>年轻代Eden/Survivor区域尺寸比例，默认8:1。通常有2个Survivor区，因此8:1意味着Survivor总大小为2</td>
</tr>
<tr>
<td>-XX:PretenureSizeThreshold</td>
<td>用于ParNew、Serial。大于此值（字节）的对象将在年老代直接分配，避免在Eden区及Survivor区之间发生大量的内存拷贝</td>
</tr>
<tr>
<td>-XX:MaxTenuringThreshold</td>
<td>
<p>设置对象进入年老代前，在Survivor区复制（活过Minor GC）的最大次数，默认情况下，JVM会动态调整TenuringThreshold值。<br />如果设置为0的话，则年轻代对象不经过Survivor区，直接进入年老代，对于年老代比较多的应用，可以提高效率<br />将此值设置为一个较大值，则年轻代对象会在Survivor区进行多次复制，这样可以增加对象在年轻代的存活时间</p>
<p>此值设置过大，则ParNew分析Survivor区对象关系时可能消耗时间过大，影响性能。</p>
<p>如果Survivor满了，对象会强制复制到年老代</p>
</td>
</tr>
<tr>
<td>-XX:TargetSurvivorRatio=50</td>
<td>
<p>JVM会试图调整TenuringThreshold期望下次Minor GC后Survivor的From区的使用量能接近50</p>
<p>如果设置为100，相当于禁止调整TenuringThreshold</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+UseTLAB</strong></span></p>
</td>
<td>
<p>启用线程本地分配缓冲（Thread local allocation buffers）。启用TLAB后，只有TLAB被用满了，才会尝试到Eden区域申请内存</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:TLABSize=n</strong></span></p>
</td>
<td>
<p>TLAB的大小</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+ResizeTLAB</p>
</td>
<td>
<p>是否可以动态修改TLAB大小</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:MaxGCPauseMillis=n</strong></span></p>
</td>
<td>
<p>设置GC暂停时间目标。如果无法达到要求，JVM会自动调整年轻代大小</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:GCTimeRatio=n</strong></span></p>
</td>
<td>
<p>控制吞吐量，设置垃圾回收时间占程序运行时间的百分比，公式为1/(1+n)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseSerialGC</p>
</td>
<td>
<p>使用串行垃圾回收 (5.0+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseG1GC</p>
</td>
<td>
<p>启用G1垃圾回收器</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:<strong>+</strong>UseParallelGC</strong></span></p>
</td>
<td>
<p><span style="color: #000000;">吞吐量收集器。Parallel scavenge collector，对年轻代使用并行垃圾回收(1.4.1+)，<span style="background-color: #c0c0c0;">不能与CMS收集器同时使用</span>。算法针对多CPU大内存（例如10G+）进行了优化，致力于在尽量减少停顿的同时最大化吞吐量</span></p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+UseParallelOldGC</strong></span></p>
</td>
<td>
<p>吞吐量收集器。在使用-XX:+UseParallelGC时，对年老代使用并行垃圾回收(5.0u6)</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+UseParNewGC</strong></span></p>
</td>
<td>
<p>Parallel copying collector，对年轻代使用并行垃圾回收，可与CMS收集器（-XX:+UseConcMarkSweepGC）同时使用。JDK5.0以上，JVM会根据系统配置自行设置。工作方式类似于原始的复制算法，但是使用多个线程进行并行的拷贝</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:ParallelGCThreads=n</strong></span></p>
</td>
<td>
<p>并行垃圾收集使用的线程数，默认与处理器核心数相等，如果CPU很多，可以减小此值</p>
</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong> -XX:+UseAdaptiveSizePolicy</strong></span></td>
<td>
<p>设置此选项后，并行收集器（Parallel Scavenge）会自动选择年轻代区大小和相应的Survivor区比例，以达到目标系统规定的最低响应时间或者收集频率等，建议使用并行收集器时启用。使用-XX:+PrintAdaptiveSizePolicy跟踪动态变化</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+UseConcMarkSweepGC</strong></span></p>
</td>
<td>
<p>低停顿收集器。对年老代使用CMS垃圾回收器，同时保证应用的运行只有很短暂的停顿(1.4.1+)</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+CMSConcurrentMTEnabled</strong></span></p>
</td>
<td>
<p>CMS并发阶段（concurrent CMS phases）是否使用多线程（使用ParNewGC的情况下）。默认已经启用，设置-XX:-CMSConcurrentMTEnabled可以禁用</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+CMSParallelRemarkEnabled</strong></span></p>
</td>
<td>在CMS重标记阶段，使用多线程来降低停顿时间。默认false。不一定能达到效果，需要进行基准测试（benchmark）来确定是否开启</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:CMSScheduleRemarkEdenSizeThreshold</strong></span></p>
</td>
<td>在预清理后，启动可中断预清理阶段的Eden占用最小值，默认2MB</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:CMSScheduleRemarkEdenPenetration</strong></span></p>
</td>
<td>
<p>当新生代存活对象占Eden的比例超过多少时，终止preclean阶段并进入remark阶段，默认50</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:CMSMaxAbortablePrecleanTime</strong></span></p>
</td>
<td>
<p>设置CMS可中断预清理（preclean）阶段最大时间，单位毫秒，超过了强制进入重标记阶段</p>
</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong> -XX:+CMSScavengeBeforeRemark</strong></span></td>
<td>
<p>在CMS的重标记阶段开始前，进行YGC清理，减少重标记阶段的耗时</p>
</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:ConcGCThreads=n</strong></span></td>
<td>CMS垃圾收集（所有阶段）使用的线程数，提高此值可能改善垃圾回收性能，但是带来额外的同步成本（synchronization overhead）。默认的，此值不会明确设置，JVM根据-XX:ParallelGCThread来自动设置，公式为：ConcGCThreads = (ParallelGCThreads + 3)/4</td>
</tr>
<tr>
<td>-XX:+UseCMSInitiatingOccupancyOnly</td>
<td>禁止HostSpot自行触发CMS GC，只有在堆百分比达到阈值时才触发，配合下一条使用</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:CMSInitiatingOccupancyFraction</strong></span></td>
<td>设置还剩余多少堆（百分比）时进行CMS收集，默认68%。如果年老代增长缓慢，可以增加此值。</td>
</tr>
<tr>
<td>
<p>-XX:CMSInitiatingPermOccupancyFraction</p>
</td>
<td>
<p>设置还剩余多少永久带（百分比）时进行CMS收集</p>
</td>
</tr>
<tr>
<td>
<p>-XX:CMSFullGCsBeforeCompaction=5</p>
</td>
<td>
<p>由于CMS不对内存空间进行整理，此值设置运行多少次Full GC以后对内存空间进行整理。设置此参数可能导致promotion failure</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+UseCMSCompactAtFullCollection</strong></span></p>
</td>
<td>Full GC后对年老代进行整理。可能会影响性能，但是可以消除碎片。默认true</td>
</tr>
<tr>
<td>
<p>-XX:+CMSIncrementalMode</p>
</td>
<td>
<p>CMS增量模式，适用于单CPU情况</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+CMSClassUnloadingEnabled</p>
</td>
<td>
<p>启用CMS类卸载功能</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+ScavengeBeforeFullGC</p>
</td>
<td>
<p>Full GC前清理年轻代( 1.4.1+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+|-UseLargePages</p>
</td>
<td>
<p>启用大内存分页支持</p>
</td>
</tr>
<tr>
<td>
<p>-XXLargePageSizeInBytes=n</p>
</td>
<td>
<p>设置大内存分页的最大尺寸</p>
</td>
</tr>
<tr>
<td>
<p>-XX:MaxHeapFreeRatio=70</p>
</td>
<td>
<p>GC后，堆的最大空余百分比</p>
</td>
</tr>
<tr>
<td>
<p>-XX:MinHeapFreeRatio=40</p>
</td>
<td>
<p>GC后，堆的最小空余百分比</p>
</td>
</tr>
<tr>
<td>
<p>-XX:ReservedCodeCacheSize=32m</p>
</td>
<td>最大代码缓存大小</td>
</tr>
<tr>
<td>
<p>-XX:CompileThreshold=10000</p>
</td>
<td>进行编译前，方法被执行的次数[-client: 1,500]</td>
</tr>
<tr>
<td>
<p>-XX:ThreadStackSize=512</p>
</td>
<td>
<p>线程堆栈大小</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseFastAccessorMethods</p>
</td>
<td>
<p>使用优化版的Get方法</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseLargePages</p>
</td>
<td>
<p>使用大页（large page）内存</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseMPSS</p>
</td>
<td>
<p>使用多内存页大小支持</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseStringCache</p>
</td>
<td>
<p>缓存普通分配的字符串 </p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseCompressedStrings</p>
</td>
<td>
<p>压缩字符串 (6u21+) </p>
</td>
</tr>
<tr>
<td>
<p>-XX:+OptimizeStringConcat</p>
</td>
<td>
<p>优化字符串连接(6u21+) </p>
</td>
</tr>
<tr>
<td>
<p>-XX:MaxDirectMemorySize</p>
</td>
<td>
<p>NIO中通过Direct内存来提高性能，这个区域的大小默认是64M，在适当的场景可以设置大一些</p>
</td>
</tr>
<tr>
<td>
<p>-Xrs</p>
</td>
<td>
<p>减少JVM对系统信号(Signals)使用</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+AggressiveOpts</p>
</td>
<td>
<p>启用在未来版本可能作为默认开启的优化选项(5.0u6)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:SoftRefLRUPolicyMSPerMB=0</p>
</td>
<td>
<p>启用积极的软引用处理，如果软引用对垃圾回收有影响了，可以使用。默认值为每兆1000毫秒。即对于堆中每MB大小的可用空间而言，一个软引用有1秒的存活时间（在最后一个强引用的对象被回收后）</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+DisableExplicitGC</p>
</td>
<td>
<p>禁止System.gc()的调用</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseGCOverheadLimit</p>
</td>
<td>
<p>抛出OutOfMemory前，允许的GC占用虚拟机时间的最大比例(6.0+)</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">G1垃圾回收器相关选项</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 35%; text-align: center;"> 参数</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>-XX:+UseG1GC</td>
<td>开启G1垃圾回收器</td>
</tr>
<tr>
<td>-XX:InitiatingHeapOccupancyPercent=n</td>
<td>堆占用了多少时触发GC，默认为45</td>
</tr>
<tr>
<td>-XX:MaxTenuringThreshold</td>
<td>默认15</td>
</tr>
<tr>
<td>-XX:MaxGCPauseMillis=n</td>
<td> </td>
</tr>
<tr>
<td>-XX:NewRatio=n</td>
<td>默认2</td>
</tr>
<tr>
<td>-XX:SurvivorRatio=n</td>
<td>默认8</td>
</tr>
<tr>
<td>-XX:ParallelGCThreads=n</td>
<td>并行GC的线程数</td>
</tr>
<tr>
<td>-XX:ConcGCThreads=n</td>
<td>并发GC使用的线程数</td>
</tr>
<tr>
<td>-XX:G1ReservePercent=n</td>
<td>设置作为空闲空间的预留内存百分比，以降低目标空间溢出的风险。默认值是 10%</td>
</tr>
<tr>
<td>-XX:G1HeapRegionSize=n</td>
<td>设置G1区域的大小。值是2的幂，范围是1 MB 到32 MB</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">调试选项</span></div>
<table style="width: 100%;" border="1" width="100%" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 30%; text-align: center;" width="45%">
<p><strong>参数与默认值</strong></p>
</td>
<td style="text-align: center;" width="55%">
<p><strong>说明</strong></p>
</td>
</tr>
<tr>
<td>
<p>-XX:-CITime</p>
</td>
<td>
<p>打印JIT编译器消耗的时间(1.4.0+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:ErrorFile=./hs_err_pid%p.log</p>
</td>
<td>
<p>如果JVM错误发生，将错误保存到指定位置(6.0+)</p>
<p>例如 -XX:ErrorFile=F:\Temp\hs_err_pid%p.log（注意，在Windows的BAT脚本中%要换为%%）</p>
</td>
</tr>
<tr>
<td>-XX:+HeapDumpOnOutOfMemoryError</td>
<td>
<p>java.lang.OutOfMemoryError发生时，是否Dump堆镜像(1.4.2u12+, 5u7+)</p>
</td>
</tr>
<tr>
<td>-XX:+HeapDumpBeforeFullGC</td>
<td>
<p>在FGC前Dump出堆内存</p>
</td>
</tr>
<tr>
<td>-XX:+HeapDumpAfterFullGC</td>
<td>在FGC后Dump出堆内存</td>
</tr>
<tr>
<td>
<p>-XX:HeapDumpPath=/dir/</p>
</td>
<td>
<p>堆Dump文件的存储位置(1.4.2u12+, 5u7+)</p>
<p>例如 -XX:HeapDumpPath=F:\Temp\</p>
</td>
</tr>
<tr>
<td>
<p>-XX:OnError="CMD"</p>
</td>
<td>
<p>出现致命错误时，运行用户定义的命令(1.4.2u9+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:OnOutOfMemoryError="CMD"</p>
</td>
<td>
<p>出现内存溢出时，运行用户定义的命令(1.4.2u12+,6.0)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-PrintCommandLineFlags</p>
</td>
<td>
<p>打印附加的命令行标记 (5.0+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-PrintCompilation</p>
</td>
<td>当方法被编译时，打印信息</td>
</tr>
<tr>
<td>
<p>-XX:+PrintTLAB</p>
</td>
<td>打印TLAB分配的情况</td>
</tr>
<tr>
<td>
<p>-XX:-PrintGC</p>
</td>
<td>
<p>当GC活动时，打印信息</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-PrintGCDetails</p>
</td>
<td>
<p>打印GC详细信息 (1.4.0+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+PrintGCDateStamps</p>
</td>
<td>
<p>打印GC时间，形式：2014-11-17T16:05:24.673+0800</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-PrintGCTimeStamps</p>
</td>
<td>
<p>打印GC时间戳（相对时间），形式：0.104</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-TraceClassLoading</p>
</td>
<td>跟踪类加载</td>
</tr>
<tr>
<td>
<p>-XX:-TraceClassResolution</p>
</td>
<td>
<p>跟踪常量池解析 (1.4.2+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-TraceClassUnloading</p>
</td>
<td>跟踪类的卸载</td>
</tr>
<tr>
<td>
<p>-XX:+PerfDataSaveToFile</p>
</td>
<td>退出时保存二进制的JVM统计信息到文件</td>
</tr>
<tr>
<td>
<p>-XX:+UseCompressedOops</p>
</td>
<td>
<p>使用压缩指针</p>
</td>
</tr>
<tr>
<td>
<p>-XX:InlineSmallCode=n</p>
</td>
<td>
<p>内联之前编译好的方法，如果其大小小于n</p>
</td>
</tr>
<tr>
<td>
<p>-XX:MaxInlineSize=35</p>
</td>
<td>
<p>内联方法最大字节码大小</p>
</td>
</tr>
<tr>
<td>
<p>-XX:FreqInlineSize=n</p>
</td>
<td>
<p>频繁使用的方法内联的最大字节码大小</p>
</td>
</tr>
<tr>
<td>
<p>-Xloggc</p>
</td>
<td>
<p>记录GC冗长信息到文件（不指定则记录到stdout）</p>
<pre class="crayon-plain-tag">-XX:+PrintFlagsFinal
-XX:PrintFLSStatistics=1
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:-TraceClassUnloading
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintTenuringDistribution
-Xloggc:%LOG_DIR%\gc.log</pre>
</td>
</tr>
<tr>
<td>
<p>-XX:-UseGCLogFileRotation
</td>
<td>记录GC冗长信息到文件时，启用文件轮换</td>
</tr>
<tr>
<td>
<p>-XX:NumberOfGClogFiles=1</p>
</td>
<td>上述文件轮换时，文件的个数</td>
</tr>
<tr>
<td>
<p>-XX:GCLogFileSize=8K</p>
</td>
<td>
<p>GC冗长日志的大小</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+PrintGCApplicationConcurrentTime</p>
</td>
<td>
<p>打印GC程序并发时间</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+PrintGCApplicationStoppedTime</p>
</td>
<td>
<p>打印GC停顿时间</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+PrintTenuringDistribution</p>
</td>
<td>
<p>显示在Survivor区域里面有效的对象的年龄，ParNewGC分析此区域老对象的关系较为耗时</p>
</td>
</tr>
<tr>
<td>
<p>-XX:PrintFLSStatistics=N</p>
</td>
<td>
<p>打印FreeListSpace的统计信息，可以得到内存碎片的信息</p>
</td>
</tr>
<tr>
<td>
<p>-Xprof</p>
</td>
<td>
<p>剖析系统性能并输出到stdout </p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">JVM性能调优</span></div>
<div class="blog_h2"><span class="graybg">JVM内存与GC设置</span></div>
<div class="blog_h3"><span class="graybg">年轻代内存大小的选择</span></div>
<ol>
<li>响应时间优先：尽量大，直到<span style="background-color: #c0c0c0;">接近最低响应时间的需求</span>限制</li>
<li>吞吐量优先：尽量大，最好到达Gbits</li>
</ol>
<div class="blog_h3"><span class="graybg">年老代内存大小的选择</span></div>
<ol>
<li>响应时间优先：年老代默认CMS收集，需要考虑<span style="background-color: #c0c0c0;">会话并发数、持续时间</span>等因素，太小，造成碎片、高频回收、暂停并导致标记清除；太大，导致收集时间长。根据：CMS收集信息、次数、年轻代/年老代回收时间比率确定</li>
<li>吞吐量优先：一般都是<span style="background-color: #c0c0c0;">较大的年轻代、较小的年老代</span>。由于并发大，年轻代对象多，所以要求尽快收集年轻代对象，年老代存放长期存活对象</li>
<li>小堆引起的内存碎片问题：CMS算法不会对堆进行整理，导致碎片问题，配置 -XX:+UseCMSCompactAtFullCollection、-XX:CMSFullGCsBeforeCompaction来启用整理</li>
</ol>
<div class="blog_h3"><span class="graybg">使用G1垃圾回收器</span></div>
<p>7u9以后的版本，可以使用G1回收器，这是一个新型、综合的垃圾回收器。</p>
<p>可以使用类似如下方式启用：java -Xmx2G -Xms2G  -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1ReservePercent=20  -XX:ConcGCThreads=8</p>
<div class="blog_h3"><span class="graybg">关于64bit虚拟机</span></div>
<p>一般认为64bit虚拟机的性能低于32位，所有某些时候32bit虚拟机集群比一个大的64bit虚拟机更合适。</p>
<p>如果JVM内存配置很大，DUMP内存映像将是非常耗时的，这和磁盘写入速度有关系</p>
<p>对于年轻代来说，非常大的内存空间不会对性能造成显著影响；而对于年老代，除非使用CMS垃圾回收器（默认是吞吐量优先的Parallel Old），否则可能导致很长的GC停顿（30G以内的内存可能数十秒 ，更高的内存可能数分钟），这对于用户交互式程序是无法忍受的。</p>
<p>CMS对于大概小于4G的堆来说，其初始标记、重标记STW时间较短，对于频繁制造垃圾的大堆，则亦可能出现问题，当CMS的工作跟不上年老代被占满的步伐时，会导致STW的串行垃圾回收器被启用，在16G内存下，这可能导致30秒或者更多的停顿，尽早（年老代被占用百分比）启用CMS，可能在一定程度上减少STW出现几率。注意，越是多核心的处理器，越可能导致CMS停顿的问题，因为CMS只会使用一个核心。</p>
<p>G1垃圾回收器不能解决CMS的问题，它的吞吐量甚至低于CMS。</p>
<div class="blog_h3"><span class="graybg">一些最佳实践</span></div>
<ol>
<li>对于G1垃圾回收器：避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小，因为固定年轻代的大小会覆盖暂停时间目标</li>
<li>让堆的最小、最大尺寸一致</li>
<li>为新生代提供足够的空间，使大部分对象停留在新生代，同时，要避免老年代触发垃圾收集（保持有10-20%的空闲空间）</li>
<li>让新生代对象生命周期尽量短，避免使其进入年老代</li>
<li>避免短命的大对象，-XX:PretenureSizeThreshold设置直接进入年老代的阈值</li>
<li>将需要大量内存的模块使用C++等手工管理内存的语言实现</li>
<li>如果有海量内存，可以使用具有低延迟、无停顿GC的虚拟机，例如Azul，它的C4回收器具有这样的特征</li>
</ol>
<div class="blog_h2"><span class="graybg">GC日志分析</span></div>
<div class="blog_h3"><span class="graybg">ParNew+CMS日志分析</span></div>
<p>CMS相关垃圾回收的可能原因有：</p>
<ol>
<li>System.gc() 的调用</li>
<li>老年代占比超过-XX:CMSInitiatingOccupancyFraction</li>
<li>如果启用-XX:+CMSClassUnloadingEnabled，当永久代占比超过-XX:CMSInitiatingPermOccupancyFraction</li>
<li>晋升失败（Promotion Failed）：YGC时，Survivor区放不下所有对象，而且老年代也放不下，原因不一定是老年代空间不足，也可能是<span style="background-color: #c0c0c0;">老年代碎片引起</span></li>
<li>并发模式失败（Concurrent Mode Failure ）：CMS的同时进行YGC，导致CMS完成前老年代空间不足（Full Promotion Guarantee Failure），此情况下会发生Full GC。可能需要调小新生代或者减小-XX:CMSInitiatingOccupancyFraction</li>
</ol>
<pre class="crayon-plain-tag">'前缀时间戳，例如221810.102，表示JVM已经启动的秒数'
'时间times：user表示对于当前进程CPU消耗在user-mode的时间；sys表示对于当前进程CPU消耗在kernel-mode的时间；real表示物理时间的时间；'
'ParNew的年轻代垃圾收集（YGC），20024K为GC开始时的内存占用，497K为GC结束后的内存占用，39296K为总大小，0.0046067 为新生代局部收集消耗时间'
'后面的20024K-&gt;9715K(126720K)表示整个堆收集前后内存占用和总内存，最后的Times显示GC过程中的总时间消耗'
0.102: [GC0.102: [ParNew: 20024K-&gt;497K(39296K), 0.0046067 secs] 20024K-&gt;9715K(126720K), 0.0046827 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
0.172: [GC0.172: [ParNew: 19990K-&gt;599K(39296K), 0.0009549 secs]

'初始标记：STW，单线程（可以从user和real几乎相等看出），只标记ROOT能直接可达的对象，一般应该很快'
'786428K表示年老代已占用空间；后面的786432K表示年老代总空间'
221810.417: [GC [1 CMS-initial-mark: 786428K(786432K)] 4527562K(5740992K), 3.1298768 secs] [Times: user=3.14 sys=0.00, real=3.16 secs] 
221813.547: [CMS-concurrent-mark-start]
'并发标记阶段，不会STW'
221814.218: [CMS-concurrent-mark: 0.670/0.671 secs] [Times: user=2.46 sys=0.00, real=0.64 secs] 
'预清理，不会STW。该阶段检查并发标记阶段时从新生代晋升的对象、新分配的对象、被应用程序线程更新过的对象，减少重新标记阶段的暂停时间'
221814.218: [CMS-concurrent-preclean-start]
221814.223: [CMS-concurrent-preclean: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
'如果Eden占用量大于CMSScheduleRemarkEdenSizeThreshold（默认2M），会启动可中断预清理（不会STW）'
221814.223: [CMS-concurrent-abortable-preclean-start]
221814.223: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
'预清理阶段只是一个取样过程，它将新生代按一定间隔进行分块，标记起始位置，以便remark时可以并行的的对块进行Trace'
'预清理阶段最好发生一次YGC，-XX:CMSScavengeBeforeRemark可以强制重标记前发生YGC'
    221814.224: [GC[YG occupancy: 3774483 K (4954560 K)]
    221814.224: [Rescan (parallel) , 4.1513281 secs]
    221818.375: [weak refs processing, 0.0000280 secs]
    221818.375: [scrub string table, 0.0014394 secs] 
        '重新标记，STW，多线程，默认通过-XX:ParallelGCThreads计算得到线程数，此阶段通常是CMS中暂停时间最长的'
        [1 CMS-remark: 786428K(786432K)] 4560911K(5740992K), 4.1529812 secs] [Times: user=28.08 sys=0.00, real=4.17 secs] 

'并发清理，不会STW'
221818.377: [CMS-concurrent-sweep-start]
CMS: Large Block: 0x00000007f8000000; Proximity: 0x00000007f7851ea0 -&gt; 0x00000007f7851ea0
CMS: Large block 0x0000000000000000
221818.736: [CMS-concurrent-sweep: 0.358/0.358 secs] [Times: user=0.87 sys=0.00, real=0.36 secs] 
'重置，CMS数据结构重新初始化，为下一次CMS做准备'
221818.736: [CMS-concurrent-reset-start]
221818.738: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

'紧跟着一次并发模式失败的Full GC'
221890.743: [GC221890.743: [ParNew: 18434K-&gt;18434K(39296K), 0.0000233 secs]90.743: [CMS90.743: [CMS-concurrent-abortable-preclean: 0.000/0.028 secs] [Times: user=0.02 sys=0.00, real=0.03 secs] 
 (concurrent mode failure): 83535K-&gt;37453K(87424K), 0.0111540 secs] 101969K-&gt;37453K(126720K), [CMS Perm : 3284K-&gt;3284K(65536K)], 0.0112398 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
221890.754: [Full GC221890.754: [CMS: 37453K-&gt;37453K(87424K), 0.0051618 secs] 37453K-&gt;37453K(126720K), [CMS Perm : 3284K-&gt;3284K(65536K)], 0.0051982 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]</pre>
<div class="blog_h3"><span class="graybg">Survivor区对象年龄分布分析</span></div>
<p>开启：-XX:+PrintTenuringDistribution可以打印分布信息</p>
<pre class="crayon-plain-tag">'期望的Survivor区占用由-XX:TargetSurvivorRatio=50指定'
'threshold 即晋升的年龄限制，可由-XX:MaxTenuringThreshold指定'
Desired survivor size 2228224 bytes, new threshold 6 (max 6)
- age   1:        232 bytes,        232 total
- age   2:     478840 bytes,     479072 total '各年龄对象的字节数、数量'</pre>
<div class="blog_h3"><span class="graybg"> 停顿时间分析</span></div>
<p>开启-XX:+PrintGCApplicationConcurrentTime、-XX:+PrintGCApplicationStoppedTime可以打印并发时间、停顿时间</p>
<pre class="crayon-plain-tag">'应用和GC并发执行的时间'
Application time: 0.0362382 seconds
'应用被GC停止的时间'
Total time for which application threads were stopped: 0.0039536 seconds</pre>
<div class="blog_h3"><span class="graybg">在启动时打印所有GC标记</span></div>
<p>开启 -XX:+PrintFlagsFinal可以打印所有GC标记</p>
<pre class="crayon-plain-tag">[Global flags]
    uintx AdaptivePermSizeWeight                    = 20              {product}           
    uintx AdaptiveSizeDecrementScaleFactor          = 4               {product}           
    uintx AdaptiveSizeMajorGCDecayTimeScale         = 10              {product}           
    ……</pre>
<div class="blog_h3"><span class="graybg">分析内存碎片信息</span></div>
<p>开启-XX:PrintFLSStatistics=1可以统计内存碎片</p>
<pre class="crayon-plain-tag">Before GC:
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 11190272
Max   Chunk Size: 11190272
Number of Blocks: 1
Av.  Block  Size: 11190272
Tree      Height: 1

After GC:
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 9993976
Max   Chunk Size: 9993976
Number of Blocks: 1
Av.  Block  Size: 9993976
Tree      Height: 1</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/jvm-options-and-performance-tuning">JVM参数与性能调优</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/jvm-options-and-performance-tuning/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JVM学习笔记</title>
		<link>https://blog.gmem.cc/jvm-study-note</link>
		<comments>https://blog.gmem.cc/jvm-study-note#comments</comments>
		<pubDate>Tue, 12 Jun 2012 04:00:36 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Command]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=2805</guid>
		<description><![CDATA[<p>JVM内存管理 JVM内存区域 根据Java虚拟机规范，在运行时，JVM管理的内存分为以下区域： 其中：方法区和堆被所有线程共享，其它三个区域则是线程私有，这些区域的功能如下：  区域 说明  程序计数器 当前线程所执行的字节码的行号指示器，在虚拟机的槪念模型里，字节码解释器工作时就是通过改变此计数器的值来选取下一条需要执行的字节码指令，分支、循环、跳转、异常处理、线程恢复等基础功能都依赖于此计数器。 如果当前正在执行的是一个java方法，这个计数器记录的是正在执行的虚拟机字节码指令的地址；如果正在执行本地方法则为空 JVM栈 此栈的生命周期与线程相同。每个方法执行的时候都会同时创建一个栈帧（Stack Frame），用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程，就对应着一个栈帧在虚拟机栈中从入栈到出栈过程。这一点和C语言是类似的，栈帧对应着一个未执行完毕的函数。 局部变量表：存放了编译期可知的基本类型、对象引用（reference类型，它不等同于对象本身，根据不同的虚拟机实现，它可能是一个指向对象起始地址的引用指针，也可能指向一个代表对象的句柄或 者其他与此对象相关的位置）和retumAddress类型（指向了一条字节码指令的地址)。其中64位长度的long和double类型的数据会占用2个局部变量空间（Slot），其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配，当进入一个方法时，这个方法需要在帧中分配多大的局部变量空间是完全确定的，在方法运行期间不会改变局部变量表的大小。 本地方法栈 类似于JVM栈，但是其是用来执行本地方法的。Hotspot虚拟机把JVM栈和本地方法栈合二为一实现。 Java堆 在虚拟机启动时创建，不需要连续的内存区域。此内存区域用于存放对象实例。根据JVM规范的描述，所有对象、数组都要在堆上分配。 Java堆是垃圾回收的主要管理区域。根据GC算法的不同，可能分为：新生代（Eden、From Surivor、To Surivor）和老年代。 Java堆可能划分出线程私有的分配缓冲区（TLAB） <a class="read-more" href="https://blog.gmem.cc/jvm-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/jvm-study-note">JVM学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">JVM内存管理</span></div>
<div class="blog_h2"><span class="graybg">JVM内存区域</span></div>
<p>根据Java虚拟机规范，在运行时，JVM管理的内存分为以下区域：</p>
<p><img class="size-full wp-image-2808 aligncenter" style="width: 70%;" src="https://blog.gmem.cc/wp-content/uploads/2012/06/jvm-1.png" alt="jvm-1" /></p>
<p>其中：方法区和堆被所有线程共享，其它三个区域则是线程私有，这些区域的功能如下：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 区域</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>程序计数器</td>
<td>
<p>当前线程所执行的字节码的行号指示器，在虚拟机的槪念模型里，字节码解释器工作时就是通过改变此计数器的值来选取下一条需要执行的字节码指令，<span style="background-color: #c0c0c0;">分支、循环、跳转、异常处理、线程恢复</span>等基础功能都依赖于此计数器。</p>
<p>如果当前正在执行的是一个java方法，这个计数器记录的是正在执行的<span style="background-color: #c0c0c0;">虚拟机字节码指令的地址</span>；如果正在执行本地方法则为空</p>
</td>
</tr>
<tr>
<td>JVM栈</td>
<td>
<p>此栈的生命周期与线程相同。每个方法执行的时候都会同时创建一个<span style="background-color: #c0c0c0;">栈帧（Stack Frame）</span>，用于存储<span style="background-color: #c0c0c0;">局部变量表、操作数栈、动态链接、方法出口</span>等信息。每一个方法被调用直至执行完成的过程，就对应着一<span style="background-color: #c0c0c0;">个栈帧在虚拟机栈中从入栈到出栈</span>过程。这一点和C语言是类似的，<span style="background-color: #c0c0c0;">栈帧对应着一个未执行完毕的函数</span>。</p>
<p>局部变量表：存放了编译期可知的<span style="background-color: #c0c0c0;">基本类型、对象引用</span>（reference类型，它不等同于对象本身，根据不同的虚拟机实现，它可能是一个指向对象起始地址的引用指针，也可能指向一个代表对象的句柄或 者其他与此对象相关的位置）和<span style="background-color: #c0c0c0;">retumAddress类型（</span>指向了一条字节码指令的地址)。其中64位长度的long和double类型的数据会占用2个局部变量空间（Slot），其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配，当进入一个方法时，这个方法需要在帧中分配多大的<span style="background-color: #c0c0c0;">局部变量空间是完全确定</span>的，在方法运行期间不会改变局部变量表的大小。</p>
</td>
</tr>
<tr>
<td>本地方法栈</td>
<td>
<p>类似于JVM栈，但是其是用来执行本地方法的。Hotspot虚拟机把JVM栈和本地方法栈合二为一实现。</p>
</td>
</tr>
<tr>
<td>Java堆</td>
<td>
<p>在虚拟机启动时创建，不需要连续的内存区域。此内存区域用于<span style="background-color: #c0c0c0;">存放对象实例</span>。根据JVM规范的描述，所有对象、数组都要在堆上分配。</p>
<p>Java堆是<span style="background-color: #c0c0c0;">垃圾回收的主要管理区域</span>。根据GC算法的不同，可能分为：<span style="background-color: #c0c0c0;">新生代（Eden、From Surivor、To Surivor）和老年代</span>。</p>
<p>Java堆可能划分出<span style="background-color: #c0c0c0;">线程私有的分配缓冲区</span>（TLAB）</p>
</td>
</tr>
<tr>
<td>TLAB</td>
<td>
<p>线程本地分配缓冲（Thread-local allocation buffer）是在<span style="background-color: #c0c0c0;">Eden Space中开辟了一小块线程私有的区域，默认占用Eden的1%空间</span></p>
<p>在Java程序中很多对象都是小对象且用过即丢，它们不存在线程共享也适合被快速GC，对于小对象通常JVM会优先分配在TLAB上，并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高</p>
<p>在TLAB上分配对象时不需要锁住整个堆</p>
<p>对象分配过程：</p>
<ol>
<li>编译器通过逃逸分析，确定对象是在栈上分配还是在堆上分配。如果是在堆上分配，则进入步骤2</li>
<li>如果tlab_top + size &lt;= tlab_end，则在在TLAB上直接分配对象并增加tlab_top 的值，如果现有的TLAB不足以存放当前对象则进入步骤3</li>
<li>重新申请一个TLAB，并再次尝试存放当前对象。如果放不下，则进入步骤4</li>
<li>在Eden区加锁（这个区是多线程共享的），如果eden_top + size &lt;= eden_end则将对象存放在Eden区，增加eden_top 的值，如果Eden区不足以存放，则进入步骤5</li>
<li>执行一次Young GC</li>
<li>经过Young GC之后，如果Eden区任然不足以存放当前对象，则直接分配到老年代</li>
</ol>
</td>
</tr>
<tr>
<td>方法区</td>
<td>
<p>存储已被虚拟机加载的<span style="background-color: #c0c0c0;">类信息、常量、静态变量、即时编译器编译后的代码</span>等数据。尽管虚拟机规范把方法区描述为堆的一个逻辑部分，但是它却有一个别名叫做<span style="background-color: #c0c0c0;">Non-Heap (非堆 ）</span></p>
<p>对于Hotspot来说，常常把方法区称为<span style="background-color: #c0c0c0;">永久代</span></p>
<p><span style="background-color: #c0c0c0;">运行时常量池（Runtime Constant Pool)</span>：存放编译期生成的各种字面量和符号引用，这部分内容将在类加载后存放到方法区的运行时常量池中。注意Java语言不强制常量在编译时产生，所以此池具有动态性</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">直接内存</span></div>
<p>除了上述的运行时区域以外，还有一块内存区域叫直接内存（Direct Memory）。 NIO类库引入了一种基于通道（Channel) 与缓冲区（Buffer)的I/O方式，它可以使用Native函数库<span style="background-color: #c0c0c0;">直接分配堆外内存</span>，然后通过一个存储在Java堆里面的<span style="background-color: #c0c0c0;">DirectByteBuffer</span>对象作为这块内存的引用进行操作，避免在<span style="background-color: #c0c0c0;">Java堆和Native堆</span>中来回复制数据以提高性能。</p>
<p>除了程序计数器以外，其它区域均可能发生OOM异常。</p>
<div class="blog_h3"><span class="graybg">对象访问</span></div>
<p>考虑最简单的代码：Object obj = new Object();</p>
<ol>
<li>Object obj 将映射到Java栈的本地变量表中，作为Reference类型出现</li>
<li>new Object()将反映到Java堆中，形成一块存储Object类型实例数据值（对象各字段数据）的结构化内存</li>
<li>关于Object类的类型信息（Class、父类、接口、方法等）存放于方法区</li>
</ol>
<p>引用类型的两种实现方式：</p>
<ol>
<li>使用句柄：堆中划分单独区域作为句柄池，Reference中存放的为对象句柄地址，而句柄中包含对象实例数据、类型数据各自的具体地址</li>
<li>直接指针：Reference直接存放对象的地址，需要考虑类型数据的布局。Hostspot使用的是这种方式</li>
</ol>
<div class="blog_h3"><span class="graybg">JVM内存溢出错误</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 300px; text-align: center;">内存溢出类型 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>java.lang.OutOfMemoryError: java heap space</td>
<td>
<p>这是最常见的堆内存溢出，即堆对内存空间不足，无法完成对象分配请求。 </p>
<p>出现此问题时，有必要分清内存泄漏（Memory Leak）、内存溢出（Memory overflow），对于前者，要检查泄漏对象到GC Root的路径，对于后者，要考虑增加内存、减少对象生命周期</p>
</td>
</tr>
<tr>
<td>java.lang.OutOfMemoryError:GC over head limit exceeded</td>
<td>垃圾回收器频繁工作，但是GC效果不理想的情况下发生。说明有无法回收的对象</td>
</tr>
<tr>
<td>java.lang.OutOfMemoryError: PermGen space</td>
<td>永久区溢出，通常在系统加载的类非常多、定义了非常多的常量、大量使用动态字节码生成技术导致</td>
</tr>
<tr>
<td>java.lang.OutOfMemoryError: Direct buffer memory</td>
<td>直接或间接使用了ByteBuffer中的allocateDirect方法的时候，而不做clear的时候就会出现类似的问题，设置-XX:MaxDirectMemorySize解决</td>
</tr>
<tr>
<td>java.lang.StackOverflowError</td>
<td>如果线程请求的栈深度大于虚拟机所允许的最大深度则抛出，通常在错误的递归调用中出现。</td>
</tr>
<tr>
<td>java.lang.OutOfMemoryError: unable to create new native thread</td>
<td>
<p>创建了超过系统限制的内存数时会导致</p>
</td>
</tr>
<tr>
<td>java.lang.OutOfMemoryError: requested n bytes for . Out of swap space</td>
<td>一般是由于地址空间不够而造成</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">操作系统对进程的限制</span></div>
<p>OS对于单个进程的内存是有限制的，<span style="background-color: #c0c0c0;">对于32位Windows，进程内存的上限为2GB</span>。</p>
<p><span style="background-color: #c0c0c0;">JVM进程总内存 = Xmx + MaxPermSize+ 程序计数器 + JVM进程本身内存消耗 + JVM栈 + 本地栈</span></p>
<p>JVM能创建的线程数，一方面受到OS对进程最大线程数的限制，一方面受到内存的限制：总可用栈内存/每个栈的大小。</p>
<div class="blog_h2"><span class="graybg">垃圾收集器与内存分配策略</span></div>
<div class="blog_h3"><span class="graybg">引用计数算法</span></div>
<p>给对象中添加一个引用计数器，每 当有一个地方引用它时，计数器值就加1，当引用失效时，计数器值就减1；任何时刻 计数器都为0的对象就是不可能再被使用的。</p>
<p>缺点：很难解决对象之间的相互<span style="background-color: #c0c0c0;">循环引用</span>的问题。</p>
<div class="blog_h3"><span class="graybg">根搜索算法</span></div>
<p>Java语言中具体垃圾回收算法均是<span style="background-color: #c0c0c0;">根搜索算法的变体</span>。通过一系列的名为“GC Roots”的对象作为起始点，从这些点开始向下搜索，搜索所 走过的路径称为引用链（Reference Chain)，当一个对象到GC Roots没有任何引用链相连时，则证明此对象是不可用 的。在Java语言里，GC Roots对象可以是：</p>
<ol>
<li>虚拟机栈（栈帧中的本地变量表）中的引用的对象</li>
<li>方法区中的类静态属性引用的对象</li>
<li>方法区中的常量引用的对象</li>
<li>本地方法栈中的引用的对象</li>
</ol>
<div class="blog_h3"><span class="graybg">常见的算法列表</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">GC算法 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>标记-清除算法</td>
<td>
<p>Mark-Sweep算法是最基础的算法，分为“标记”和“清除”两个阶段：<span style="background-color: #c0c0c0;">首先标记出所有需要回收</span>的对象，在<span style="background-color: #c0c0c0;">标记完成后统一回收</span>掉所有被标记的对象</p>
<p>缺点有两个：</p>
<ol>
<li>标记和清除过程的效率都不高，在标记时必须STW</li>
<li>清除之后会产生大量不连续的内存碎片（因为垃圾是分散的），空间碎片太多可能会导致需要分配较大对象时无法找到足够的<span style="background-color: #c0c0c0;">连续内存</span>而不得不提前触发另一次GC</li>
</ol>
</td>
</tr>
<tr>
<td>复制算法</td>
<td>
<p>复制算法主要解决效率问题，将可用内存按容量<span style="background-color: #c0c0c0;">划分为大小相等的两块，每次只使用其中的一块</span>。当这一块的内存用完了，就将还存活着的对象复制到另外一块上面，然后再把已使用过的内存空间一次清理掉。该算法<span style="background-color: #c0c0c0;">避免了内存碎片</span>，但是具有严重缺点：<span style="background-color: #c0c0c0;">两倍的内存消耗</span></p>
<p>由于巨大的内存浪费，实际都采用复制算法的变体来<span style="background-color: #c0c0c0;">回收新生代</span>。JVM通常将内存分为一块较大的Eden空间和两块较小的Survivor空间，每次使用Eden和其中的 一块Survivor。当回收时，<span style="background-color: #c0c0c0;">将Eden和Survivor中还存活着的对象一次性地拷贝到另外一块Survivor空间</span>上，<span style="background-color: #c0c0c0;">最后清理掉Eden和刚才用过的Survivor的空间</span>。HotSpot默认Eden和Survivor的大小比例是8 : 1，也就是Eden中可用内存空间为整个新生代容量的90%。当Survivor空间不够用时，需要依赖年老代进行Promotion</p>
<p>Eden和Survivor默认8:1的比例是基于<span style="background-color: #c0c0c0;">绝大部分对象都是朝生暮死型</span>的假设，某些垃圾收集器能够自适应的调整该比例，该比值越小，内存浪费越大</p>
</td>
</tr>
<tr>
<td>标记-整理算法</td>
<td>
<p>Mark-Compact算法<span style="background-color: #c0c0c0;">针对年老代</span>特点进行设计，<span style="background-color: #c0c0c0;"> 标记过程仍然与“标记-清除”算法一样</span>，但后续步骤不是直接对可回收对象进行清理，而是让<span style="background-color: #c0c0c0;">所有存活的对象都向一端移动，然后直接清理掉端边界以外</span>的内存</p>
<p>该算法的效率低于复制算法</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">垃圾回收分类</span></div>
<ol>
<li>Minor GC：只针对年轻代的GC，也称为Young GC</li>
<li>Full GC：针对年老代的GC，偶尔会伴随年轻代、永久代的GC，也称为Major GC</li>
</ol>
<div class="blog_h3"><span class="graybg">JVM垃圾收集器实现</span></div>
<p>Jdk 6u22以后，可用的垃圾收集器有（年轻代年老代之间的连线表示可以组合使用）： <img class="size-full wp-image-2848 aligncenter" style="width: 70%;" src="https://blog.gmem.cc/wp-content/uploads/2012/06/jvm-3.png" alt="jvm-2" /></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 垃圾收集器</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Serial</td>
<td>
<p>JDK1.3.1之前的新生代收集唯一选择，<span style="background-color: #c0c0c0;">STW的基于复制算法的单GC线程收集器</span></p>
<p>使用-XX:+UseSerialGC打开，该选项会同时打开Serial Old。在JMC中，该收集器显示为Copy</p>
</td>
</tr>
<tr>
<td>ParNew</td>
<td>
<p><span style="background-color: #c0c0c0;">Serial收集器的多GC线程版本</span>，除了使用多条线程进行垃圾收集之外，其余行为包括Serial收集器可用的所有控制参数（例如：-XXSurvivorRatio、 -XX:PretenureSizeThreshold、-XX:HandlePromotionFailure 等）、收集算法、STW、对象分配规则、回收策略与Serial完全一样</p>
<p>ParNew是许多server模式下JVM的默认新生代收集器，因为它可以与CMS一起工作</p>
<p>在单CPU场景下该收集器不会比Serial表现更好。</p>
<p>-XX:ParallelGCThreads用来指定线程数，默认与CPU数相同，在CPU特别多的情况下，可以减少数量</p>
<p>使用-XX:+UseParNewGC打开，默认与Serial Old配合。在JMC中，该收集器显示为ParNew</p>
</td>
</tr>
<tr>
<td>Parallel Scavenge</td>
<td>
<p><span style="background-color: #c0c0c0;">STW的基于复制算法的多GC线程收集器</span>。它的特点是：<span style="background-color: #c0c0c0;">吞吐量优先</span>（Throughput)，而其它垃圾回收器都是以响应时间为目标，吞吐量=用户代码时间 / (用户代码时间 + GC时间)</p>
<p>-XX:MaxGCPauseMillis用于控制响应时间，-XX:GCTimeRatio用于控制吞吐量。</p>
<p><span style="background-color: #c0c0c0;">自适应调节</span>也是此回收器与ParNew的重要区别：-XX:+UseAdaptiveSizePolicy用于自动调整新生代大小、E/S比例、晋升阈值（-XX:PretenureSizeThreshold）来达到最合适的响应时间和吞吐量要求</p>
<p>使用-XX:+UseParallelGC打开，默认与Serial Old配合。在JMC中，该收集器显示为PS Scavenge</p>
<p>并行收集器（Parallel Scavenge / Parallel Old）的：</p>
<ol>
<li>优势：多线程扫描、整理堆</li>
<li>劣势：不管Minor/Full GC都STW</li>
</ol>
</td>
</tr>
<tr>
<td>Serial Old</td>
<td>
<p><span style="background-color: #c0c0c0;">STW的基于标记-清除-整理（Mark Sweep Compact）算法的单GC线程收集器</span>，专用于年老代（Tenured）</p>
<p>使用-XX:+UseSerialGC打开，该选项会同时打开Serial。在JMC中，该收集器显示为MarkSweepCompact</p>
</td>
</tr>
<tr>
<td>Parallel Old</td>
<td>
<p><span style="background-color: #c0c0c0;">基于标记-清除算法的多GC线程年老代收集器</span>，具有压缩功能，JDK1.6以后出现。主要用于配合Parallel Scavenge</p>
<p>使用-XX:+UseParallelOldGC打开，该选项会同时打开Parallel Scavenge。在JMC中，该收集器显示为PS MarkSweep</p>
</td>
</tr>
<tr>
<td>CMS</td>
<td>
<p>CMS (Concurrent Mark Sweep)，<span style="background-color: #c0c0c0;">低停顿的并发的标记-清除算法收集器</span>，<span style="background-color: #c0c0c0;">以获取最短回收停顿时间为目标</span>。B/S系统服务器适合使用CMS，工作流程分为以下4步骤：</p>
<ol>
<li>初始标记（CMS initial mark)，STW，标记GC Roots能直接到达的对象，很快</li>
<li>并发标记（CMS concurrent mark)，进行GC Root Tracing</li>
<li>重新标记（CMS remark)，<span style="background-color: #c0c0c0;"><strong>STW</strong></span>，修正并发标记期间，用户程序并发运行导致的对象记录变动，<span style="background-color: #c0c0c0;"><strong>比初始标记略长，默认单线程</strong></span></li>
<li>并发清除（CMS concurrent sweep)</li>
</ol>
<p>缺点：</p>
<ol>
<li>对CPU资源敏感：默认回收线程数=(CPU数量+3)/4，CPU不足4个时，CMS对用户程序代码影响较大</li>
<li>浮动垃圾问题：可能出现Concurrent Mode Failure而导致<span style="background-color: #c0c0c0;">自动使用Serial Old</span>进行Full GC。-XX:CMSInitiatingOccupancyFraction=68为默认值，可以调整以改变触发CMS的年老代内存占用比例</li>
<li>基于标记-清除算法，GC后产生大量碎片，可能因没有连续空间导致提前发生Full GC。-XX:+UseCMSCompactAtFullCollection可以在Full GC后进行碎片整理，-XX: CMSFullGCsBeforeCompaction用于执行多少次不压缩的Full GC后进行压缩</li>
</ol>
<p>使用-XX:+UseConcMarkSweepGC打开，该选项会自动打开ParNew。在JMC中，该收集器显示为MarkSweepCompact</p>
<p>并发模式失败：YGC要求提升对象到年老代，而后者没有足够的空间（没有足够的时间为其清理出空间）</p>
</td>
</tr>
<tr>
<td>G1</td>
<td>
<p>JDK1.6u14后出现，G1将整个Java堆（包括新生代、老年代）划分为多个大小固定的独立区域 (Region)，并且跟踪这些区域里面的垃圾堆积程度，在后台维护一个优先列表，每次根据允许的收集时间，优先回收垃圾最多的区域（这就是Garbage First名称的来由）</p>
<p>G1用于更好的支持大内存（4G+）</p>
<p>G1随时整理堆，而CMS只有在STW时才整理</p>
<p>从JDK 8u20开始G1支持字符串（包括底层char[]）去重，使用-XX:+UseStringDeduplicationJVM打开</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">内存分配和回收策略</span></div>
<ol>
<li>对象优先在Eden分配，如果Eden空间不足，发生Minor GC</li>
<li>大对象直接进入年老代：所谓大对象就是指，需要大量连续内存空间的Java对象（长字符串、数组）</li>
<li>长期存活对象进入年老代：所谓长期，是指经历多次Minor GC仍然存活</li>
<li>空间分配担保</li>
</ol>
<div class="blog_h3"><span class="graybg">Java引用的类型</span></div>
<ol>
<li>强引用：普通方式new的对象，均为强引用，这类对象不会被回收</li>
<li>软引用：系统将要发生内存溢出异常之前，将会把这些对象列进回收范围之中并进行第二 次回收。如果这次回收还是没有足够的内存，才会抛出内存溢出异常</li>
<li>弱引用：一旦垃圾回收器运作，即被回收，最多活到下一次垃圾回收之前</li>
<li>幻引用：一个对象是否 有虚引用的存在，完全不会对其生存时间构成影响，也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收 集器回收时收到一个系统通知。</li>
</ol>
<div class="blog_h1"><span class="graybg">JVM执行子系统</span></div>
<div class="blog_h2"><span class="graybg">Class文件的结构</span></div>
<p>Class文件是一组以字节为基础单位的、格式紧凑的二进制流，如果某数据项需要超过8位，则按高位在前方式分割为若干字节存储。 </p>
<div class="blog_h3"><strong><span class="graybg">数据类型只包括2类</span></strong> </div>
<ol>
<li>无符号数：ul、u2、u4、u8分别代表1个字节、2个字节、 4个字节和8个字节的无符号数，无符号数可以用来描述数字、索引引用、数量值、Unicode编码的字符</li>
<li>表是以多个无符号数、其他表作为数据项构成的复合类型，一般以 _info结尾。整个Class文件就是一张表：<br />
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px; text-align: center;"> 类型</td>
<td style="width: 150px; text-align: center;">名称</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>u4</td>
<td>magic</td>
<td>1个。用于确认是否为合法Class文件，为0xCAFEBABE</td>
</tr>
<tr>
<td>u2</td>
<td>minor_version</td>
<td>1个。次版本号</td>
</tr>
<tr>
<td>u2</td>
<td>major_version</td>
<td>1个。主版本号</td>
</tr>
<tr>
<td>u2</td>
<td>constant_pool_count</td>
<td>1个。记录常量池条目的数量</td>
</tr>
<tr>
<td>cpinfo</td>
<td>constant_pool</td>
<td>
<p>constant_pool_count - 1个。此容器的计数从1开始。主要包括两类常量：字面量（Literal)和符号引用（Symbolic Reference），字面量包括字符串、声明为final的常量值。符号引用包括：</p>
<ol>
<li>类和接口的全限定名（Fully Qualified Name)</li>
<li>字段的名称和描述符（Descriptor)</li>
<li>方法的名称和描述符</li>
</ol>
<p>Java与C不同，不存在"链接"的编译步骤，而是在JVM加载class文件时动态链接——class文件中不包含方法、字段 的内存布局信息——通过符号引用进行翻译，得到具体内存地址</p>
</td>
</tr>
<tr>
<td>u2</td>
<td>access_flags</td>
<td>1个。识别一些类或接口层次的访问信息，包括：这个Class是类还是接口 、是否定义为 public类型、是否定义为abstract类型、如果是类的话，是否被声明为final，等等</td>
</tr>
<tr>
<td>u2</td>
<td>this_class</td>
<td>1个。类索引（指向常量池）</td>
</tr>
<tr>
<td>u2</td>
<td>superclass</td>
<td>1个。父类索引（指向常量池）</td>
</tr>
<tr>
<td>u2</td>
<td>interfaces_count</td>
<td>1个。接口个数</td>
</tr>
<tr>
<td>u2</td>
<td>interfaces </td>
<td>interfaccs_count个。接口索引集合（指向常量池）</td>
</tr>
<tr>
<td>u2</td>
<td>fields_count</td>
<td>1个。字段个数</td>
</tr>
<tr>
<td>field_info</td>
<td>fields</td>
<td>
<p>fields_count个。字段表包含字段的各种限定符、描述符、名字索引等信息。字段表集合中不会列出从超类或父接口中继承而来的字段。</p>
<p>结构：</p>
<p>u2 access_flags 1，字段访问标记，参考下面的表格<br />u2 name_index 1，简单名称的索引<br />u2 descriptor_index，描述符的索引<br />u2 attributes_count，属性的个数<br />attribute_info attributes attributes_count 属性用于描述一些额外的信息</p>
</td>
</tr>
<tr>
<td>u2</td>
<td>methods_count</td>
<td>1个。方法个数</td>
</tr>
<tr>
<td>method_info</td>
<td>methods</td>
<td>
<p>methods_count个。方法表包含字段的各种限定符、描述符、名字索引等信息</p>
<p>结构：</p>
<p>u2 access_flags 1，方法访问标记，参考下面的表格<br />u2 name_index 1，简单名称的索引<br />u2 descriptor_index，描述符的索引<br />u2 attributes_count，属性的个数<br />attribute_info attributes attributes_count 属性用于描述一些额外的信息，包括表示方法的代码的Code属性。</p>
</td>
</tr>
<tr>
<td>u2</td>
<td>attributes_count</td>
<td>1个。</td>
</tr>
<tr>
<td>attribute_info</td>
<td>attributes</td>
<td>
<p>attributes_count个。属性表</p>
<p>结构：</p>
<p>u2 attribute_name_index 1 属性名称在常量池的索引<br />u2 attribute_length 1 属性长度<br />u1 info attribute_length 属性内容</p>
</td>
</tr>
</tbody>
</table>
</li>
</ol>
<div class="blog_h3"><span class="graybg">JVM版本对照表</span></div>
<table border="1" width="100%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top">
<p align="center"><b><span lang="EN-US">JDK</span> 编译器版本</b></p>
</td>
<td valign="top">
<p align="center"><b><span lang="EN-US"> target</span> 参数</b></p>
</td>
<td valign="top">
<p align="center"><b>十六进制<span lang="EN-US">minor.major </span></b></p>
</td>
<td valign="top">
<p align="center"><b>十进制 <span lang="EN-US">minor.major</span></b></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.1.8</span></p>
</td>
<td valign="top">
<p align="left">不能带 <span lang="EN-US">target</span> 参数</p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 03 00 2D</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">45.3</span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.2.2</span></p>
</td>
<td valign="top">
<p align="left">不带<span lang="EN-US">(</span>默认为 <span lang="EN-US">-target 1.1)</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 03 00 2D </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">45.3 </span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.2.2</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">-target 1.2</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 2E</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">46.0 </span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.3.1_19</span></p>
</td>
<td valign="top">
<p align="left">不带<span lang="EN-US">(</span>默认为 <span lang="EN-US">-target 1.1)</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 03 00 2D</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">45.3</span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.3.1_19</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">-target 1.3</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 2F</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">47.0 </span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">j2sdk1.4.2_10</span></p>
</td>
<td valign="top">
<p align="left">不带<span lang="EN-US">(</span>默认为 <span lang="EN-US">-target 1.2) </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 2E</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">46.0 </span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">j2sdk1.4.2_10 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">-target 1.4 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 30</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">48.0 </span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.5.0_11 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US"> </span>不带<span lang="EN-US">(</span>默认为 <span lang="EN-US">-target 1.5) </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 31 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">49.0</span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.5.0_11 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">-target 1.4 -source 1.4 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 30</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">48.0 </span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.6.0_01</span></p>
</td>
<td valign="top">
<p align="left">不带<span lang="EN-US">(</span>默认为 <span lang="EN-US">-target 1.6)</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 32  </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">50.0</span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.6.0_01 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">-target 1.5</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 31 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">49.0</span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.6.0_01</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">-target 1.4 -source 1.4  </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 30</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">48.0</span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.6.0_01 </span></p>
</td>
<td valign="top">
<p align="left">不带<span lang="EN-US">(</span>默认为 <span lang="EN-US">-target 1.6) </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 32 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">50.0</span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.7.0</span></p>
</td>
<td valign="top">
<p align="left">不带<span lang="EN-US">(</span>默认为 <span lang="EN-US">-target 1.6)</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 32 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">50.0 </span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.7.0</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">-target 1.7</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 33</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">51.0 </span></p>
</td>
</tr>
<tr>
<td valign="top">
<p align="left"><span lang="EN-US">jdk1.7.0 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US"> -target 1.4 -source 1.4</span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">00 00  00 30 </span></p>
</td>
<td valign="top">
<p align="left"><span lang="EN-US">48.0 </span></p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">常量池</span></div>
<p>常量池是Class文件结构中与其他项目关联最多的数据类型，也是占用Class文件空间最大的数据项目之一。</p>
<p>常量池中的每一项常量都是一个表，共有<span style="background-color: #c0c0c0;">11种结构各不相同的表结构数据</span>，这11 种表都有一个共同的特点，就是表开始的<span style="background-color: #c0c0c0;">第一位是一个u1类型的标志位</span>，代表当前这个常量属于哪种常量类型：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;">类型 </td>
<td style="width: 30px; text-align: center;"> </td>
<td style="text-align: center;"> 描述</td>
</tr>
</thead>
<tbody>
<tr>
<td>CONSTANT_Utf8_info</td>
<td>1</td>
<td>
<p>UTF-8编码的字符串</p>
<p>u1 tag 1<br />u2 length 1，字符串长度为多少字节<br />u1 bytes length 使用UTF-8缩略编码的字符串内容</p>
</td>
</tr>
<tr>
<td>CONSTANT_Integer_info</td>
<td>3 </td>
<td>整型字面量，高位在前</td>
</tr>
<tr>
<td>CONSTANT_Float_info</td>
<td>4 </td>
<td>浮点型字面量，高位在前</td>
</tr>
<tr>
<td>CONSTANT_Long_info</td>
<td>5 </td>
<td>长整型字面量，高位在前</td>
</tr>
<tr>
<td>CONSTANT_Doublc_info</td>
<td>6 </td>
<td>双精度浮点型字面量，高位在前</td>
</tr>
<tr>
<td>CONSTANT_Class_info</td>
<td>7 </td>
<td>
<p>类或接口的符号引用</p>
<p>u1 tag 1<br />u2 name index 1：指向CONSTANT_Utf8_info类型的常量，相对于常量池的偏移量</p>
</td>
</tr>
<tr>
<td>CONSTANT_String_info</td>
<td>8 </td>
<td>字符串类型字面量，指向字符串字面量的索引</td>
</tr>
<tr>
<td>CONSTANT_Fieldref_info</td>
<td>9 </td>
<td>
<p>字段的符号引用</p>
<p>tag u1<br />index u2 指向声明字段的类或接口描述符CONSTANTS_Class_info的索引项<br />index u2 指向字段描述符CONSTANT_NameAndType的索引项</p>
</td>
</tr>
<tr>
<td>CONSTANT_Methodref_info</td>
<td>10 </td>
<td>
<p>类中方法的符号引用</p>
<p>tag u1<br />index u2 指向声明方法的类描述符CONSTANT_Class_info 的索引项<br />index u2 指向名称及类型描述符CONSTANT_NameAndType的索引项</p>
</td>
</tr>
<tr>
<td>CONSTANT_InterfaceMethodref_info</td>
<td>11</td>
<td>
<p>接口中方法的符号引用</p>
<p>tag u1<br />index u2 指向声明方法的类描述符CONSTANT_Class_info 的索引项<br />index u2 指向名称及类型描述符CONSTANT_NameAndType的索引项</p>
</td>
</tr>
<tr>
<td>CONSTANT_NameAndType_info</td>
<td>12</td>
<td>
<p>字段或方法的部分符号引用</p>
<p>tag u1<br />index u2 指向该字段或方法名称常量项的索引<br />index u2 指向该字段或方法描述符常量项的索引</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><strong><span class="graybg">类访问标记</span></strong></div>
<p>在常量池结束之后，紧接着的2个字节代表访问标志（access_flags)：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">标志名称 </td>
<td style="text-align: center;">标志值 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td> ACC_PUBLIC</td>
<td> 0x0001</td>
<td> 是否为public类型</td>
</tr>
<tr>
<td> ACC_FINAL</td>
<td> 0x0010</td>
<td> 是否被声明为final,只有类可设置</td>
</tr>
<tr>
<td> ACC_SUPER</td>
<td> 0x0020</td>
<td> 是否允许使用invokespecial字节码指令</td>
</tr>
<tr>
<td> ACC_INTERFACE</td>
<td> 0x0200</td>
<td> 标识这是一个接口</td>
</tr>
<tr>
<td> ACC_ABSTRACT</td>
<td> 0x0400</td>
<td> 是否为abstract类型，对于接口或抽象类来说值为真</td>
</tr>
<tr>
<td> ACC_SYNTHETIC</td>
<td> 0x1000</td>
<td> 标识这个类并非由用户代码产生的</td>
</tr>
<tr>
<td> ACC_ANNOTATION</td>
<td> 0x2000</td>
<td> 标识这是一个注解</td>
</tr>
<tr>
<td> ACC_ENUM</td>
<td> 0x4000</td>
<td> 标识这是一个枚举</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<div class="blog_h3"><strong><span class="graybg">字段访问标记</span></strong></div>
<p>&nbsp;</p>
<p>字段表的前两个字节：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">标志名称 </td>
<td style="text-align: center;">标志值 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ACC_PUBLIC</td>
<td> 0x0001</td>
<td>字段是否public</td>
</tr>
<tr>
<td>ACC_PRIVATE</td>
<td> 0x0002</td>
<td>字段是否pravate</td>
</tr>
<tr>
<td>ACC_PROTECTED</td>
<td> 0x0004</td>
<td>字段是否protected</td>
</tr>
<tr>
<td>ACC_STATIC</td>
<td> 0x0008</td>
<td>字段是否static</td>
</tr>
<tr>
<td>ACC_FINAL</td>
<td> 0x0010</td>
<td>字段是否final</td>
</tr>
<tr>
<td>ACC_VOLATILE</td>
<td> 0x0040</td>
<td>字段是否volatile</td>
</tr>
<tr>
<td>ACC_TRANSIENT</td>
<td> 0x0080</td>
<td>字段是否transient</td>
</tr>
<tr>
<td>ACC_SYNTHETIC</td>
<td> 0x1000</td>
<td>字段是否由编译器自动产生的</td>
</tr>
<tr>
<td>ACC_ENUM</td>
<td> 0x4000</td>
<td>字段是否enum</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">方法访问标记</span></div>
<p>方法表的前两个字节：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">标志名称 </td>
<td style="text-align: center;">标志值 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ACC_PUBLIC</td>
<td>0x0001</td>
<td>方法是否public</td>
</tr>
<tr>
<td>ACC_PRIVATE</td>
<td>0x0002</td>
<td>方法是否pravate</td>
</tr>
<tr>
<td>ACC_PROTECTED</td>
<td>0x0004</td>
<td>方法是否protected</td>
</tr>
<tr>
<td>ACC_STATIC</td>
<td>0x0008</td>
<td>方法是否static</td>
</tr>
<tr>
<td>ACC_FINAL</td>
<td>0x0010</td>
<td>方法是否final</td>
</tr>
<tr>
<td>ACC_SYNCHRONIZED</td>
<td>0x0020</td>
<td>方法是否synchronized</td>
</tr>
<tr>
<td>ACC_BRIDGE</td>
<td>0x0040</td>
<td>方法是否为编译器产生的桥接方法</td>
</tr>
<tr>
<td>ACC_VARARGS</td>
<td>0x0080</td>
<td>方法是否接受不定参数</td>
</tr>
<tr>
<td>ACC_NATIVE</td>
<td>0x0100</td>
<td>方法是否为native</td>
</tr>
<tr>
<td>ACC_ABSTRACT</td>
<td>0x0400</td>
<td>方法是否为abstract</td>
</tr>
<tr>
<td>ACC_STRICT</td>
<td>0x0800</td>
<td>方法是否为strict</td>
</tr>
<tr>
<td>ACC_SYNTHETIC</td>
<td>0x1000</td>
<td>方法是否是由编译器自动产生的</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg"><a id="type-id-in-jvm"></a>字段与方法描述符 </span></div>
<p>描述符标识符列表：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px; text-align: center;"> 标识符</td>
<td style="text-align: center;">含义 </td>
</tr>
</thead>
<tbody>
<tr>
<td> B</td>
<td>基本类型byte</td>
</tr>
<tr>
<td> C</td>
<td>基本类型char</td>
</tr>
<tr>
<td> D</td>
<td>基本类型double</td>
</tr>
<tr>
<td> F</td>
<td>基本类型float </td>
</tr>
<tr>
<td> I</td>
<td>基本类型int </td>
</tr>
<tr>
<td> J </td>
<td>基本类型long </td>
</tr>
<tr>
<td> S</td>
<td>基本类型short </td>
</tr>
<tr>
<td> Z</td>
<td>基本类型boolean </td>
</tr>
<tr>
<td> V</td>
<td>特殊类型void</td>
</tr>
<tr>
<td> L</td>
<td>对象类型，如Ljava/lang/Object;</td>
</tr>
<tr>
<td> [</td>
<td>
<p>表示数组的前缀。例如java.lang.String[][] 表示为 [[Ljava/lang/String;</p>
</td>
</tr>
</tbody>
</table>
<p>描述方法时，按如下规则：</p>
<ol>
<li>先参数列表，后返回值。例如：void ific()描述为()V</li>
<li>参数按声明顺序放置于()内。例如：int compare(Object o1,Object o2)描述为(Ljava/lang/ObjectLjava/lang/Object)I</li>
</ol>
<div class="blog_h3"><strong><span class="graybg">属性表集合</span></strong></div>
<p>在Class文件、字段 表、方法表中都可以携带自己的属性表集合，以用于描述某些场景专有的信息。JVM规范中预定义了9项虚拟机实现应当能识別的属性：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">属性名称 </td>
<td style="text-align: center;">使用位置 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td> Code</td>
<td> 方法表</td>
<td>
<p>Java代码编译成的字节码指令</p>
<p>结构：</p>
<p>u2 attribute_name_index 1，固定为"Code"<br />u4 attribute_length 1 <br />u2 maxstack 1 操作数栈最大深度<br />u2 max_locals 1 局部变量表所需的存储空间，单位为Slot<br />u4 code_length 1<br />ul code code_length 存放字节码指令<br />u2 exception_table_length 1<br />exception_info exception_table exception_table_length<br />u2 attributes_count 1<br />attribute_info attributes attributes_count </p>
</td>
</tr>
<tr>
<td> ConstantValue</td>
<td> 字段表</td>
<td> final关键字定义的常量值</td>
</tr>
<tr>
<td> Deprecated</td>
<td> 类、方法表、字段表</td>
<td> 被声明为deprecated的方法和字段</td>
</tr>
<tr>
<td> Exceptions</td>
<td> 方法表</td>
<td> 方法抛出的异常</td>
</tr>
<tr>
<td> InnerClasses</td>
<td> 类文件</td>
<td> 内部类列表</td>
</tr>
<tr>
<td> LineNumberTable</td>
<td> Code属性</td>
<td> Java源码的行号与字节码指令的对应关系</td>
</tr>
<tr>
<td> LocalVariableTable</td>
<td> Code属性</td>
<td> 方法的局部变量描述</td>
</tr>
<tr>
<td> SourceFile</td>
<td> 类文件</td>
<td> 源文件的名称</td>
</tr>
<tr>
<td> Synthetic</td>
<td> 类、方法表、宇段表</td>
<td> 标记方法或字段为编译器自动生成的</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">类加载机制</span></div>
<div class="blog_h3"><span class="graybg">Java类的生命周期</span></div>
<p><strong> <img class="size-full wp-image-2926 aligncenter" style="width: 80%;" src="https://blog.gmem.cc/wp-content/uploads/2012/06/jvm-study.png" alt="jvm-study" /></strong></p>
<ol>
<li>加载：JVM规范没有规定何时加载类，实行根据需要决定。加载阶段需要完成3件事：<br />a) 通过一个类的全限定名来获取定义此类的二进制字节流<br />b) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构<br />c) 在Java堆中生成一个代表这个类的java.lang.Class对象，作为方法区这些数据的访问入口</li>
<li>验证：确保Class文件的字节流中包含的信息符合当前虚拟机的要求，并且不会危害虚拟机自身的安全。包括文件格式验证、元数据验证、字节码验证、符号引用验证等步骤</li>
<li>准备：为类变量分配内存并设置类变量初始值，这些内存都在<span style="background-color: #c0c0c0;">方法区</span>中分配。</li>
<li>解析：是将常量池内的符号引用替换为直接引用的过程。包括接口和类解析、字段解析、类方法解析、接口方法解析</li>
<li>初始化：是执行类构造方法()的过程（此方法收集静态变量赋值、static代码块自动生成），虚拟机保证多线程环境下()方法被正确的加锁与同步。以下情况，如果类尚未初始化，则需要触发初始化。遇到 new、getstatic、putstatic 或 invokestatic 这 4 条字节码指令时；当通过反射调用时；父类未初始化时，首先初始化父类；虚拟机启动时，初始化main函数所在类</li>
</ol>
<div class="blog_h3"><strong><span class="graybg">类加载器</span></strong> </div>
<p>比较两个类是否“相 等”，只有在<span style="background-color: #c0c0c0;">这两个类是由同一个类加载器加载</span>的前提之下才有意义。</p>
<p>从JVM的角度，类加载器只分为两类：一种是启动类加载器 (Bootstrap ClassLoader)，这个类加栽器使用C++语言实现，是虚拟机自身的一部分； 另外一种就是所有其他的类加载器，这些类加栽器都由Java语言实现且全都继承自抽象类java.lang.ClassLoader。</p>
<p>从开发人员角度，类加载器可以细分为：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">类加载器</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>启动类加载器<br />Bootstrap ClassLoader</td>
<td>负责将<span style="background-color: #c0c0c0;">$JAVA_HOME\lib</span>目录中的、或者被<span style="background-color: #c0c0c0;">-Xbootclasspath</span>参数所指定的路径中的，并且是虚拟机识别的（<span style="background-color: #c0c0c0;">仅按照文件名识别</span>，如rt.jar）类库加载到虚拟机内存中</td>
</tr>
<tr>
<td>
<p>扩展类加栽器<br />Extension ClassLoader</p>
</td>
<td>由<span style="background-color: #c0c0c0;">sun.misc.Launcher$ExtClassLoader</span>实现，它负责加载<span style="background-color: #c0c0c0;">$JAVA_HOME\lib\ext</span>目录中的、或者被 <span style="background-color: #c0c0c0;">java.ext.dirs</span>系统变最所指定的路径中的<span style="background-color: #c0c0c0;">所有类库</span></td>
</tr>
<tr>
<td>
<p>应用程序类加栽器<br />Application ClassLoader)</p>
</td>
<td>由<span style="background-color: #c0c0c0;">sun.misc. Launcher$AppClassLoader</span>来实现，ClassLoader中.getSystemClassLoader()的返回值即为此加载器，一般也称为<span style="background-color: #c0c0c0;">系统类加载器</span>。负责加载CLASSPATH上所指定的类库，如果应用程序中没有自定义过自己的类加载器，一般情况下这个就是程序中 默认的类加栽器。</td>
</tr>
</tbody>
</table>
<p>所谓双亲委派模型，指出了启动类加载器外，所有ClassLoader都可通过getParent()得到父加载器。通常，如果一个类加载器收到了类加载的请求，它<span style="background-color: #c0c0c0;">首先不会自己去尝试加载这个类，而是把这个请求委派给父类加载器去完成</span>，每一个层次的类加 载器都是如此，因此所有的加载请求最终都应该传送到顶层的启动类加载器中，只有当 父加载器反馈自己无法完成这个加载请求时，子加载器才会尝试自己加载。</p>
<p>线程<span style="background-color: #c0c0c0;">上下文 类加载器（Thread Context ClassLoader)</span>可以通过java.lang.Thread类的 setContextClassLoader()方法进行设置，如果创建线程时还未设置，<span style="background-color: #c0c0c0;">它将会从父线程中继承</span>。</p>
<div class="blog_h2"><span class="graybg">虚拟机字节码执行引擎</span></div>
<div class="blog_h3"><span class="graybg">运行时栈帧结构</span></div>
<p><span style="background-color: #c0c0c0;">栈帧（Stack Frame)是用于支持JVM进行方法调用和方法执行的数据结构</span>，是JVM运行时数据区中的<span style="background-color: #c0c0c0;">虚拟机栈的栈元素</span>。栈帧存储了方法的<span style="background-color: #c0c0c0;">局部变量表、操作数栈、动态链接和方法返回地址</span>等信息。方法从调用开始到执行完成的过程，就对应着一个<span style="background-color: #c0c0c0;">栈帧在虚拟机栈里面从入栈到出栈</span>的过程。对于执行引擎来讲，只有<span style="background-color: #c0c0c0;">栈顶的栈帧是有效的，称为当前栈帧（Current Stack Frame)，这个栈帧所关联的方法称为当前方法（Current Method)。</span>执行引擎所运行的<span style="background-color: #c0c0c0;">所有字节码指令都只针对当前栈帧</span>进行操作，栈帧结构示意图如下：<br /><img class="size-full wp-image-2939 aligncenter" style="width: 80%;" src="https://blog.gmem.cc/wp-content/uploads/2012/06/jvm-5.png" alt="jvm-5" /></p>
<div class="blog_h3"><span class="graybg">局部变量表</span></div>
<p>局部变量表的容量以<span style="background-color: #c0c0c0;">变量槽（Variable Slot,下称Slot)为最小单位</span>，每个Slot 都应该能存放一个 boolean、byte、char、short、int、float、reference 或 retumAddress 类型的数据（即一般的说Slot大小为32bits）</p>
<p>虚拟机通过索引定位的方式使用局部变量表，索引值的范围是从0开始到局部变量 表最大的Slot数量。对于Long、Double占用2个Slot，高位在前</p>
<p>对于块级作用域的变量，其Slot可以被重用</p>
<div class="blog_h3"><span class="graybg">操作数栈</span></div>
<p>也称为操作栈，每个栈元素可以是任意Java类型，Long、Double占用两个栈容量。当方法开始执行时，操作数栈是空的，其后会有各种字节码指令<span style="background-color: #c0c0c0;">向操作数栈中写入和提取内容，也就是入栈出栈操作</span>。例如， 在做算术运算的时候是通过操作数栈来进行的，又或者在<span style="background-color: #c0c0c0;">调用其他方法的时候是通过操作数栈来进行参数传递</span>的。</p>
<div class="blog_h3"><span class="graybg">动态链接</span></div>
<p>每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用，持有这个引用 是为了支持方法调用过程中的动态连接</p>
<div class="blog_h3"><span class="graybg">方法返回地址</span></div>
<p>方法退出有两种方式：</p>
<ol>
<li>执行引擎遇到方法返回的字节码指令，这时候可能会有返回值传递给上层的方法调用者，这种退出方法的方式称为正常完成出口</li>
<li>方法执行过程中遇到了异常，并且这个异常<span style="background-color: #c0c0c0;">没有在方法体内得到处理</span>，无论是Java虚拟机内部产生的异常，还是代码中使用athrow字节码指令 产生的异常，只要在本方法的异常表中没有<span style="background-color: #c0c0c0;">搜索到匹配的异常处理器</span>，就会导致方法退出，这种退出方法的方式称为异常完成出口</li>
</ol>
<p>方法退出后需要返回到方法被调用的位置，程序才能继续执行，方法返回时可能需要在栈帧中保存一些信息，用来帮助恢复它的上层方法的执行状态。一般来说，<span style="background-color: #c0c0c0;">方法正常退出时，调用者的PC计数器的值就可以作为返回地址</span>，栈帧中很可能会保存这个计数器值。而方法异常退出时，返回地址是<span style="background-color: #c0c0c0;">要通过异常处理器表来确定</span>。方法退出时可能执行的操作有：</p>
<ol>
<li>恢复上层方法的局部变量表和操作数栈</li>
<li>把返回值（如果有的话）压入调用者栈帧的操作数栈中</li>
<li>调整PC计数器的值以指向方法调用指令后面的一条指令</li>
</ol>
<div class="blog_h1"><span class="graybg">高效并发</span></div>
<div class="blog_h2"><span class="graybg">Java内存模型</span> </div>
<div class="blog_h3"><span class="graybg">主内存与工作内存</span></div>
<p>为了获得较好的执行效能，JMM不限制执行引擎使用处理器的<span style="background-color: #c0c0c0;">特定寄存器或缓存来和主内存进行交互</span>，也没有限制<span style="background-color: #c0c0c0;">即时编译器调整代码执行顺序</span>。</p>
<p>JMM规定了<strong>所有的变量都存储在主内存（Main Memory，相当于物理内存)</strong>中。每条<strong>线程</strong>还有自己的<strong>工作内存（Working Memory，相当于处理器高速缓存、寄存器）</strong>，线程的<strong>工作内存中保存</strong>了被该线程使用到的<strong>变量的主内存副本拷贝</strong>， 线程对<strong>变量的所有操作</strong>（读取、赋值等）都必须<strong>在工作内存中进行</strong>，而不能直接读写主内存中的变量。不同的<strong>线程之间也无法直接访问对方工作内存中的变量</strong>，线程间变量值的传递<strong>均需要通过主内存</strong>来完成。</p>
<div class="blog_h3"><span class="graybg">内存间交互操作</span></div>
<p>JMM定义了<span style="background-color: #c0c0c0;">8种原子性操作</span>来确定工作内存与主内存如何交互：</p>
<ol>
<li>lock (锁定）：作用于主内存的变量，它把一个变量标识为一条线程独占的状态</li>
<li>unlock (解锁）：作用于主内存的变量，它把一个处于锁定状态的变量释放出来， 释放后的变量才可以被其他线程锁定</li>
<li>read (读取）：作用于主内存的变量，它把一个变量的值从主内存传输到线程的 工作内存中，以便随后的load动作使用</li>
<li>load (载入）：作用于工作内存的变量，它把read操作从主内存中得到的变量值放入工作内存的变量副本中</li>
<li>use (使用）：作用于工作内存的变量，它把工作内存中一个变量的值传递给执 行引擎，每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作</li>
<li>assign (赋值作用于工作内存的变量，它把一个从执行引擎接收到的值陚值 给工作内存的变量，每当虚拟机遇到一个给变量陚值的字节码指令时执行这个操作</li>
<li>store (存储）：作用于工作内存的变董，它把工作内存中一个变量的值传送到主内存中，以便随后的write操作使用</li>
<li> write (写入）：作用于主内存的变量，它把store操作从工作内存中得到的变量的值放入主内存的变量中</li>
</ol>
<p>JMM还规定了在执行上述八种基本操作时必须满足如下规则：</p>
<ol>
<li>不允许read和load、store和write操作之一单独出现，即不允许一个变量从主内存读取了但工作内存不接受，或者从工作内存发起回写了但主内存不接受的情况出现</li>
<li>不允许一个线程丢弃它的最近的assign操作，即变量在工作内存中改变了之后必须把该变化同步回主内存</li>
<li>不允许一个线程无原因地（没有发生过任何assign操作）把数据从线程的工作内存同步回主内存中</li>
<li>一个新的变量只能在主内存中“诞生”，不允许在工作内存中直接使用一个未被初始化（load或assign)的变量，换句话说就是对一个变量实施use和store操作之前，必须先执行过了 assign和load操作</li>
<li>一个变量在同一个时刻只允许一条线程对其进行lock操作，但lock操作可以被同一条线程重复执行多次，多次执行lock后，只有执行相同次数的unlock操作， 变量才会被解锁</li>
<li>如果对一个变量执行lock操作，将会清空工作内存中此变量的值，在执行引擎使用这个变量前，需要重新执行load或assign操作初始化变量的值</li>
<li>如果一个变量事先没有被lock操作锁定，则不允许对它执行unlock操作，也不允许去unlock一个被其他线程锁定住的变量</li>
<li>对一个变量执行unlock操作之前，必须先把此变量同步回主内存中（执行sotre 和write操作）</li>
</ol>
<div class="blog_h3"><span class="graybg">对于volatile型变量的特殊规则</span></div>
<p>关键字volatile可以说是Java虚拟机提供的<span style="background-color: #c0c0c0;">最轻量级的同步机制</span>。</p>
<p>当一个变量被定义成volatile之后，它将具备两种特性：</p>
<ol>
<li><span style="background-color: #c0c0c0;">第一是保证此变量对所有线程的可见性</span>，这里的“可见性”是指当一条线程修改了这个变量的值，新值对于<span style="background-color: #c0c0c0;">其他线程来说是可以立即得知</span>的。而普通变量不能做到这一点，变量值在线程间传递均需要通过主内存来完成，如：线程A修改一个普通变量的值，然后向主内存进行回写，另外一条线程B在线程A回写完成了之后再从主内存进行读取操作，新变量的值才会对线程B可见。</li>
<li><span style="background-color: #c0c0c0;">第二是禁止指令重排序优化</span>，<span style="background-color: #c0c0c0;">普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果</span>，而不能保证变 量<span style="background-color: #c0c0c0;">赋值操作的顺序与程序代码中的执行顺序</span>一致</li>
</ol>
<p>但是<span style="background-color: #c0c0c0;">volatile并不保证操作的原子性</span>，在<span style="background-color: #c0c0c0;">不符合</span>以下两条规则的运算场景中，仍要通过加锁来保证原子性：</p>
<ol>
<li>运算结果并不依赖变量的当前值（例如简单setter），或者能够确保只有单一的线程修改变量的值</li>
<li>变量不需要与其他的状态变量共同参与不变约束</li>
</ol>
<div class="blog_h3"><span class="graybg">对于long和double型变量的特殊规则</span></div>
<p>对于64位的数据类型（long和double)，JMM允许虚拟机将没有被volatile修饰的64位数据的<span style="background-color: #c0c0c0;">读写操作划分为两次进行</span>。但是实际上，商业JVM基本上都把64位数据的读写操作作为原子操作实现。</p>
<div class="blog_h3"><span class="graybg">原子性、可见性与有序性</span></div>
<ol>
<li>原子性（Atomicity）：由Java内存模型来直接保证的原子性变量操作包括read、 load、assign、use、store和write这六个，我们大致可以认为<span style="background-color: #c0c0c0;">基本数据类型的访问读写是具备原子性</span>的。如果需要在大范围实现原子性，lock、unlock操作可以完成（对应JVM指令monitorenter 、monitorexit，在代码上反映为<span style="background-color: #c0c0c0;">Synchronized代码块</span>）</li>
<li>可见性（Visibility）：当一个线程修改了共享变量的值，其他线程能够 立即得知这个修改。volatile保证了多线程 操作时变置的可见性，而普通变量则不能保证这一点。synchronized、final也可以实现可见性</li>
<li>有序性（Ordering)：<span style="background-color: #c0c0c0;">天然的有序性：如果在本线程内观察，所有的操作都是有序的</span>。在另一个线程观察，<span style="background-color: #c0c0c0;">所有的操作都是无序的</span>——指令重排、工作内存和主内存同步延迟的影响导致无序。volatile、synchronized提供有序性保证</li>
</ol>
<div class="blog_h3"><span class="graybg">先行发生原则</span></div>
<p>“先行发生”（happens-before)的原则非常重 要，它是判断数据<span style="background-color: #c0c0c0;">是否存在竞争，线程是否安全</span>的主要依据。下面是Java内存模型下一些“天然的”先行发生关系（无需任何同步保证手段）：</p>
<ol>
<li>程序次序规则（Program Order Rule)：在一个线程内，按照程序代码顺序，书写在前面的操作先行发生于书写在后面的操作</li>
<li>监视器锁定规则（Monitor Lock Rule)：一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁，而“后面”是指时间上的先后顺序</li>
<li>volatile变量规则 (Volatile Variable Rule)：对一个volatile变量的写操作先行发生于后面对这个变量的读操作，这里的“后面”同样是指时间上的先后顺序</li>
<li>线程启动规则（Thread Start Rule) ：Thread对象的start()方法先行发生于此线程的每一个动作</li>
<li>线程终止规则（Thread Termination Rule)：线程中的所有操作都先行发生于对此线程的终止检测，我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行</li>
<li>线程中断规则（Thread Interruption Rule)：对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生，可以通过Thread.interrupted()方法检测到是否有中断发生</li>
<li>对象终结规则（Hnalizer Rule)：一个对象的初始化完成（构造函数执行结束）先行发生于它的finalize()方法的开始</li>
<li>传递性（Transitivity)：如果操作A先行发生于操作B，操作B先行发生于操作 C，那么操作A先行发生于操作C</li>
</ol>
<div class="blog_h2"><span class="graybg">线程</span></div>
<p>Java中线程的实现具有以下几种方式：</p>
<ol>
<li>使用内核线程实现。内核线程（Kernel Thread, KLT）就是直接由操作系统内核支持的线程，这种线程由内核来完成线程切换，内核通过操纵调度器（Scheduler)对线程进行调度，并负责<span style="background-color: #c0c0c0;">将线程的任务映射到各个处理器上</span>。<br />程序一般不会直接去使用内核线程，而是去使用内核线程的一种髙级接口——<span style="background-color: #c0c0c0;">轻量级进程（Light Weight Process, LWP)。</span>轻量级进程就是我们通常意义上所讲的线程，由于每个轻量级进程都由一个内核线程支持，因此只有先支持内核线程，才能有轻量级进程。这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型</li>
<li>使用用户线程实现。狭义上的用户线程指的是完全建立在用户空间的线程库上，系统内核不能感知到线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成，不需要内核的帮助。如果程序实现得当，这种线程不需要切换到内核态，因此<span style="background-color: #c0c0c0;">操作可以是非常快速且低消耗的，也可以支持规模更大的线程数量</span></li>
<li>混合实现：既存在用户线程，也存在轻量级进程。用户线程还是完全建立在用户空间中，因此用户线程的创建、切换、析构等操作依然廉价，并且可以支持大规模的用户线程并发。而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁，这样可以使用内核提供的线程调度功能及处理器映射，并且用户线程的系统调用要通过轻量级线程来完成，大大降低了进程被阻塞的风 险。在这种混合模式中，用户线程与轻量级进程的数量比是不定的，是M:N的关系</li>
</ol>
<div class="blog_h2"><span class="graybg">线程状态转换</span></div>
<p>Java语言定义了 5种进程状态：</p>
<ol>
<li>新建（New）：创建后尚未启动的线程处于这种状态</li>
<li>运行（Runable）：Runable包括了操作系统线程状态中的Running和Ready，也就是处于此状态的线程有可能正在执行，也有可能正在等待着CPU为它分配执行时间</li>
<li>无限期等待（Waiting）：处于这种状态的进程不会被分配CPU执行时间，它们要等待被其他线程显式地唤醒。以下方法会让线程陷入无限期的等待状态： <br />a) 没有设置Timeout参数的Object.wait()方法<br />b) 没有设置Timeout参数的Thread.join()方法<br />c) LockSupport.park()方法</li>
<li>限期等待（Timed Waitting）：处于这种状态的进程也不会被分配CPU执行时间， 不过无须等待被其他线程显式地唤醒，在一定时间之后它们会由系统自动唤醒。 以下方法会让线程进入限期等待状态：<br />a) Thread. sleep()方法<br />b) 设置了 Timeout 参数的 Object.wait()方法<br />c) 设置了 Timeout 参数的 Thread.join()方法<br />d) LockSupport.parkNanos() 方法<br />e) LockSupport.parkUntil()方法</li>
<li>阻塞（Blocked）：进程被阻塞了，“阻塞状态”与“等待状态”的区别是：“阻塞状态”在等待着获取到一个排它锁，这个事件将在另外一个线程放弃这个锁的时候发生；而“等待状态”则是在等待一段时间，或者唤醒动作的发生。在程序等待进入同步区域的时候，线程将进入这种状态。</li>
<li>结束（Terminated）：已终止线程的线程状态，线程已经结束执行</li>
</ol>
<div class="blog_h2"><span class="graybg">线程安全</span></div>
<p>当多个线程访问一个对象时，如果不用考虑这些线程在运行时环境下的调度和交替执行，也不需要进行额外的同步，或者在调用方进行任何其他的协调操作，调用这个对象的行为都可以获得正确的结果，那这个对象就是线程安全的。</p>
<p>按照线程安全的级别，可以按线程安全强度把共享数据分为5类：</p>
<ol>
<li>不可变对象：例如字符串、数字的部分子类、枚举</li>
<li>绝对线程安全：完全满足上述关于线程安全的定义。即使多个线程同时对这个对象进行一系列顺序的操作，也不会出现意外的结果</li>
<li>相对线程安全：通常意义上所讲的线程安全，它需要保证对<span style="background-color: #c0c0c0;">这个对象<strong>单独的操作</strong>是线程安全</span>的，我们在调用的时候不需要做额外的保障措施。在Java语言中，大部分的线程安全类都属于这种类型，例如Vector、HashTable、 Collections.synchronizedCollection()方法包装的集合等</li>
<li>线程兼容：是指对象本身并不是线程安全的，但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中安全地使用，我们平常说一个<span style="background-color: #c0c0c0;">类不是线程安全的，绝大多数指的都是这种情况</span>。Java API中大部分的类都是线程兼容</li>
<li>线程对立：是指不管调用端是否采取了同步措施，都无法在多线程环境中并发使用的代码。由于Java语言天生就具备多线程特性，线程对立这种排斥多线程的代码是很少出现。一个线程对立的例子是Thread类的suspend()和resume()方法</li>
</ol>
<div class="blog_h3"><span class="graybg">线程安全的实现方法</span></div>
<ol>
<li>互斥同步（Mutual Exclusion &amp; Synchronization)：是最常见的一种并发正确性保障手段，<span style="background-color: #c0c0c0;">同步是指在多个线程并发访问共享数据时，保证共享数据在同一个时刻只被一条 (或者是一些，使用信号量的时候）线程使用</span>。而互斥是实现同步的一种手段，<span style="background-color: #c0c0c0;">临界区 (Critical Section)、互斥量（Mutex)和信号量（Semaphore)</span>都是主要的互斥实现方式。在Java里面，最基本的互斥同步手段就是synchronized关键字。synchronized是Java语言中一个重量级的操作，虚拟机会进行一些优化，譬如在<span style="background-color: #c0c0c0;">通知操作系统阻塞线程之前加入一段自旋等待过程，避免频繁地切入到内核态中</span>。除了 synchronized之外，还可以使用java.util.concurrent的重入锁（<span style="background-color: #c0c0c0;">ReentrantLock</span>)来实现同步，在基本用法上，ReentrantLock与synchronized 很相似，他们都具备一样的线程重入特性，只是代码写法上有点区别，一个表现为API 层面的互斥锁（<span style="background-color: #c0c0c0;">lock和unloek方法配合try/fmally语句块来完成</span>），一个表现为原生语法层面的互斥锁。不过ReentrantLock比synchronized增加了一些髙级功能，主要有以下三项：<br />a) <span style="background-color: #c0c0c0;">等待可中断</span>：当持有锁的线程长期不释放锁的时候，正在等待的线程可以选择 放弃等待，改为处理其他事情，可中断特性对处理执行时间非常长的同步块很有帮助<br />b)<span style="background-color: #c0c0c0;"> 公平锁</span>：多个线程在等待同一个锁时，必须按照申请锁的时间顺序来依次获得锁；而非公平锁则不保证这一点，在锁被释放时，任何一个等待锁的线程都有机会获得锁。synchronized中的锁是非公平的，<span style="background-color: #c0c0c0;">ReentrantLock默认情况下也是非公平</span>的，但可以通过带布尔值的构造函数要求使用公平锁<br />c) <span style="background-color: #c0c0c0;">锁绑定多个条件</span>：一个ReentrantLock对象可以同时绑定多个Condition对象， 而在synchronized中，锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含的条件，如果要和多于一个的条件关联的时候，就不得不额外地添加一个 锁，而ReentrantLock则无须这样做，只需要多次调用newCondition()方法即可</li>
<li>非阻塞同步：随着硬件指令集的发展，可以使用基于冲突检测的乐观并发策略。Atomiclnteger就是这样的例子</li>
<li>无同步方案：<br />a) 可重入代码：也叫纯代码（Pure Code），可以在代码执行的任何时刻中断它，转而去执行另外一段代码（包括递归调用它本身），而在控制权返回后，原来的程序不会出现任何错误。可重入代码有一些共同的特征：例如<span style="background-color: #c0c0c0;">不依赖存储在堆上的数据和公用的系统资源、 用到的状态量都由参数中传入、不调用非可重入的方法</span>等。<br />b) 线程本地存储（Thread Local Storage）</li>
</ol>
<div class="blog_h2"><span class="graybg">锁优化</span></div>
<div class="blog_h3"><span class="graybg">自旋锁与自适应自旋</span></div>
<p>互斥同步对性能最大的影响是阻塞的实现， 挂起线程和恢复线程的操作都需要转入内核态中完成，这些操作给系统的并发性能带来 了很大的压力。在许多应用上，<span style="background-color: #c0c0c0;">共享数据的锁定状态 只会持续很短的一段时间</span>，为了这段时间去挂起和恢复线程并不值得。如果物理机器有 一个以上的处理器，能让两个或以上的线程同时并行执行，我们就可以让后面请求锁的那个线程“稍等一会儿”，<span style="background-color: #c0c0c0;">但不放弃处理器的执行时间</span>，看看持有锁的线程是否很快就会释放锁。为了让线程等待，我们<span style="background-color: #c0c0c0;">只须让线程执行一个忙循环（自旋）</span>，这项技术就是 所谓的自旋锁。</p>
<p>自旋锁在JDK1.6以后默认开启。使用-XX:+UseSpinning可以强制开启。自旋默认值是10次，可以使用参数-XX:PreBlockSpin来更改。</p>
<p>在JDK 1.6中引入了<span style="background-color: #c0c0c0;">自适应的自旋锁</span>。自旋的时间由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象 上，自旋等待刚刚成功获得过锁，并且持有锁的线程正在运行中，那么虚拟机就会认为这次自旋也很有可能再次成功，进而它将允许自旋等待持续相对更长的时间。</p>
<div class="blog_h3"><span class="graybg">锁消除</span></div>
<p>对一些<span style="background-color: #c0c0c0;">代码上要求同步，但是被检测到不可能存在共享数据竞争</span>的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持。</p>
<div class="blog_h3"><span class="graybg">锁粗化</span></div>
<p>大部分情况下，倾向于将同步块的作用范围限制得尽量小，但是如果一系列的连续操作都对同一个对反复加锁和解锁，甚至加锁操作是出现在循环体中的，那即使没有线程竞争，频繁地 进行互斥同步操作也会导致不必要的性能损耗。</p>
<div class="blog_h3"><span class="graybg">轻量级锁</span></div>
<p>轻量级锁是JDK 1.6中加入的新型锁机制，它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。首先 需要强调一点的是，轻量级锁并不是用来代替重量级锁的，它的本意是<span style="background-color: #c0c0c0;">在没有多线程竞争的前提下，减少传统的重量级锁使用操作系统互斥量产生的性能消耗</span>。</p>
<p>轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁，在整个同步周期内都是不存在竞争的”，这是一个经验数据。如果没有竞争，轻量级锁使用CAS操作避免了 使用互斥量的开销，但如果存在锁竞争，除了互斥量的开销外，还额外发生了 CAS操 作，因此在有竞争的情况下，轻量级锁会比传统的重量级锁更慢。</p>
<div class="blog_h3"><span class="graybg">偏向锁</span></div>
<p>偏向锁也是JDK 1.6中引入的一项锁优化，它的目的是<span style="background-color: #c0c0c0;">消除数据在无竞争情况下的同步原语，进一步提高程序的运行性能</span>。如果说轻量级锁是在无竞争的情况下使用CAS 操作去消除同步使用的互斥量，那偏向锁就是在无竞争的情况下<span style="background-color: #c0c0c0;">把整个同步都消除掉， 连CAS操作都不做</span>了。<span style="background-color: #c0c0c0;">偏向锁可以提高带有同步但无竞争的程序性能</span>。如果程序中<span style="background-color: #c0c0c0;">大多数的锁都总是被多个不同的线程访问</span>，那偏向模式就是多余的。有时候使用参数-XX:-UseBiasedLocking来禁止偏向锁优化反而可以提升性能。</p>
<div class="blog_h2"><span class="graybg">逃逸分析</span></div>
<p>逃逸分析是一种可以有效<span style="background-color: #c0c0c0;">减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法</span>。通过逃逸分析，Java Hotspot编译器能够<span style="background-color: #c0c0c0;">分析出一个新的对象的引用的使用范围</span>从而决定是否要将这个对象分配到堆上。</p>
<p>在计算机语言编译器优化原理中，逃逸分析是指<span style="background-color: #c0c0c0;">分析指针动态范围的方法</span>，它同编译器优化原理的指针分析和外形分析相关联。当变量（或者对象）在方法中分配后，其指针<span style="background-color: #c0c0c0;">有可能被返回或者被全局引用</span>，这样就<span style="background-color: #c0c0c0;">会被其他过程或者线程所引用</span>，这种现象<span style="background-color: #c0c0c0;">称作指针（或者引用）的逃逸（Escape）</span>。</p>
<p>Java在Java SE <span style="background-color: #c0c0c0;">6u23</span>以及以后的版本中支持并<span style="background-color: #c0c0c0;">默认开启了逃逸分析的选项</span>。Java的 HotSpot JIT编译器，能够在方法重载或者动态加载代码的时候对代码进行逃逸分析，同时Java对象在堆上分配和内置线程的特点使得逃逸分析成Java的重要功能。</p>
<p>&nbsp;</p>
<p>经过逃逸分析之后，可以得到三种对象的逃逸状态：</p>
<ol>
<li>GlobalEscape（全局逃逸）， 即一个对象的引用逃出了方法或者线程</li>
<li>ArgEscape（参数级逃逸），即在方法调用过程当中传递对象的引用给一个方法</li>
<li>NoEscape（没有逃逸），一个可以进行标量替换的对象。可以不将这种对象分配在传统的堆上</li>
</ol>
<p>编译器可以使用逃逸分析的结果，对程序进行以下优化：</p>
<ol>
<li>堆分配对象变成栈分配对象。一个方法当中的对象，对象的引用没有发生逃逸，那么这个方法<span style="background-color: #c0c0c0;">可能会被分配在栈内存上</span>而非常见的堆内存上</li>
<li>消除同步。<span style="background-color: #c0c0c0;">线程同步的代价是相当高</span>的，<span style="background-color: #c0c0c0;">同步的后果是降低并发性和性能</span>。<span style="background-color: #c0c0c0;">逃逸分析可以判断出某个对象是否始终只被一个线程访问</span>，如果只被一个线程访问，那么对该对象的同步操作就可以转化成没有同步保护的操作，这样就能大大提高并发程度和性能</li>
<li>矢量替代。逃逸分析方法如果发现对象的内存存储结构不需要连续进行的话，就可以将对象的部分甚至全部都<span style="background-color: #c0c0c0;">保存在CPU寄存器</span>内，这样能大大提高访问速度</li>
</ol>
<p>逃逸分析对应的JVM参数是 -XX:+DoEscapeAnalysis</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/jvm-study-note">JVM学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/jvm-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JProfiler学习笔记</title>
		<link>https://blog.gmem.cc/jprofiler-study-note</link>
		<comments>https://blog.gmem.cc/jprofiler-study-note#comments</comments>
		<pubDate>Mon, 22 Mar 2010 02:17:31 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[性能剖析]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1908</guid>
		<description><![CDATA[<p>剖析视图 内存视图（Memory Views） 该视图专注于对象的内存分配情况，包含以下选项卡：  All Objects 显示堆中实时的对象情况：显示某种类型（聚合级别可以是类、包等）对象的数量（Instance Count）、浅尺寸（Shallow size）关于浅尺寸的计算： 普通对象大小的计算不包括引用、类变量，由变量本身地址、基本类型的变量占用的内存等组成 数组整体计算，不分为单个元素计算大小/数量 注意，有些对象虽然没有引用，但是可能没被GC回收，可以手工Run GC 右击某一类型，可以转到Class Tracker或者Heaper Walker视图 Recorded Objects 显示已经进行记录的对象。与All Objects相比，可以查看对象分配调用树（Allocation Call Tree）、分配热点（Allocation <a class="read-more" href="https://blog.gmem.cc/jprofiler-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/jprofiler-study-note">JProfiler学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">剖析视图</span></div>
<div class="blog_h3"><span class="graybg">内存视图（Memory Views）</span></div>
<p>该视图专注于对象的内存分配情况，包含以下选项卡：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 150px;"> All Objects</td>
<td>显示堆中实时的对象情况：显示某种类型（聚合级别可以是类、包等）对象的数量（Instance Count）、浅尺寸（Shallow size）关于浅尺寸的计算：</p>
<ol>
<li>普通对象大小的计算不包括引用、类变量，由变量本身地址、基本类型的变量占用的内存等组成</li>
<li>数组整体计算，不分为单个元素计算大小/数量</li>
</ol>
<p>注意，有些对象虽然没有引用，但是可能没被GC回收，可以手工Run GC</p>
<p>右击某一类型，可以转到Class Tracker或者Heaper Walker视图</td>
</tr>
<tr>
<td>Recorded Objects</td>
<td>显示已经进行记录的对象。与All Objects相比，可以查看对象分配调用树（Allocation Call Tree）、分配热点（Allocation Hot Sports）点击工具栏：Start Memory，可以录制对象分配</td>
</tr>
<tr>
<td>Allocation Call Tree</td>
<td>显示选中的、已录制对象类型的分配调用数，分析调用栈的哪些栈帧比较消耗资源。从线程执行开始处，一直追溯到对象被创建的地方，对象占用内存的大小被显示，效果如下图：<img src="https://blog.gmem.cc/wp-content/uploads/2010/03/alloc-tree.jpg" alt="" /></td>
</tr>
<tr>
<td>Allocation Hot Sports</td>
<td>显示选中的、已录制对象类型的分配热点：以创建对象的方法为根显示调用树，效果如下图：<img src="https://blog.gmem.cc/wp-content/uploads/2010/03/alloc-hotsopt.jpg" alt="alloc-hotsopt" width="514" height="71" /></td>
</tr>
<tr>
<td>Class Tracker</td>
<td> 显示选中类型对象的实例个数的曲线图（按时间）</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">堆遍历（Heap Walker）</span></div>
<p>该视图用于静态分析堆快照，在生成快照之前，会进行一次Full GC。包含以下选项卡：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 150px;">Classes</td>
<td>类似于Memory Views的All Objects，可以显示快照中所有对象的数量、大小。右击选择Use Selected Instances，则可以显示单种类型的数据</td>
</tr>
<tr>
<td>Allocations</td>
<td>分析对象分配情况：包括4种显示方式：Cumulated Allocation Tree：累积调用树，显示对象分配调用树，包含对象sizeAllocation Tree：调用树，显示对象分配调用树，包含对象sizeAllocation Tree Map：以Tree Map形式显示调用树Allocation Hot spots：显示分配对象最多的方法</td>
</tr>
<tr>
<td>Biggest Objects</td>
<td>显示占用内存最多的对象</td>
</tr>
<tr>
<td>References</td>
<td>显示单种类型的引用情况包含4种显示方式：Incoming references：其它对象对每一个选中对象的引用Outcoming references：每一个选中对象对其它对象的引用Cumulated Incoming references：显示其它对象、字段对选中对象类型的总体引用统计Cumulated Outcoming references：显示选中对象类型对其它对象、字段的总体引用统计</td>
</tr>
<tr>
<td>Time</td>
<td>分析对象分配的消耗时间</td>
</tr>
<tr>
<td>Inspections</td>
<td>提供多种精细的对象分配分析方式</td>
</tr>
<tr>
<td>Graph</td>
<td>图形化方式显示对象的引用图</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">CPU视图（CPU Views）</span></div>
<p>该视图用于分析方法的CPU占用时间，包括以下选项卡：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td> Call Tree</td>
<td>显示自顶而下的调用堆栈的树，树的根是线程开始处，树的分叉表示的不同的调用路径。暗红色显示当前节点及其子节点消耗的时间、高亮红色显示本级节点消耗的时间。</p>
<div>显示未过滤类对过滤类的直接调用，不显示深入的调用，在被过滤类的左上角显示<span style="color: #ff0000;"><strong>红三角</strong></span>。main方法、线程的run方法总是显示，不予过滤。</div>
<div>通过Profiling Settings可以选择CPU时间的度量方式：Elapsed time/Estimated CPU time，前者为Wall clock time（物理世界真实时间），后者为占用CPU的时间。由于操作系统的固有限制，10ms以下的时间可能不具有统计学意义。</div>
<div>界面右上角的Thread Status用于选择参与分析的线程状态，通常选择Runnable来分析性能问题，有时可以结合Net I/O、Blocked</div>
<div></div>
<div>工具栏Start Tracking，可以追踪某些任务在不同线程之间的传递（调用点/执行点Call site/Execution site）</div>
<p>效果如下图：<br />
<img src="https://blog.gmem.cc/wp-content/uploads/2010/03/call-tree.jpg" alt="call-tree" width="478" height="166" /></td>
</tr>
<tr>
<td style="width: 150px;"> Hot Spots</td>
<td>显示耗时最高的方法调用，显示消耗时间、平均消耗时间、调用次数等字段，以耗时方法为根，可以追溯到线程启动处Filtered Classes设置：选择被过滤类占用时间，可以单独列出，或者算在调用它的元素上（main、线程run不能过滤）<br />
每个方法可能被多个调用栈调用，百分比显示不同调用栈对此方法的固有时间的“贡献”<br />
不显示Total time，只显示Inherent Time</td>
</tr>
<tr>
<td>Call Graph</td>
<td>显示方法调用的序列，包括某个方法的调用与被调用情况</td>
</tr>
<tr>
<td>Method Stat</td>
<td>点击工具栏图标，可以录制方法的统计信息并进行分析</td>
</tr>
<tr>
<td>Call Tracer</td>
<td>点击工具栏图标，可以对方法调用进行跟踪</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">线程视图（Thread Views）</span></div>
<p>该视图用于分析线程的状态和历史分析、检测死锁，并可以获取线程快照，包含以下选项卡：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 150px;">Thread History</td>
<td>线程状态时间线，可以选择显示活着的或者死去的线程</p>
<div>
<div><span style="font-family: 微软雅黑;"><b><span style="color: #328712;">绿色：Runnable</span></b>：表示线程可以接受CPU调度，但不一定正在占用CPU，和线程优先级、调度算法、系统负载有关</span></div>
<div><span style="font-family: 微软雅黑;"><b><span style="color: #ffaf00;">黄色：Waiting：</span></b>表示线程正在睡眠（java.lang.Thread.Sleep），或者在监视器上等待（java.lang.Object.wait），前者不会放弃占有的监视器</span></div>
<div><span style="font-family: 微软雅黑;"><b><span style="color: #e30000;">红色：Blocked：</span></b>表示线程被阻塞，正在积极尝试进入同步区</span></div>
<div><span style="font-family: 微软雅黑;"><b><span style="color: #1eccff;">浅蓝色：Net I/O：</span></b>线程正在等待网络操作的完成，监听Socket或者读写Socket时产生此状态</span></div>
<div>效果图如下：</div>
<div><img src="https://blog.gmem.cc/wp-content/uploads/2010/03/thread-history.jpg" alt="thread-history" width="497" height="78" /></div>
</div>
</td>
</tr>
<tr>
<td>Thread Monitor</td>
<td>显示线程的开始结束时间、父线程、状态等信息</td>
</tr>
<tr>
<td>Thread Dumps</td>
<td>获取线程快照，可以分析瞬时系统各线程的调用栈</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">监视器视图（Monitor Views）</span></div>
<p>该视图用于锁状态检测，包含以下选项卡：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td> Current Locking Graph</td>
<td>当前锁状态图，显示监视器、以及在监视器上等待的线程。</p>
<p><strong>黑色箭头</strong>表示当前拥有此监视器的线程</p>
<p><span style="color: #800000;"><strong>红色虚线</strong></span>表示被阻塞的线程（积极尝试获得锁）</p>
<p><span style="color: #ffcc00;"><strong>黄色箭头</strong></span>表示在监视器上等待的线程（java.lang.Object.wait）</p>
<p>效果图如下：</p>
<p><img src="https://blog.gmem.cc/wp-content/uploads/2010/03/current-monitor.jpg" alt="current-monitor" width="363" height="117" /></td>
</tr>
<tr>
<td>Current Monitors</td>
<td>显示当前活动的监视器，包括监视器状态、监视器类、等待线程、拥有者线程等信息</td>
</tr>
<tr>
<td style="width: 150px;">Locking History Graph</td>
<td>可以录制监视器锁定的历史</td>
</tr>
<tr>
<td>Monitor History</td>
<td>可以显示所有监视器的历史</td>
</tr>
<tr>
<td>Monitor Usage Stat</td>
<td>以监视器、线程等方式分组，显示监视器的使用统计，包括阻塞次数、阻塞持续时间、等待次数、等待持续时间等</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">虚拟机遥感视图（VM Telemetry Views）</span></div>
<p>该视图可以显示时间线上的系统宏观信息，包括以下选项卡：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td>Memory</td>
<td>显示已有、空闲内存的区域图</td>
</tr>
<tr>
<td>Recorded Objects</td>
<td>显示堆中对象（数组、非数组）的数量</td>
</tr>
<tr>
<td>Recorded Throughput</td>
<td>显示已记录对象的创建和销毁数量</td>
</tr>
<tr>
<td>GC Activity</td>
<td>显示GC活动率百分比</td>
</tr>
<tr>
<td>Classes</td>
<td>显示加载的类的数量</td>
</tr>
<tr>
<td>Threads</td>
<td>显示处于各种状态的线程的数量</td>
</tr>
<tr>
<td>CPU Load</td>
<td>显示CPU负载百分比</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">JavaEE以及探针（JEE &amp; Probes）</span></div>
<p>可以使用多种探针来检测不同的应用领域，包括：JDBC、JPA/Hibernate、JNDI、JMS、Servlet、RMI、Socket、WebServices、Files等。</p>
<div class="blog_h2"><span class="graybg">剖析配置</span></div>
<div class="blog_h3"><span class="graybg">触发器</span></div>
<p>用于在特定条件下触发剖析动作<br />
<strong>触发条件：</strong>方法被调用、堆使用阈值、CPU使用率阈值、内存溢出异常、定时器、虚拟机启动、虚拟机关闭等<br />
<strong>剖析动作：</strong>启/停录制、调用跟踪、监视器跟踪，触发堆dump、线程dump，启/停探针录制，保存快照，保存HPROF快照，运行脚本等</p>
<div class="blog_h2"><span class="graybg">离线剖析</span></div>
<p><strong>使用场景</strong>：脚本启动剖析、定期快照保存、剖析远程服务器</p>
<div><span style="color: #000000;">Session ID：需要指定id=xxx参数来激活相应的Profiling Settings，此ID显示在Session Settings - Application Settings的左上角，如果配置文件中只包含一个Session，则不需要配置。</span></div>
<div>
<div><span style="color: #000000;">配置文件位置：可以指定config=xxx.xml，来指明设置的存放位置，默认是~/.jprofiler7/config.xml</span></div>
<div>离线剖析命令行样例：</div>
<div>
<pre class="crayon-plain-tag">java 
"-agentpath:D:\JavaEE\jprofiler\7.2.2\bin\windows-x64\jprofilerti.dll=offline,id=109,config=D:\JavaEE\Config\jprofiler-offline.xml"
"-Xbootclasspath/a:D:\JavaEE\jprofiler\7.2.2\\bin\agent.jar"
-classpath myapp.jar cc.gmem.demo.MyApp</pre><br />
&nbsp;</p>
</div>
</div>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/jprofiler-study-note">JProfiler学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/jprofiler-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
