<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>JVM - 标签 - Victor's Code Journey</title><link>http://www.victorchu.info/tags/jvm/</link><description>JVM - 标签 - Victor's Code Journey</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><managingEditor>victorchu0610@outlook.com (victorchutian)</managingEditor><webMaster>victorchu0610@outlook.com (victorchutian)</webMaster><lastBuildDate>Sat, 28 Aug 2021 16:31:28 +0800</lastBuildDate><atom:link href="http://www.victorchu.info/tags/jvm/" rel="self" type="application/rss+xml"/><item><title>JVM性能分析工具-Async Profiler</title><link>http://www.victorchu.info/posts/2021/08/b23a2d0b/</link><pubDate>Sat, 28 Aug 2021 16:31:28 +0800</pubDate><author><name>victorchutian</name></author><guid>http://www.victorchu.info/posts/2021/08/b23a2d0b/</guid><description><![CDATA[<div class="featured-image">
                <img src="/feature-images/jvm.webp" referrerpolicy="no-referrer">
            </div><p>很多 JVM CPU Profiler(例如VisualVM,NetBean Profiler,YourKit 和 JProfiler等)都提供了CPU分析器。一般CPU Profiling功能有两种实现方式: Sampling和Instrumentation。</p>
<ul>
<li>Sampling方式基于无侵入的额外线程对所有线程的调用栈快照进行固定频率抽样，它的性能开销很低。但由于它基于“采样”的模式，以及JVM固有的只能在安全点(SafePoint)进行采样的“缺陷”，会导致统计结果存在一定的偏差。核心原理如下：
<ul>
<li>引入Profiler依赖，或直接利用Agent技术注入目标JVM进程并启动Profiler。</li>
<li>启动一个采样定时器，以固定的采样频率每隔一段时间（毫秒级）对所有线程的调用栈进行Dump。</li>
<li>汇总并统计每次调用栈的Dump结果，在一定时间内采到足够的样本后，导出统计结果，内容是每个方法被采样到的次数及方法的调用关系。</li>
</ul>
</li>
<li>Instrumentation则是利用Instrument API，对所有必要的Class进行字节码增强，在进入每个方法前进行埋点，方法执行结束后统计本次方法执行耗时，最终进行汇总。Instrumentation方式对几乎所有方法添加了额外的AOP逻辑，这会导致对线上服务造成巨额的性能影响，但其优势是：绝对精准的方法调用次数、调用时间统计。</li>
</ul>
<p>Sampling由于低开销的特性，更适合用在CPU密集型的应用中，以及不可接受大量性能开销的线上服务中。而Instrumentation则更适合用在I/O密集的应用中、对性能开销不敏感以及确实需要精确统计的场景中。上面介绍的CPU Profiler更多的是基于Sampling来实现。</p>]]></description></item><item><title>Java虚拟机-启动参数详解</title><link>http://www.victorchu.info/posts/2018/12/4a4d773a/</link><pubDate>Sun, 02 Dec 2018 15:20:22 +0800</pubDate><author><name>victorchutian</name></author><guid>http://www.victorchu.info/posts/2018/12/4a4d773a/</guid><description><![CDATA[<div class="featured-image">
                <img src="/feature-images/jvm.webp" referrerpolicy="no-referrer">
            </div><p>java命令用于启动JVM虚拟机。Java启动参数分为3种:</p>
<ol>
<li>标准参数: 所有的JVM实现都必须实现这些参数的功能，而且向后兼容。JVM的标准参数都是以”-“开头。</li>
<li>非标准参数: 默认JVM(HotSpot虚拟机)实现这些参数的功能，但是并不保证所有jvm实现都满足，且不保证向后兼容.JVM的非标准参数都是以”-x“开头。</li>
<li>非stable参数：此类参数通常具有特定的系统要求，并且可能需要对系统配置参数的特权访问。各个jvm实现会有所不同，将来可能会随时取消，需要慎重使用。JVM的非stable参数都是以”-xx“开头。</li>
</ol>]]></description></item><item><title>Java虚拟机-SafePoint</title><link>http://www.victorchu.info/posts/2018/07/668bd42f/</link><pubDate>Tue, 24 Jul 2018 16:08:57 +0800</pubDate><author><name>victorchutian</name></author><guid>http://www.victorchu.info/posts/2018/07/668bd42f/</guid><description><![CDATA[<div class="featured-image">
                <img src="/feature-images/jvm.webp" referrerpolicy="no-referrer">
            </div><blockquote>
  <p>A point in program where the state of execution is known by the VM。</p>

</blockquote><p>HotSpot的安全点定义: Safepoint 是程序执行过程中的一个点，在这个点，所有的GC根都是已知的，所有的堆对象内容都是一致的。从全局角度来看，所有的线程都必须在安全点阻塞，然后GC才能运行。（作为一种特殊情况，运行JNI代码的线程可以继续运行，因为它们只使用句柄。在安全点期间，它们必须阻塞而不是加载句柄的内容。）从局部角度来看，安全点是代码块中的一个特别点，执行线程可能会在该点阻塞GC。大多数执行点都符合安全点的条件。每个安全点都有强不变量，这些不变量在非安全点可能会被忽略。</p>
<p>总而言之，Safepoint 指在 Java 虚拟机中，程序执行时的一个特殊点。在 Safepoint 处，所有的线程都会被暂停下来，以便进行JVM 的一些特定的操作<a href="#refer-anchor-1" rel=""><sup>1</sup></a>。例如:</p>
<ul>
<li>定时进入 SafePoint：经过<code>-XX:GuaranteedSafepointInterval</code>配置的时间，都会让所有线程进入 Safepoint，一旦所有线程都进入，立刻从 Safepoint 恢复。这个定时主要是为了一些没必要立刻 Stop the world 的任务执行，推荐设置-XX:GuaranteedSafepointInterval=0关闭这个定时</li>
<li>由于 jstack，jmap 和 jstat 等命令，也就是 Signal Dispatcher 线程要处理的大部分命令，都会导致 Stop the world：这种命令都需要采集堆栈信息，所以需要所有线程进入 Safepoint 并暂停。</li>
<li>偏向锁取消(这个不一定会引发整体的 Stop the world，参考 JEP 312: Thread-Local Handshakes):Java 认为，锁大部分情况是没有竞争的（某个同步块大多数情况都不会出现多线程同时竞争锁），所以可以通过偏向来提高性能。即在无竞争时，之前获得锁的线程再次获得锁时，会判断是否偏向锁指向我，那么该线程将不用再次获得锁，直接就可以进入同步块。但是高并发的情况下，偏向锁会经常失效，导致需要取消偏向锁，取消偏向锁的时候，需要 Stop the world，因为要获取每个线程使用锁的状态以及运行状态。</li>
<li>Java Instrument 导致的 Agent 加载以及类的重定义：由于涉及到类重定义，需要修改栈上和这个类相关的信息，所以需要 Stop the world。</li>
<li>Java Code Cache 相关：当发生 JIT 编译优化或者去优化，需要 OSR 或者 Bailout 或者清理代码缓存的时候，由于需要读取线程执行的方法以及改变线程执行的方法，所以需要 Stop the world</li>
<li>GC：这个由于需要每个线程的对象使用信息，以及回收一些对象，释放某些堆内存或者直接内存，所以需要 Stop the world</li>
<li>JFR 的一些事件：如果开启了 JFR 的 OldObject 采集，这个是定时采集一些存活时间比较久的对象，所以需要 Stop the world。同时，JFR 在 dump 的时候，由于每个线程都有一个 JFR 事件的 buffer，需要将 buffer 中的事件采集出来，所以需要 Stop the world。</li>
</ul>]]></description></item><item><title>Java虚拟机-伪共享</title><link>http://www.victorchu.info/posts/2018/05/4cd66d97/</link><pubDate>Fri, 04 May 2018 15:28:48 +0800</pubDate><author><name>victorchutian</name></author><guid>http://www.victorchu.info/posts/2018/05/4cd66d97/</guid><description><![CDATA[<div class="featured-image">
                <img src="/feature-images/jvm.webp" referrerpolicy="no-referrer">
            </div><p>在Java程序中,数组的成员在缓存中也是连续的. 其实从Java对象的相邻成员变量也会加载到同一缓存行中. 如果多个线程操作不同的成员变量, 但是相同的缓存行, 伪共享(False Sharing)问题就发生了. 关于伪共享的介绍可以参考<a href="/posts/2018/05/ee80e066/" rel="">上一篇博客: CPU Memory Cache</a>。</p>]]></description></item><item><title>java 类加载器</title><link>http://www.victorchu.info/posts/2017/10/921530f5/</link><pubDate>Sat, 28 Oct 2017 19:18:21 +0800</pubDate><author><name>victorchutian</name></author><guid>http://www.victorchu.info/posts/2017/10/921530f5/</guid><description><![CDATA[<div class="featured-image">
                <img src="/feature-images/java.webp" referrerpolicy="no-referrer">
            </div><p>前面我们介绍了<a href="/posts/2017/10/51a7bdd0/" rel="">Java的类加载流程</a>。这篇文章，我们来详细了解下java的类加载器。</p>]]></description></item><item><title>Java 类加载</title><link>http://www.victorchu.info/posts/2017/10/51a7bdd0/</link><pubDate>Fri, 27 Oct 2017 11:24:08 +0800</pubDate><author><name>victorchutian</name></author><guid>http://www.victorchu.info/posts/2017/10/51a7bdd0/</guid><description><![CDATA[<div class="featured-image">
                <img src="/feature-images/java.webp" referrerpolicy="no-referrer">
            </div><p>在java代码中，类型的加载，连接与初始化过程都是在程序运行期间完成的（类class文件信息在编译期间已经确定好）。</p>
<p><img class="tw-inline" loading="lazy" src='/posts/2017/10/51a7bdd0/jvm-classloader.webp'    height="168" width="474"></p>
<p>类从被加载到虚拟机内存中开始，到卸载出内存为止，它的整个生命周期包括：加载<code>Loading</code>、验证<code>Verification</code>、准备<code>Preparation</code>、解析<code>Resolution</code>、初始化<code>Initialization</code>、使用<code>Using</code>和卸载<code>Unloading</code>7个阶段。其中准备、验证、解析3个部分统称为连接<code>Linking</code>。</p>
<p>加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的，类的加载过程必须按照这种顺序按部就班地开始，而解析阶段则不一定：它在某些情况下可以在初始化阶段之后再开始，这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。</p>
<blockquote>
  <p>注意，本文的JDK版本是Java 1.8，在Java 9 引进模块化后，ClassLoader也有了一些新变化。</p>

</blockquote>]]></description></item><item><title>Java虚拟机-Java内存模型</title><link>http://www.victorchu.info/posts/2017/10/9b98afdc/</link><pubDate>Tue, 17 Oct 2017 07:58:35 +0800</pubDate><author><name>victorchutian</name></author><guid>http://www.victorchu.info/posts/2017/10/9b98afdc/</guid><description><![CDATA[<div class="featured-image">
                <img src="/feature-images/jvm.webp" referrerpolicy="no-referrer">
            </div><p>多任务处理在现代计算机操作系统中是一个必备功能。计算机的运算能力变得十分强大的同时，运算速度与存储和通信子系统速度的差距太大，大量的时间都花费在磁盘I/O、网络通信和数据库访问上，因此为了避免处理器的大部分时间都花费在等待其它资源，一种行之有效的方法是让计算机同时处理多项任务。</p>]]></description></item><item><title>Java虚拟机-GC收集器</title><link>http://www.victorchu.info/posts/2017/10/df91eca7/</link><pubDate>Wed, 11 Oct 2017 00:19:01 +0800</pubDate><author><name>victorchutian</name></author><guid>http://www.victorchu.info/posts/2017/10/df91eca7/</guid><description><![CDATA[<div class="featured-image">
                <img src="/feature-images/jvm.webp" referrerpolicy="no-referrer">
            </div><p>HotSpot虚拟机实现GC算法时，必须对算法的执行效率有严格的考量，才能保证虚拟机的高效运行。本文介绍了HotSpotGC 算法的部分内部实现。</p>]]></description></item><item><title>Java虚拟机-内存分配与回收策略</title><link>http://www.victorchu.info/posts/2017/09/d6c519ad/</link><pubDate>Sat, 09 Sep 2017 01:10:43 +0800</pubDate><author><name>victorchutian</name></author><guid>http://www.victorchu.info/posts/2017/09/d6c519ad/</guid><description><![CDATA[<div class="featured-image">
                <img src="/feature-images/jvm.webp" referrerpolicy="no-referrer">
            </div><p>对象的内存分配，主要是在堆上分配(也可能是JIT编译后被拆散成标量类型并间接的栈上分配):</p>
<ul>
<li>对象主要分配在新生代的Eden区上，如果启动了本地线程分配缓冲，将按线程优先分配在TLAB上。</li>
<li>少数情况下会直接分配在老年代中。</li>
</ul>
<p>分配的规则并不是百分百固定的，细节取决于当前使用的是哪一种垃圾收集器组合，还有虚拟机中与内存相关的参数设置。</p>
<p>对象的回收主要分为两种:</p>
<ul>
<li>新生代GC(Minor GC)指发生在新生代的垃圾回收动作，Minor GC十分频繁，回收速度较快。</li>
<li>老年代GC(Major/Full GC)指发生在老年代的GC，出现了Major GC，经常会伴随至少一次Minor GC ,但非绝对，Parallel Scavenger 收集器里有直接进行Major GC的策略选择。通常，Major GC 比Minor GC 慢10倍以上。</li>
</ul>]]></description></item><item><title>Java虚拟机-GC算法简介</title><link>http://www.victorchu.info/posts/2017/09/eca99d89/</link><pubDate>Fri, 08 Sep 2017 01:10:43 +0800</pubDate><author><name>victorchutian</name></author><guid>http://www.victorchu.info/posts/2017/09/eca99d89/</guid><description><![CDATA[<div class="featured-image">
                <img src="/feature-images/jvm.webp" referrerpolicy="no-referrer">
            </div><p>在了解垃圾回收算法前，我们先要了解几个基本概念。</p>
<p>首先是mutator和collector，这两个名词经常在垃圾收集算法中出现，collector指的就是垃圾收集器，而mutator是指除了垃圾收集器之外的部分，比如说我们应用程序本身。mutator的职责一般是NEW(分配内存),READ(从内存中读取内容),WRITE(将内容写入内存)，而collector则就是回收不再使用的内存来供mutator进行NEW操作的使用。</p>
<p>第二个基本概念是关于mutator roots(mutator根对象),mutator根对象一般指的是分配在堆内存之外，可以直接被mutator直接访问到的对象，一般是指静态/全局变量以及Thread-Local变量(在Java中，存储在java.lang.ThreadLocal中的变量和分配在栈上的变量,方法内部的临时变量等都属于此类).</p>
<p>第三个基本概念是关于可达对象的定义，从mutator根对象开始进行遍历，可以被访问到的对象都称为是可达对象。这些对象也是mutator(你的应用程序)正在使用的对象。</p>]]></description></item></channel></rss>