<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>SoSo</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://soso.github.io/"/>
  <updated>2018-10-19T13:39:47.000Z</updated>
  <id>https://soso.github.io/</id>
  
  <author>
    <name>许桂斌</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>【笔记】高性能MySQL（一）</title>
    <link href="https://soso.github.io/2018/08/20/High-Performance-MySQL/"/>
    <id>https://soso.github.io/2018/08/20/High-Performance-MySQL/</id>
    <published>2018-08-19T19:37:37.000Z</published>
    <updated>2018-10-19T13:39:47.000Z</updated>
    
    <content type="html"><![CDATA[<p>MySQL架构与历史。</p><a id="more"></a><h1 id="MySQL逻辑架构"><a href="#MySQL逻辑架构" class="headerlink" title="MySQL逻辑架构"></a>MySQL逻辑架构</h1><p><img src="https://github.com/SoSo/YouDaoNoteImages/blob/master/MySQL%E9%80%BB%E8%BE%91%E6%9E%B6%E6%9E%84.png?raw=true" alt="MySQL逻辑架构"></p><p>MySQL 的逻辑架构分为三层。</p><p>最上层是客户端层，非MySQL独有。</p><p>第二层架构是 MySQL 服务层，大多数 MySQL 的核心服务功能都在这一层，包括查询解析、分析、优化、缓存以及所有的内置函数（例如，日期、时间、数学和加密函数），所有跨存储引擎的功能都在这一层实现，包括存储过程、触发器、视图等。</p><p>第三层是存储引擎。存储引擎负责 MySQL 中数据的存储和提取。服务器通过 API 与存储引擎进行通信。这些接口屏蔽了不同存储引擎之间的差异，使得这些差异对上层的查询过程透明。</p><h1 id="并发控制"><a href="#并发控制" class="headerlink" title="并发控制"></a>并发控制</h1><p>MySQL 在两个层面进行并发控制：服务器层 &amp; 存储引擎层。</p><h2 id="读写锁"><a href="#读写锁" class="headerlink" title="读写锁"></a>读写锁</h2><p>因为读数据和写数据需要不同的并发控制。所以使用两种锁来处理，这两种锁通常被称为共享锁（shared lock）和排他锁(exclusive lock)，也叫读锁和写锁。</p><p>如果一个资源加上了读锁时，其他需要共享锁的操作也能同时进行，即多个线程可以在同一时刻读取同一资源。</p><p>如果一个资源加上写锁，则其他操作都不能进行，即同一时刻只能存在一个线程进行写入同一资源。</p><p>写锁比读锁有更高的优先级，因此一个写锁请求可能会被插入到读锁队列的前面。</p><h2 id="锁粒度"><a href="#锁粒度" class="headerlink" title="锁粒度"></a>锁粒度</h2><p>锁粒度即是要在哪个层面上进行加锁操作，可以对整张表进行加锁操作（表级锁），也可以对表中的某行数据进行加锁操作（行级锁）。</p><p>对数据进行更精确的锁定可以提高系统的并发量，但是需要消耗更多的资源进行加锁操作。锁的各种操作，包括获得锁、检测锁是否已经解除、释放锁等，都会增加系统的开销。</p><p>大多数商业数据库一般都是在表上施加行级锁(row-level lock)。而 MySQL 不同的存储引擎实现了不同的锁策略和锁粒度。最重要的两种锁粒度：</p><ul><li>表级锁(table lock)<br>当一个线程对表进行写操作（插入、删除、更新等）前，需要先获得写锁，这会对整个表进行锁定，因此会阻塞其他线程的读写操作。</li><li>行级锁(row lock)<br>行级锁可以最大程度地支持并发处理。</li></ul><h2 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h2><p>事务即是将一组原子性的 SQL 查询。这一组 SQL 语句要么全部执行成功，要么全部执行失败。</p><p>事务的四种特性(ACID)</p><ul><li>原子性（atomicity）<br>一个事务必须被视为一个不可分割的最小单元，一个事务中的所有操作要么全部提交成功，要么全部失败回滚。</li><li>一致性（consistency）<br>数据库总是从一个一致性的状态转换到另一个一致性的状态。</li><li>隔离性（isolation）<br>一个事务所做的修改在最终提交之前，对其他事务是不可见的。</li><li>持久性（durability）<br>一旦事务提交，则其所做的修改就会永久保存到数据库中。</li></ul><h3 id="隔离级别"><a href="#隔离级别" class="headerlink" title="隔离级别"></a>隔离级别</h3><p>三个概念</p><ul><li>脏读(dirty read)<br>一个事务T1更新(UPDATE)了一行数据(data1 -&gt; data2)，这时表数据已发生改变，但是事务T1还没提交，这时另一个事务T2去读这个数据data2，这种情况就是脏读。<br>如果T1后续操作失败进行回滚的话，数据重新变回data1，T2读取的数据data2就是无效数据。</li><li>不可重复读<br>事务T1读取某一数据data1，事务T2读取并修改了该数据data -&gt; data2，T1为了对读取值进行检验而再次读取该数据data2，便得到了不同的结果，这种情况就是不可重复读。<br>因为每次重新查询的数据无法保证一致，所以事务T1的提交就无法保证正确。</li><li>幻读<br>事务T1将表中满足字段A&gt;100的所有数据进行修改操作，同时事务T2插入了一条满足A&gt;100的数据，T1再次查询满足A&gt;100的数据进行校验，发现多出一条数据未修改，这种情况就是幻读。</li></ul><p>四种隔离级别</p><ul><li>READ UNCOMMITTED（未提交读）<br>事务中的修改，即使没有提交，对其他事务也都是可见的。</li><li>READ COMMITTED（提交读）<br>一个事务开始时，只能“看见”已经提交的事务所做的修改。大多数数据库系统的默认隔离级别都是READ COMMITTED（MySQL不是）。</li><li>REPEATABLE READ（可重复读）<br>保证了在同一个事务中多次读取同样记录的结果是一致的。可重复读是MySQL的默认隔离级别。</li><li>SERIALIZABLE（可串行化）<br>最高隔离级别。强制事务串行执行，除非需要确保数据的一致性并且可以接受没有并发，不然不使用。</li></ul><table><thead><tr><th>隔离级别</th><th>脏读可能性</th><th>不可重复读可能性</th><th>幻读可能性</th><th>加锁</th></tr></thead><tbody><tr><td>READ UNCOMMITTED</td><td>Yes</td><td>Yes</td><td>Yes</td><td>No</td></tr><tr><td>READ COMMITTED</td><td>No</td><td>Yes</td><td>Yes</td><td>No</td></tr><tr><td>REPEATABLE READ</td><td>No</td><td>No</td><td>Yes</td><td>No</td></tr><tr><td>SERIALIZABLE</td><td>No</td><td>No</td><td>No</td><td>Yes</td></tr></tbody></table><p>可以使用下面的命令来修改事务的隔离级别。<br><figure class="hljs highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">// 设置全局/当前会话的事务隔离级别<br><span class="hljs-keyword">SET</span> <span class="hljs-keyword">GLOBAL</span>/<span class="hljs-keyword">SESSION</span> <span class="hljs-keyword">TRANSACTION</span> <span class="hljs-keyword">ISOLATION</span> <span class="hljs-keyword">LEVEL</span> <span class="hljs-keyword">READ</span> COMMITTED;<br></code></pre></td></tr></table></figure></p><h3 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a>死锁</h3><p>当两个事务相互持有对方在等待的锁时，就会产生死锁。</p><figure class="hljs highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs undefined">// 事务T1<br>START TRANSACTION;<br>SELECT close FROM StockPrice WHERE stock_id = 4 for update;<br>...<br>SELECT close FROM StockPrice WHERE stock_id = 3 for update;<br><br>// 事务T2<br>START TRANSACTION;<br>SELECT close FROM StockPrice WHERE stock_id = 3 for update;<br>...<br>SELECT close FROM StockPrice WHERE stock_id = 4 for update;<br></code></pre></td></tr></table></figure><p>当如上两个事务同时执行时就会引发死锁问题。</p><p>解决死锁一般有两种方法。</p><ul><li>检测死锁的循环依赖。</li><li>查询的时间达到锁等待时间后放弃锁。</li></ul><p>锁机制是由不同存储引擎实现的。以同样的顺序执行语句，有些存储引擎会产生死锁，有些则不会。</p><h3 id="MySQL中的事务"><a href="#MySQL中的事务" class="headerlink" title="MySQL中的事务"></a>MySQL中的事务</h3><ul><li><p>自动提交（AutoCommit）<br>自动提交是指如果不是显示地开始一个事务，则每个查询都被当作一个事务执行提交。MySQL 默认采用自动提交（AUTOCOMMIT）模式。  </p><figure class="hljs highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs undefined">// 查询是否开启自动提交<br>mysql&gt; SHOW VARIABLES LIKE &apos;AUTOCOMMIT&apos;;<br>// 开启自动提交<br>mysql&gt; SET AUTOCOMMIT = 1;<br></code></pre></td></tr></table></figure></li><li><p>在事务中混合使用存储引擎<br>因为事务是由下层的存储引擎实现的，如果在同一个事务内使用不同存储引擎，有的存储引擎支持事务（如 InnoDB），有的存储引擎不支持事务（如 MyISAM），那事务发生回滚时非事务型表的变更会无法撤销。因此不要在同一个事务中使用多种存储引擎。   </p></li></ul><h2 id="多版本并发控制"><a href="#多版本并发控制" class="headerlink" title="多版本并发控制"></a>多版本并发控制</h2><h2 id="选择合适引擎"><a href="#选择合适引擎" class="headerlink" title="选择合适引擎"></a>选择合适引擎</h2><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://book.douban.com/subject/23008813/" target="_blank" rel="noopener">《高性能MySQL》</a><br><a href="https://www.jianshu.com/p/d7665192aaaf" target="_blank" rel="noopener">https://www.jianshu.com/p/d7665192aaaf</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;MySQL架构与历史。&lt;/p&gt;
    
    </summary>
    
      <category term="笔记" scheme="https://soso.github.io/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
      <category term="笔记" scheme="https://soso.github.io/tags/%E7%AC%94%E8%AE%B0/"/>
    
      <category term="MySQL" scheme="https://soso.github.io/tags/MySQL/"/>
    
      <category term="数据库" scheme="https://soso.github.io/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>【笔记】JVM内存管理（四） - 内存分配与回收策略</title>
    <link href="https://soso.github.io/2018/07/27/note-JVM-memory-4/"/>
    <id>https://soso.github.io/2018/07/27/note-JVM-memory-4/</id>
    <published>2018-07-26T16:22:23.000Z</published>
    <updated>2018-07-26T10:12:27.000Z</updated>
    
    <content type="html"><![CDATA[<p>对象关于新生代和老年代的内存分配。</p><a id="more"></a><h1 id="Minor-GC-amp-Full-GC"><a href="#Minor-GC-amp-Full-GC" class="headerlink" title="Minor GC &amp; Full GC"></a>Minor GC &amp; Full GC</h1><ul><li>新生代GC（Minor GC）：指发生在新生代的垃圾收集动作，因为Java对象大多都具备朝生夕灭的特性，所以Minor GC非常频繁，一般回收速度也比较快。</li><li>老年代GC（Major GC / Full GC）：指发生在老年代的GC，出现了Major GC，经常会伴随至少一次的Minor GC（但非绝对的，在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程）。Major GC的速度一般会比Minor GC慢10倍以上。</li></ul><h1 id="内存分配策略"><a href="#内存分配策略" class="headerlink" title="内存分配策略"></a>内存分配策略</h1><h2 id="对象优先在-Eden-分配"><a href="#对象优先在-Eden-分配" class="headerlink" title="对象优先在 Eden 分配"></a>对象优先在 Eden 分配</h2><p>大多数情况下，对象在新生代的Eden区中分配。当Eden区没有足够空间进行分配时，虚拟机将发起一次Minor GC。</p><h2 id="大对象直接进入老年代"><a href="#大对象直接进入老年代" class="headerlink" title="大对象直接进入老年代"></a>大对象直接进入老年代</h2><p>大对象是指，需要大量连续内存空间的Java对象，最典型的大对象就是很长的字符串以及数组。大对象对虚拟机的内存分配来说是一个坏消息（尤其是遇到朝生夕灭的“短命大对象”，写程序时应避免），经常出现大对象容易导致内存还有不少空间时就提前触发GC以获取足够的连续空间来安置它们。</p><p>虚拟机提供了一个<code>-XX:PretenureSizeThreshold</code>参数，令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制。</p><h2 id="长期存活的对象将进入老年代"><a href="#长期存活的对象将进入老年代" class="headerlink" title="长期存活的对象将进入老年代"></a>长期存活的对象将进入老年代</h2><p>虚拟机给每个对象定义了一个对象年龄（Age）计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活，并且能被Survivor容纳的话，将被移动到Survivor空间中，并且对象年龄设为1。对象在Survivor区中每“熬过”一次Minor GC，年龄就增加1岁，当它的年龄增加到一定程度（默认为15岁），就将会被晋升到老年代中。对象晋升老年代的年龄阈值，可以通过参数-XX:MaxTenuringThreshold设置。</p><h2 id="动态对象年龄判定"><a href="#动态对象年龄判定" class="headerlink" title="动态对象年龄判定"></a>动态对象年龄判定</h2><p>虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代，如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半，年龄大于或等于该年龄的对象就可以直接进入老年代，无须等到MaxTenuringThreshold中要求的年龄。</p><h2 id="空间分配担保"><a href="#空间分配担保" class="headerlink" title="空间分配担保"></a>空间分配担保</h2><p>在发生Minor GC之前，虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间，如果这个条件成立，那么Minor GC可以确保是安全的。如果不成立，则虚拟机会查看<code>HandlePromotionFailure</code>设置值是否允许担保失败。如果允许，那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小，如果大于，将尝试着进行一次Minor GC，尽管这次Minor GC是有风险的；如果小于，或者<code>HandlePromotionFailure</code>设置不允许冒险，那这时也要改为进行一次Full GC。</p><p>取平均值进行比较其实仍然是一种动态概率的手段，如果某次Minor GC存活后的对象突增，远远高于平均值的话，依然会导致担保失败。如果出现了<code>HandlePromotionFailure</code>失败，那就只好在失败后重新发起一次Full GC。虽然担保失败时绕的圈子是最大的，但大部分情况下都还是会将<code>HandlePromotionFailure</code>开关打开，避免Full GC过于频繁。</p><h1 id="Full-GC的触发条件"><a href="#Full-GC的触发条件" class="headerlink" title="Full GC的触发条件"></a>Full GC的触发条件</h1><p>对于Minor GC，其触发条件非常简单，当Eden区空间满时，就将触发一次Minor GC。而 Full GC 则相对复杂，有以下条件：</p><ul><li><p>调用<code>System.gc()</code><br>此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。因此强烈建议能不使用此方法就不要使用，让虚拟机自己去管理它的内存，可通过<code>-XX:+ DisableExplicitGC</code>来禁止RMI调用<code>System.gc()</code>。</p></li><li><p>老年代空间不足<br>老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等，当执行Full GC后空间仍然不足，则抛出如下错误： <code>Java.lang.OutOfMemoryError: Java heap space</code> 为避免以上两种状况引起的Full GC，调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。</p></li><li><p>空间分配担保失败<br>前文介绍过，使用复制算法的Minor GC需要老年代的内存空间作担保，如果出现了HandlePromotionFailure担保失败，则会触发Full GC。</p></li><li><p>JDK 1.7及以前的永久代空间不足<br>在 JDK 1.7 及以前，HotSpot 虚拟机中的方法区是用永久代实现的，永久代中存放的为一些 Class 的信息、常量、静态变量等数据。<br>当系统中要加载的类、反射的类和调用的方法较多时，永久代可能会被占满，在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了，那么虚拟机会抛出 <code>java.lang.OutOfMemoryError: PermGen space</code>。<br>为避免以上原因引起的 Full GC，可采用的方法为增大永久代空间或转为使用 CMS GC。</p></li><li><p>Concurrent Mode Failure<br>执行CMS GC的过程中同时有对象要放入老年代，而此时老年代空间不足（有时候“空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC），便会报<code>Concurrent Mode Failure</code>错误，并触发Full GC。</p></li></ul><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p>周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.<br><a href="https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E8%99%9A%E6%8B%9F%E6%9C%BA.md" target="_blank" rel="noopener">https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E8%99%9A%E6%8B%9F%E6%9C%BA.md</a><br><a href="https://crowhawk.github.io/2017/08/15/jvm_3/" target="_blank" rel="noopener">https://crowhawk.github.io/2017/08/15/jvm_3/</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;对象关于新生代和老年代的内存分配。&lt;/p&gt;
    
    </summary>
    
      <category term="JVM" scheme="https://soso.github.io/categories/JVM/"/>
    
    
      <category term="JVM" scheme="https://soso.github.io/tags/JVM/"/>
    
      <category term="内存结构" scheme="https://soso.github.io/tags/%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84/"/>
    
      <category term="笔记" scheme="https://soso.github.io/tags/%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>【笔记】JVM内存管理（三） - 垃圾收集器</title>
    <link href="https://soso.github.io/2018/07/26/note-JVM-memory-3/"/>
    <id>https://soso.github.io/2018/07/26/note-JVM-memory-3/</id>
    <published>2018-07-25T19:30:05.000Z</published>
    <updated>2018-07-26T10:11:30.000Z</updated>
    
    <content type="html"><![CDATA[<p>介绍7种垃圾收集器。</p><a id="more"></a><p><img src="/images/2018-07/HotSpot的7个收集器.jpg" alt="HotSpot的7个垃圾收集器"></p><p>HotSpot的7种作用于不同分代的收集器，如果两个收集器之间存在连线，就说明它们可以搭配使用。虚拟机所处的区域，则表示它是属于新生代收集器还是老年代收集器。Hotspot实现了如此多的收集器，正是因为目前并无完美的收集器出现，只是选择对具体应用最适合的收集器。</p><h1 id="相关概念"><a href="#相关概念" class="headerlink" title="相关概念"></a>相关概念</h1><h2 id="并行和并发"><a href="#并行和并发" class="headerlink" title="并行和并发"></a>并行和并发</h2><ul><li>并行（Parallel）：指多条垃圾收集线程并行工作，但此时用户线程仍然处于等待状态。</li><li>并发（Concurrent）：指用户线程与垃圾收集线程同时执行（但不一定是并行的，可能会交替执行），用户程序在继续运行。而垃圾收集程序运行在另一个CPU上。</li></ul><h2 id="吞吐量"><a href="#吞吐量" class="headerlink" title="吞吐量"></a>吞吐量</h2><p>吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值，即<br>吞吐量 = 运行用户代码时间 /（运行用户代码时间 + 垃圾收集时间）。<br>假设虚拟机总共运行了100分钟，其中垃圾收集花掉1分钟，那吞吐量就是99%。</p><h1 id="新生代收集器"><a href="#新生代收集器" class="headerlink" title="新生代收集器"></a>新生代收集器</h1><h2 id="Serial收集器"><a href="#Serial收集器" class="headerlink" title="Serial收集器"></a>Serial收集器</h2><p><img src="/images/2018-07/Serial收集器.png" alt="Serial收集器"></p><p>Serial（串行）收集器是最基本、发展历史最悠久的收集器，它是采用复制算法的新生代收集器，曾经（JDK 1.3.1之前）是虚拟机新生代收集的唯一选择。它是一个单线程收集器，只会使用一个CPU或一条收集线程去完成垃圾收集工作，更重要的是它在进行垃圾收集时，必须暂停其他所有的工作线程，直至Serial收集器收集结束为止（“Stop The World”）。这项工作是由虚拟机在后台自动发起和自动完成的，在用户不可见的情况下把用户正常工作的线程全部停掉，这对很多应用来说是难以接收的。</p><p>为了消除或减少工作线程因内存回收而导致的停顿，HotSpot虚拟机开发团队在JDK 1.3之后的Java发展历程中研发出了各种其他的优秀收集器，这些将在稍后介绍。但是这些收集器的诞生并不意味着Serial收集器已经“老而无用”，实际上到现在为止，它依然是HotSpot虚拟机运行在Client模式下的默认的新生代收集器。它也有着优于其他收集器的地方：简单而高效（与其他收集器的单线程相比），对于限定单个CPU的环境来说，Serial收集器由于没有线程交互的开销，专心做垃圾收集自然可以获得更高的单线程收集效率。</p><p>在用户的桌面应用场景中，分配给虚拟机管理的内存一般不会很大，收集几十兆甚至一两百兆的新生代（仅仅是新生代使用的内存，桌面应用基本不会再大了），停顿时间完全可以控制在几十毫秒最多一百毫秒以内，只要不频繁发生，这点停顿时间可以接收。所以，Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择。</p><h2 id="ParNew-收集器"><a href="#ParNew-收集器" class="headerlink" title="ParNew 收集器"></a>ParNew 收集器</h2><p><img src="/images/2018-07/ParNew收集器.png" alt="ParNew收集器"></p><p>ParNew收集器就是Serial收集器的多线程版本，它也是一个新生代收集器。除了使用多线程进行垃圾收集外，其余行为包括Serial收集器可用的所有控制参数、收集算法（复制算法）、Stop The World、对象分配规则、回收策略等与Serial收集器完全相同，两者共用了相当多的代码。</p><p>ParNew收集器除了使用多线程收集外，其他与Serial收集器相比并无太多创新之处，但它却是许多运行在Server模式下的虚拟机中首选的新生代收集器，其中有一个与性能无关的重要原因是，除了Serial收集器外，目前只有它能和CMS收集器（Concurrent Mark Sweep）配合工作，CMS收集器是JDK 1.5推出的一个具有划时代意义的收集器，具体内容将在稍后进行介绍。</p><p>ParNew 收集器在单CPU的环境中绝对不会有比Serial收集器有更好的效果，甚至由于存在线程交互的开销，该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证可以超越。在多CPU环境下，随着CPU的数量增加，它对于GC时系统资源的有效利用是很有好处的。它默认开启的收集线程数与CPU的数量相同，在CPU非常多的情况下可使用-XX:ParallerGCThreads参数设置</p><h2 id="Parallel-Scavenge收集器"><a href="#Parallel-Scavenge收集器" class="headerlink" title="Parallel Scavenge收集器"></a>Parallel Scavenge收集器</h2><p>Parallel Scavenge收集器也是一个并行的多线程新生代收集器，它也使用复制算法。Parallel Scavenge收集器的特点是它的关注点与其他收集器不同，CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间，而Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。</p><p>停顿时间越短就越适合需要与用户交互的程序，良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用CPU时间，尽快完成程序的运算任务，主要适合在后台运算而不需要太多交互的任务。</p><p>Parallel Scavenge收集器除了会显而易见地提供可以精确控制吞吐量的参数，还提供了一个参数<code>-XX:+UseAdaptiveSizePolicy</code>，这是一个开关参数，打开参数后，就不需要手工指定新生代的大小（-Xmn）、Eden和Survivor区的比例（-XX:SurvivorRatio）、晋升老年代对象年龄（-XX:PretenureSizeThreshold）等细节参数了，虚拟机会根据当前系统的运行情况收集性能监控信息，动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量，这种方式称为GC自适应的调节策略（GC Ergonomics）。自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。</p><p>另外值得注意的一点是，Parallel Scavenge收集器无法与CMS收集器配合使用，所以在JDK 1.6推出Parallel Old之前，如果新生代选择Parallel Scavenge收集器，老年代只有Serial Old收集器能与之配合使用。</p><h1 id="老年代收集器"><a href="#老年代收集器" class="headerlink" title="老年代收集器"></a>老年代收集器</h1><h2 id="Serial-Old收集器"><a href="#Serial-Old收集器" class="headerlink" title="Serial Old收集器"></a>Serial Old收集器</h2><p><img src="/images/2018-07/Serial收集器.png" alt="Serial收集器"></p><p>Serial Old 是 Serial收集器的老年代版本，它同样是一个单线程收集器，使用“标记-整理”（Mark-Compact）算法。</p><p>此收集器的主要意义也是在于给Client模式下的虚拟机使用。如果在Server模式下，它还有两大用途：</p><ul><li>在JDK1.5 以及之前版本（Parallel Old诞生以前）中与Parallel Scavenge收集器搭配使用。</li><li>作为CMS收集器的后备预案，在并发收集发生Concurrent Mode Failure时使用。</li></ul><h2 id="Parallel-Old收集器"><a href="#Parallel-Old收集器" class="headerlink" title="Parallel Old收集器"></a>Parallel Old收集器</h2><p><img src="/images/2018-07/ParNew收集器.png" alt="ParNew收集器"></p><p>Parallel Old收集器是Parallel Scavenge收集器的老年代版本，使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供的，在此之前，如果新生代选择了Parallel Scavenge收集器，老年代除了Serial Old以外别无选择，所以在Parallel Old诞生以后，“吞吐量优先”收集器终于有了比较名副其实的应用组合，在注重吞吐量以及CPU资源敏感的场合，都可以优先考虑Parallel Scavenge加Parallel Old收集器。Parallel Old收集器的工作流程与Parallel Scavenge相同。</p><h2 id="CMS收集器"><a href="#CMS收集器" class="headerlink" title="CMS收集器"></a>CMS收集器</h2><p><img src="/images/2018-07/CMS收集器.jpg" alt="CMS收集器"></p><p>CMS（Concurrent Mark Sweep）收集器是一种以获取最短回收停顿时间为目标的收集器，它非常符合那些集中在互联网站或者B/S系统的服务端上的Java应用，这些应用都非常重视服务的响应速度。Mark Sweep指的是“标记-清除”算法。</p><p>CMS收集器工作的整个流程分为以下4个步骤：</p><ul><li>初始标记：仅仅只是标记一下 GC Roots 能直接关联到的对象，速度很快，需要停顿。</li><li>并发标记：进行 GC Roots Tracing 的过程，它在整个回收过程中耗时最长，不需要停顿。</li><li>重新标记：为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录，需要停顿。</li><li>并发清除：不需要停顿。</li></ul><p>由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作，所以，从总体上来说，CMS收集器的内存回收过程是与用户线程一起并发执行的。</p><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><p>CMS是一款优秀的收集器，它的主要优点在名字上已经体现出来了：并发收集、低停顿，因此CMS收集器也被称为并发低停顿收集器（Concurrent Low Pause Collector）。</p><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><ul><li>对CPU资源非常敏感 其实，面向并发设计的程序都对CPU资源比较敏感。在并发阶段，它虽然不会导致用户线程停顿，但会因为占用了一部分线程（或者说CPU资源）而导致应用程序变慢，总吞吐量会降低。CMS默认启动的回收线程数是（CPU数量+3）/4，也就是当CPU在4个以上时，并发回收时垃圾收集线程不少于25%的CPU资源，并且随着CPU数量的增加而下降。但是当CPU不足4个时（比如2个），CMS对用户程序的影响就可能变得很大，如果本来CPU负载就比较大，还要分出一半的运算能力去执行收集器线程，就可能导致用户程序的执行速度忽然降低了50%，其实也让人无法接受。</li><li>无法处理浮动垃圾（Floating Garbage） 可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行着，伴随程序运行自然就还会有新的垃圾不断产生。这一部分垃圾出现在标记过程之后，CMS无法再当次收集中处理掉它们，只好留待下一次GC时再清理掉。这一部分垃圾就被称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行，那也就还需要预留有足够的内存空间给用户线程使用，因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集，需要预留一部分空间提供并发收集时的程序运作使用。</li><li>标记-清除算法导致的空间碎片 CMS是一款基于“标记-清除”算法实现的收集器，这意味着收集结束时会有大量空间碎片产生。空间碎片过多时，将会给大对象分配带来很大麻烦，往往出现老年代空间剩余，但无法找到足够大连续空间来分配当前对象。</li></ul><h1 id="G1收集器"><a href="#G1收集器" class="headerlink" title="G1收集器"></a>G1收集器</h1><p>G1（Garbage-First），它是一款面向服务端应用的垃圾收集器，在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。</p><p>Java 堆被分为新生代、老年代和永久代，其它收集器进行收集的范围都是整个新生代或者老年代，而 G1 可以直接对新生代和老年代一起回收。</p><p><img src="/images/2018-07/Java堆内存分代分配.png" alt="Java堆内存分代分配"></p><p>G1 把堆划分成多个大小相等的独立区域（Region），新生代和老年代不再物理隔离。</p><p><img src="/images/2018-07/G1收集器Java堆内存分配.png" alt="G1收集器Java堆内存分配"></p><p>通过引入 Region 的概念，从而将原来的一整块内存空间划分成多个的小空间，使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性，使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间（这两个值是通过过去回收的经验获得），并维护一个优先列表，每次根据允许的收集时间，优先回收价值最大的 Region。</p><p>每个 Region 都有一个 Remembered Set，用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set，在做可达性分析的时候就可以避免全堆扫描。</p><p><img src="/images/2018-07/G1收集器.jpg" alt="G1收集器"></p><p>如果不计算维护 Remembered Set 的操作，G1 收集器的运作大致可划分为以下几个步骤：</p><ul><li>初始标记: 仅仅只是标记一下GC Roots 能直接关联到的对象，并且修改TAMS（Nest Top Mark Start）的值，让下一阶段用户程序并发运行时，能在正确可以的Region中创建对象，此阶段需要停顿线程，但耗时很短。</li><li>并发标记: 从GC Root 开始对堆中对象进行可达性分析，找到存活对象，此阶段耗时较长，但可与用户程序并发执行。</li><li>最终标记: 为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录，虚拟机将这段时间对象变化记录在线程的Remembered Set Logs里面，最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中，这阶段需要停顿线程，但是可并行执行。</li><li>筛选回收 首先对各个Region中的回收价值和成本进行排序，根据用户所期望的GC停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行，但是因为只回收一部分Region，时间是用户可控制的，而且停顿用户线程将大幅度提高收集效率。</li></ul><h2 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h2><p><strong>空间整合：</strong><br>整体来看是基于“标记 - 整理”算法实现的收集器，从局部（两个 Region 之间）上来看是基于“复制”算法实现的，这意味着运行期间不会产生内存空间碎片。</p><p><strong>可预测的停顿：</strong><br>能让使用者明确指定在一个长度为 M 毫秒的时间片段内，消耗在 GC 上的时间不得超过 N 毫秒。</p><h2 id="更多"><a href="#更多" class="headerlink" title="更多"></a>更多</h2><p>更多关于G1收集器: <a href="http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html" target="_blank" rel="noopener">Getting Started with the G1 Garbage Collector</a></p><h1 id="比较"><a href="#比较" class="headerlink" title="比较"></a>比较</h1><table><thead><tr><th style="text-align:center">收集器</th><th style="text-align:center">单线程/并行</th><th style="text-align:center">串行/并发</th><th style="text-align:center">新生代/老年代</th><th style="text-align:center">收集算法</th><th style="text-align:center">目标</th><th style="text-align:center">适用场景</th></tr></thead><tbody><tr><td style="text-align:center"><strong>Serial</strong></td><td style="text-align:center">单线程</td><td style="text-align:center">串行</td><td style="text-align:center">新生代</td><td style="text-align:center">复制</td><td style="text-align:center">响应速度优先</td><td style="text-align:center">单 CPU 环境下的 Client 模式</td></tr><tr><td style="text-align:center"><strong>Serial Old</strong></td><td style="text-align:center">单线程</td><td style="text-align:center">串行</td><td style="text-align:center">老年代</td><td style="text-align:center">标记-整理</td><td style="text-align:center">响应速度优先</td><td style="text-align:center">单 CPU 环境下的 Client 模式、CMS 的后备预案</td></tr><tr><td style="text-align:center"><strong>ParNew</strong></td><td style="text-align:center">并行</td><td style="text-align:center">串行</td><td style="text-align:center">新生代</td><td style="text-align:center">复制算法</td><td style="text-align:center">响应速度优先</td><td style="text-align:center">多 CPU 环境时在 Server 模式下与 CMS 配合</td></tr><tr><td style="text-align:center"><strong>Parallel Scavenge</strong></td><td style="text-align:center">并行</td><td style="text-align:center">串行</td><td style="text-align:center">新生代</td><td style="text-align:center">复制算法</td><td style="text-align:center">吞吐量优先</td><td style="text-align:center">在后台运算而不需要太多交互的任务</td></tr><tr><td style="text-align:center"><strong>Parallel Old</strong></td><td style="text-align:center">并行</td><td style="text-align:center">串行</td><td style="text-align:center">老年代</td><td style="text-align:center">标记-整理</td><td style="text-align:center">吞吐量优先</td><td style="text-align:center">在后台运算而不需要太多交互的任务</td></tr><tr><td style="text-align:center"><strong>CMS</strong></td><td style="text-align:center">并行</td><td style="text-align:center">并发</td><td style="text-align:center">老年代</td><td style="text-align:center">标记-清除</td><td style="text-align:center">响应速度优先</td><td style="text-align:center">集中在互联网站或 B/S 系统服务端上的 Java 应用</td></tr><tr><td style="text-align:center"><strong>G1</strong></td><td style="text-align:center">并行</td><td style="text-align:center">并发</td><td style="text-align:center">新生代 + 老年代</td><td style="text-align:center">标记-整理 + 复制算法</td><td style="text-align:center">响应速度优先</td><td style="text-align:center">面向服务端应用，将来替换 CMS</td></tr></tbody></table><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p>周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.<br><a href="https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E8%99%9A%E6%8B%9F%E6%9C%BA.md" target="_blank" rel="noopener">https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E8%99%9A%E6%8B%9F%E6%9C%BA.md</a><br><a href="https://crowhawk.github.io/2017/08/15/jvm_3/" target="_blank" rel="noopener">https://crowhawk.github.io/2017/08/15/jvm_3/</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;介绍7种垃圾收集器。&lt;/p&gt;
    
    </summary>
    
      <category term="JVM" scheme="https://soso.github.io/categories/JVM/"/>
    
    
      <category term="JVM" scheme="https://soso.github.io/tags/JVM/"/>
    
      <category term="内存结构" scheme="https://soso.github.io/tags/%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84/"/>
    
      <category term="笔记" scheme="https://soso.github.io/tags/%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>【笔记】JVM内存管理（二） - GC算法</title>
    <link href="https://soso.github.io/2018/07/24/note-JVM-memory-2/"/>
    <id>https://soso.github.io/2018/07/24/note-JVM-memory-2/</id>
    <published>2018-07-24T12:18:54.000Z</published>
    <updated>2018-07-26T06:58:17.000Z</updated>
    
    <content type="html"><![CDATA[<p>垃圾回收主要是针对 Java 堆和方法区进行。<br><a id="more"></a></p><h1 id="判断一个对象是否存活"><a href="#判断一个对象是否存活" class="headerlink" title="判断一个对象是否存活"></a>判断一个对象是否存活</h1><h2 id="引用计数算法"><a href="#引用计数算法" class="headerlink" title="引用计数算法"></a>引用计数算法</h2><p>给对象添加一个引用计数器，当对象增加一个引用时计数器加 1，引用失效时计数器减 1。当计数器为0时即可认为对象无用了，可进行回收。</p><p>这种做法比较简单，但是这可能会引发循环引用的问题，当两个对象相互引用对方时，将导致引用计数器永远不为0，因此无法对它们进行回收。</p><p><img src="/images/2018-07/循环引用1.png" alt="循环引用1"></p><p>因为循环引用不好解决，所以Java 虚拟机不使用引用计数算法。</p><h2 id="可达性分析算法"><a href="#可达性分析算法" class="headerlink" title="可达性分析算法"></a>可达性分析算法</h2><p>可达性分析是基于图论的分析方法，它会找一组对象作为GC Root（根结点），并从根结点进行遍历，遍历结束后如果发现某个对象是不可达的（即从GC Root到此对象没有路径），那么它就会被标记为不可达对象，等待GC。比如下图中Object1、2、3、4都是存活的对象，而 Object5、6、7都是可回收对象：</p><p><img src="/images/2018-07/可达性分析.png" alt="可达性分析"></p><p>Java虚拟机使用该算法来判断对象是否可被回收，在Java中GC Roots 一般包含以下内容：</p><ul><li>虚拟机栈中引用的对象</li><li>本地方法栈中引用的对象</li><li>方法区中类静态属性引用的对象</li><li>方法区中的常量引用的对象</li></ul><h2 id="引用类型"><a href="#引用类型" class="headerlink" title="引用类型"></a>引用类型</h2><p>无论是通过引用计算算法判断对象的引用数量，还是通过可达性分析算法判断对象是否可达，判定对象是否可被回收都与引用有关。</p><p>Java 具有四种强度不同的引用类型。</p><h3 id="强引用"><a href="#强引用" class="headerlink" title="强引用"></a>强引用</h3><p>被强引用关联的对象不会被垃圾收集器回收。当内存空间不足，Java虚拟机宁愿抛出OutOfMemoryError错误，使程序异常终止，也不会靠随意回收具有强引用的对象来解决内存不足的问题。我们平时直接使用<code>new</code>关键字创建对象时就是对对象进行了强引用。</p><p><strong>代码示例</strong><br><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Object obj = <span class="hljs-keyword">new</span> Object();<br></code></pre></td></tr></table></figure></p><h3 id="软引用"><a href="#软引用" class="headerlink" title="软引用"></a>软引用</h3><p>如果一个对象只具有软引用，则内存空间足够，垃圾回收器就不会回收它；如果内存空间不足了，就会回收这些对象的内存。软引用可用来实现内存敏感的高速缓存。</p><p><strong>代码示例</strong><br><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">Object obj = <span class="hljs-keyword">new</span> Object();<br>SoftReference&lt;Object&gt; sf = <span class="hljs-keyword">new</span> SoftReference&lt;Object&gt;(obj);<br>obj = <span class="hljs-keyword">null</span>;  <span class="hljs-comment">// 使对象只被软引用关联</span><br></code></pre></td></tr></table></figure></p><h3 id="弱引用"><a href="#弱引用" class="headerlink" title="弱引用"></a>弱引用</h3><p>弱引用是一种生命周期比软引用更短的引用。在垃圾回收器线程扫描它所管辖的内存区域的过程中，一旦发现了只具有弱引用的对象，不管当前内存空间足够与否，都会回收它的内存。不过，由于垃圾回收器是一个优先级很低的线程，因此不一定会很快发现那些只具有弱引用的对象。</p><p>当你想引用一个对象，但是这个对象有自己的生命周期，你不想介入这个对象的生命周期，这时候你就是用弱引用。</p><p><strong>代码示例</strong><br><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">Object obj = <span class="hljs-keyword">new</span> Object();<br>WeakReference&lt;Object&gt; wf = <span class="hljs-keyword">new</span> WeakReference&lt;Object&gt;(obj);<br>obj = <span class="hljs-keyword">null</span>;<br></code></pre></td></tr></table></figure></p><p>Tomcat 中的 ConcurrentCache 就使用了 WeakHashMap 来实现缓存功能。ConcurrentCache 采取的是分代缓存，经常使用的对象放入 eden 中，而不常用的对象放入 longterm。eden 使用 ConcurrentHashMap 实现，longterm 使用 WeakHashMap，保证了不常使用的对象容易被回收。</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConcurrentCache</span>&lt;<span class="hljs-title">K</span>, <span class="hljs-title">V</span>&gt; </span>&#123;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> size;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map&lt;K, V&gt; eden;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map&lt;K, V&gt; longterm;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ConcurrentCache</span><span class="hljs-params">(<span class="hljs-keyword">int</span> size)</span> </span>&#123;<br>        <span class="hljs-keyword">this</span>.size = size;<br>        <span class="hljs-keyword">this</span>.eden = <span class="hljs-keyword">new</span> ConcurrentHashMap&lt;&gt;(size);<br>        <span class="hljs-keyword">this</span>.longterm = <span class="hljs-keyword">new</span> WeakHashMap&lt;&gt;(size);<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> V <span class="hljs-title">get</span><span class="hljs-params">(K k)</span> </span>&#123;<br>        V v = <span class="hljs-keyword">this</span>.eden.get(k);<br>        <span class="hljs-keyword">if</span> (v == <span class="hljs-keyword">null</span>) &#123;<br>            v = <span class="hljs-keyword">this</span>.longterm.get(k);<br>            <span class="hljs-keyword">if</span> (v != <span class="hljs-keyword">null</span>)<br>                <span class="hljs-keyword">this</span>.eden.put(k, v);<br>        &#125;<br>        <span class="hljs-keyword">return</span> v;<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">put</span><span class="hljs-params">(K k, V v)</span> </span>&#123;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.eden.size() &gt;= size) &#123;<br>            <span class="hljs-keyword">this</span>.longterm.putAll(<span class="hljs-keyword">this</span>.eden);<br>            <span class="hljs-keyword">this</span>.eden.clear();<br>        &#125;<br>        <span class="hljs-keyword">this</span>.eden.put(k, v);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="虚引用"><a href="#虚引用" class="headerlink" title="虚引用"></a>虚引用</h3><p>虚引用不同于其余三种引用，虚引用不会影响对象的生命周期，也无法通过虚引用获得对象的一个实例；如果一个对象仅持有虚引用，那么它就和没有任何引用一样，在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动，它必须和引用队列联合使用。</p><p><strong>代码示例</strong><br><figure class="hljs highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs undefined">final ReferenceQueue&lt;Object&gt; referenceQueue = new ReferenceQueue&lt;Object&gt;();<br>Object obj = new Object();<br>PhantomReference&lt;Object&gt; pf = new PhantomReference&lt;Object&gt;(obj, referenceQueue);<br>obj = null;<br></code></pre></td></tr></table></figure></p><h3 id="引用队列"><a href="#引用队列" class="headerlink" title="引用队列"></a>引用队列</h3><p>在检测到适当的可到达性更改后，垃圾回收器会将已注册的引用对象添加到引用队列中。所以当一个对象被回收，需要进行额外的处理时，就需要使用引用队列了。</p><p>从<a href="http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/cf44386c8fe3/src/share/classes/java/lang/ref/ReferenceQueue.java" target="_blank" rel="noopener">源码</a>可以看到，<code>ReferenceQueue</code>的实现更像是栈，它有有入队(enqueue)和出队(poll&amp;remove，其中remove阻塞等待提取队列元素)的API。当引用对象被回收时会将对象进行入栈操作，然后我们可以通过出栈操作来进行对象被回收时的最后操作。</p><p><strong>代码示例</strong><br><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java">Thread thread = <span class="hljs-keyword">new</span> Thread(() -&gt; &#123;<br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-keyword">int</span> cnt = <span class="hljs-number">0</span>;<br>        WeakReference&lt;Object&gt; k;<br>        <span class="hljs-keyword">while</span>((k = (WeakReference) rq.remove()) != <span class="hljs-keyword">null</span>) &#123;<br>            System.out.println((cnt++) + <span class="hljs-string">"回收了:"</span> + k);<br>        &#125;<br>    &#125; <span class="hljs-keyword">catch</span>(InterruptedException e) &#123;<br>        <span class="hljs-comment">//结束循环</span><br>    &#125;<br>&#125;);<br>thread.setDaemon(<span class="hljs-keyword">true</span>);<br>thread.start();<br></code></pre></td></tr></table></figure></p><h2 id="方法区的回收"><a href="#方法区的回收" class="headerlink" title="方法区的回收"></a>方法区的回收</h2><p>因为方法区主要存放永久代对象，而永久代对象的回收率比新生代差很多，因此在方法区上进行回收性价比不高。</p><p>对方法区的回收主要是对常量池的回收和对类的卸载。类的卸载条件很多，需要满足以下三个条件，并且满足了也不一定会被卸载：</p><ul><li>该类所有的实例都已经被回收，也就是 Java 堆中不存在该类的任何实例。</li><li>加载该类的 ClassLoader 已经被回收。</li><li>该类对应的 java.lang.Class 对象没有在任何地方被引用，也就无法在任何地方通过反射访问该类方法。</li></ul><p>可以通过<code>-Xnoclassgc</code>参数来控制是否对类进行卸载。</p><p>在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGi 这类频繁自定义<code>ClassLoader</code>的场景都需要虚拟机具备类卸载功能，以保证不会出现内存溢出。</p><p>在JDK1.8中，JVM摒弃了永久代，用元空间来作为方法区的实现。元空间的内存管理由元空间虚拟机来完成。先前，对于类的元数据我们需要不同的垃圾回收器进行处理，现在只需要执行元空间虚拟机的C++代码即可完成。在元空间中，类和其元数据的生命周期和其对应的类加载器是相同的。话句话说，只要类加载器存活，其加载的类的元数据也是存活的，因而不会被回收掉。</p><h2 id="finalize"><a href="#finalize" class="headerlink" title="finalize()"></a>finalize()</h2><p><code>finalize()</code>类似C++的析构函数，用来做关闭外部资源等工作。但是<code>try-finally</code>等方式可以做的更好，并且该方法运行代价高昂，不确定性大，无法保证各个对象的调用顺序，因此最好不要使用。</p><p>当一个对象可被回收时，如果需要执行该对象的<code>finalize()</code>方法，那么就有可能通过在该方法中让对象重新被引用，从而实现自救。自救只能进行一次，如果回收的对象之前调用了<code>finalize()</code>方法自救，后面回收时不会调用<code>finalize()</code>方法。</p><h1 id="垃圾收集算法"><a href="#垃圾收集算法" class="headerlink" title="垃圾收集算法"></a>垃圾收集算法</h1><h2 id="标记－清除"><a href="#标记－清除" class="headerlink" title="标记－清除"></a>标记－清除</h2><p><img src="/images/2018-07/标记-清除算法.jpg" alt="标记-清除算法"></p><p>标记－清除（Mark-Sweep）算法是最基础的垃圾收集算法，后续的收集算法都是基于它的思路并对其不足进行改进而得到的。算法分成“标记”、“清除”两个阶段：首先标记出所有需要回收的对象，在标记完成后统一回收所有被标记的对象。</p><p>缺点：</p><ul><li>空间问题，标记清除之后会产生大量不连续的内存碎片，空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时，无法找到足够的连续内存而不得不触发另一次垃圾收集动作。</li><li>效率问题，因为内存碎片的存在，操作会变得更加费时，因为查找下一个可用空闲块已不再是一个简单操作。</li></ul><h2 id="复制"><a href="#复制" class="headerlink" title="复制"></a>复制</h2><p><img src="/images/2018-07/复制算法.jpg" alt="复制算法"></p><p>将可用内存按容量分成大小相等的两块，每次只使用其中的一块。当这一块内存用完，就将还存活着的对象复制到另一块上面，然后再把已使用过的内存空间一次清理掉。</p><p>缺点：只能有效地使用一半内存。</p><p>现在的商业虚拟机都采用这种收集算法来回收新生代，但是并不是将内存划分为大小相等的两块，而是分为一块较大的 Eden 空间和两块较小的 Survivor 空间，每次使用 Eden 空间和其中一块 Survivor。在回收时，将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上，最后清理 Eden 和使用过的那一块 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1，保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活，那么一块 Survivor 空间就不够用了，此时需要依赖于老年代进行分配担保，也就是借用老年代的空间存储放不下的对象。</p><h2 id="标记－整理"><a href="#标记－整理" class="headerlink" title="标记－整理"></a>标记－整理</h2><p><img src="/images/2018-07/标记-整理算法.png" alt="标记-整理算法"></p><p>复制算法在对象存活率较高时要进行较多的复制操作，效率将会变低。更关键的是：如果不想浪费50%的空间，就需要有额外的空间进行分配担保，以应对被使用的内存中所有对象都100%存活的极端情况，所以在老年代一般不能直接选用复制算法。</p><p>根据老年代的特点，标记－整理（Mark-Compact）算法被提出来，主要思想为：此算法的标记过程与标记－清除算法一样，但后续步骤不是直接对可回收对象进行清理，而是让所有存活的对象都向一端移动，然后直接清理掉边界以外的内存。</p><h2 id="分代收集"><a href="#分代收集" class="headerlink" title="分代收集"></a>分代收集</h2><p>当前商业虚拟机的垃圾收集都采用分代收集（Generational Collection）算法，此算法相较于前几种没有什么新的特征，主要思想为：根据对象存活周期的不同将内存划分为几块，一般是把Java堆分为新生代和老年代，这样就可以根据各个年代的特点采用最适合的收集算法：</p><ul><li>新生代 在新生代中，每次垃圾收集时都发现有大批对象死去，只有少量存活，那就选用复制算法，只需要付出少量存活对象的复制成本就可以完成收集。</li><li>老年代 在老年代中，因为对象存活率高、没有额外空间对它进行分配担保，就必须使用“标记-清除”或“标记-整理”算法来进行回收。</li></ul><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p>周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.<br><a href="https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E8%99%9A%E6%8B%9F%E6%9C%BA.md" target="_blank" rel="noopener">https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E8%99%9A%E6%8B%9F%E6%9C%BA.md</a><br><a href="https://www.sczyh30.com/posts/Java/java-reference-type/" target="_blank" rel="noopener">https://www.sczyh30.com/posts/Java/java-reference-type/</a><br><a href="https://blog.csdn.net/u011936381/article/details/11709245" target="_blank" rel="noopener">https://blog.csdn.net/u011936381/article/details/11709245</a><br><a href="https://www.jianshu.com/p/73260a46291c" target="_blank" rel="noopener">https://www.jianshu.com/p/73260a46291c</a><br><a href="https://crowhawk.github.io/2017/08/10/jvm_2/" target="_blank" rel="noopener">https://crowhawk.github.io/2017/08/10/jvm_2/</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;垃圾回收主要是针对 Java 堆和方法区进行。&lt;br&gt;
    
    </summary>
    
      <category term="JVM" scheme="https://soso.github.io/categories/JVM/"/>
    
    
      <category term="JVM" scheme="https://soso.github.io/tags/JVM/"/>
    
      <category term="内存结构" scheme="https://soso.github.io/tags/%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84/"/>
    
      <category term="笔记" scheme="https://soso.github.io/tags/%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>【笔记】JVM内存管理（一） - 运行时内存区域</title>
    <link href="https://soso.github.io/2018/07/24/note-JVM-memory-1/"/>
    <id>https://soso.github.io/2018/07/24/note-JVM-memory-1/</id>
    <published>2018-07-23T19:54:37.000Z</published>
    <updated>2018-07-26T06:58:26.000Z</updated>
    
    <content type="html"><![CDATA[<p>JVM的内存区域分为程序计数器、虚拟机栈、本地方法栈等。<br><a id="more"></a></p><p><img src="/images/2018-07/JVM内存区域.png" alt="JVM内存区域"></p><h1 id="程序计数器"><a href="#程序计数器" class="headerlink" title="程序计数器"></a>程序计数器</h1><p>程序计数器主要用于存储正在执行的虚拟机字节码指令的地址（不是直接存储指令）。Java虚拟机会按照PC的地址从内存中读取第一条指令，每一条指令执行时，CPU会自动修改PC的量至下一条指令的地址，指令之间的跳转离不开PC。</p><p>Java虚拟机允许多个线程同时执行指令。如果有多个线程正在执行指令，那么每个线程都会有一个程序计数器，它是线程私有的。在任意时刻，一个线程只允许执行一个方法的代码。每当执行到一条Java方法的指令时，程序计数器保存当前执行字节码的地址；若执行的为native方法，则PC的值为undefined。</p><h1 id="虚拟机栈"><a href="#虚拟机栈" class="headerlink" title="虚拟机栈"></a>虚拟机栈</h1><p><img src="/images/2018-07/栈帧.png" alt="栈帧"></p><p>每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程，就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。</p><p>Java 虚拟机栈在方法调用和返回中也扮演了很重要的角色。因为除了栈帧的入栈和出栈之外，Java虚拟机栈不会再受其它因素的影响，因此栈帧可在系统的堆上分配内存（注意，是系统的Heap而不是Java Heap）。Java虚拟机栈所使用的内存不需要保证是连续的。</p><p>可以通过 -Xss 这个虚拟机参数来指定一个程序的 Java 虚拟机栈内存大小：<br><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">java -Xss=<span class="hljs-number">512</span>M HackTheJava<br></code></pre></td></tr></table></figure></p><p>该区域可能抛出以下异常:</p><ul><li>当线程请求的栈深度超过最大值，会抛出 <code>StackOverflowError</code> 异常；</li><li>栈进行动态扩展时如果无法申请到足够内存，会抛出 <code>OutOfMemoryError</code> 异常。</li></ul><h1 id="本地方法栈"><a href="#本地方法栈" class="headerlink" title="本地方法栈"></a>本地方法栈</h1><p>本地方法栈和Java虚拟机栈的作用相似，Java虚拟机栈执行的是字节码，而本地方法栈执行的是native方法。本地方法栈使用传统的栈（C Stack）来支持native方法。在HotSpot JVM中Java虚拟机栈和本地方法栈合二为一。</p><p><img src="/images/2018-07/JNI工作原理图.gif" alt="JNI"></p><h1 id="Java堆"><a href="#Java堆" class="headerlink" title="Java堆"></a>Java堆</h1><p>在JVM中，Java堆是可供各线程共享的运行时内存区域，是Java虚拟机所管理的内存区域中最大的一块，所有对象实例都在这里分配内存。同时，Java堆也是发生GC收集的主要区域。现代的垃圾收集器基本都是采用分代收集算法，主要思想是针对不同的对象采取不同的垃圾回收算法。虚拟机把Java堆分成以下三块：</p><ul><li>新生代（Young Generation）</li><li>老年代（Old Generation）</li><li>永久代（Permanent Generation）</li></ul><p>当一个对象被创建时，它首先进入新生代，之后有可能被转移到老年代中。<br>新生代存放着大量的生命很短的对象，因此新生代在三个区域中垃圾回收的频率最高。为了更高效地进行垃圾回收，把新生代继续划分成以下三个空间：</p><ul><li>Eden（伊甸园）</li><li>From Survivor（幸存者）</li><li>To Survivor</li></ul><p><img src="/images/2018-07/heap区内存.gif" alt="heap区内存"></p><p>Java 堆不需要连续内存，并且可以动态增加其内存，增加失败会抛出 <code>OutOfMemoryError</code> 异常。</p><p>可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的 Java 堆内存大小，第一个参数设置初始值，第二个参数设置最大值。<br><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">java -Xms=<span class="hljs-number">1</span>M -Xmx=<span class="hljs-number">2</span>M HackTheJava<br></code></pre></td></tr></table></figure></p><h1 id="方法区"><a href="#方法区" class="headerlink" title="方法区"></a>方法区</h1><p>方法区是线程共享的，它储存了每一个类的结构信息，比如运行时常量池（runtime constant pool）、字段和方法数据、构造函数和普通方法的字节码内容，还包括一些初始化的时候用到的特殊方法。方法区是堆的逻辑部分。</p><p>和 Java 堆一样不需要连续的内存，并且可以动态扩展，动态扩展失败一样会抛出 <code>OutOfMemoryError</code> 异常。</p><p>对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载，但是一般比较难实现。</p><p>在JDK1.7及以前的HotSpot JVM中，方法区位于永久代（Permanent Generation，PermGen）中。由于永久代内可能会发生内存泄露或溢出等问题而导致的java.lang.OutOfMemoryError: PermGen ，JEP小组从JDK1.7开始就筹划移除永久代，并且在JDK 1.7中把字符串常量，符号引用等移出了永久代。到了Java 8，永久代被彻底地移出了JVM，取而代之的是元空间（Metaspace）。</p><h1 id="运行时常量池"><a href="#运行时常量池" class="headerlink" title="运行时常量池"></a>运行时常量池</h1><p>运行时常量池是class文件中每一个类或接口的常量池表的运行时表示形式，是方法区的一部分。它包括了若干种不同的常量。常量池表存放编译器生成的各种字面量和符号引用，这部分内容将在类加载后进入方法区的运行时常量池中存放。运行时常量池具有动态性，运行期间也可以将新的量放到运行时常量池中，典型的应用是<code>String</code>类的<code>intern()</code>方法。</p><p>JDK 1.7开始，字符串常量和符号引用等就被移出永久代：</p><ul><li>符号引用迁移至系统堆内存(Native Heap)</li><li>字符串字面量迁移至Java堆(Java Heap)</li></ul><h1 id="直接内存"><a href="#直接内存" class="headerlink" title="直接内存"></a>直接内存</h1><p>在 JDK 1.4 中新加入了 NIO 类，它可以使用 Native 函数库直接分配堆外内存，然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能，因为避免了在 Java 堆和 Native 堆中来回复制数据。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p>周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.<br><a href="https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E8%99%9A%E6%8B%9F%E6%9C%BA.md" target="_blank" rel="noopener">https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E8%99%9A%E6%8B%9F%E6%9C%BA.md</a><br><a href="https://www.sczyh30.com/posts/Java/jvm-memory/#%E6%96%B9%E6%B3%95%E5%8C%BA" target="_blank" rel="noopener">https://www.sczyh30.com/posts/Java/jvm-memory/#%E6%96%B9%E6%B3%95%E5%8C%BA</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;JVM的内存区域分为程序计数器、虚拟机栈、本地方法栈等。&lt;br&gt;
    
    </summary>
    
      <category term="JVM" scheme="https://soso.github.io/categories/JVM/"/>
    
    
      <category term="JVM" scheme="https://soso.github.io/tags/JVM/"/>
    
      <category term="内存结构" scheme="https://soso.github.io/tags/%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84/"/>
    
  </entry>
  
  <entry>
    <title>【转】深入理解Java类加载器(ClassLoader)</title>
    <link href="https://soso.github.io/2018/07/23/reproduce-JVM-load-class/"/>
    <id>https://soso.github.io/2018/07/23/reproduce-JVM-load-class/</id>
    <published>2018-07-22T16:54:37.000Z</published>
    <updated>2018-08-08T06:25:24.000Z</updated>
    
    <content type="html"><![CDATA[<p>介绍了JVM类加载的原理。<br><a id="more"></a></p><h1 id="类加载的机制的层次结构"><a href="#类加载的机制的层次结构" class="headerlink" title="类加载的机制的层次结构"></a>类加载的机制的层次结构</h1><p>每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑，这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件，”.class”文件中保存着Java代码经转换后的虚拟机指令，当需要使用某个类时，虚拟机将会加载它的”.class”文件，并创建对应的class对象，将class文件加载到虚拟机的内存，这个过程称为类加载，这里我们需要了解一下类加载的过程，如下：</p><p><img src="/images/2018-07/类加载过程.png" alt="类加载过程"></p><ul><li>加载：类加载过程的一个阶段：通过一个类的完全限定查找此类字节码文件，并利用字节码文件创建一个Class对象</li><li>验证：目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求，不会危害虚拟机自身安全。主要包括四种验证，文件格式验证，元数据验证，字节码验证，符号引用验证。</li><li>准备：为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0，至于5的值将在初始化时赋值)，这里不包含用final修饰的static，因为final在编译的时候就会分配了，注意这里不会为实例变量分配初始化，类变量会分配在方法区中，而实例变量是会随着对象一起分配到Java堆中。</li><li>解析：主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标，可以是任何字面量，而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析，字段解析，类方法解析，接口方法解析(这里涉及到字节码变量的引用，如需更详细了解，可参考《深入Java虚拟机》)。</li><li>初始化：类加载最后阶段，若该类具有超类，则对其进行初始化，执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值，成员变量也将被初始化)。</li></ul><p>这便是类加载的5个过程，而类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中，然后转换为一个与目标类对应的<code>java.lang.Class</code>对象实例，在虚拟机提供了3种类加载器，引导（Bootstrap）类加载器、扩展（Extension）类加载器、系统（System）类加载器（也称应用类加载器），下面分别介绍</p><h2 id="启动（Bootstrap）类加载器"><a href="#启动（Bootstrap）类加载器" class="headerlink" title="启动（Bootstrap）类加载器"></a>启动（Bootstrap）类加载器</h2><p>启动类加载器主要加载的是JVM自身需要的类，这个类加载使用C++语言实现的，是虚拟机自身的一部分，它负责将 &lt;JAVA_HOME&gt;/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中，注意必由于虚拟机是按照文件名识别加载jar包的，如rt.jar，如果文件名不被虚拟机识别，即使把jar包丢到lib目录下也是没有作用的(出于安全考虑，Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。</p><h2 id="扩展（Extension）类加载器"><a href="#扩展（Extension）类加载器" class="headerlink" title="扩展（Extension）类加载器"></a>扩展（Extension）类加载器</h2><p>扩展类加载器是指Sun公司(已被Oracle收购)实现的<code>sun.misc.Launcher$ExtClassLoader</code>类，由Java语言实现的，是Launcher的静态内部类，它负责加载<code>&lt;JAVA_HOME&gt;/lib/ext</code>目录下或者由系统变量<code>-D java.ext.dir</code>指定位路径中的类库，开发者可以直接使用标准扩展类加载器。</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//ExtClassLoader类中获取路径的代码</span><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> File[] getExtDirs() &#123;<br>     <span class="hljs-comment">//加载&lt;JAVA_HOME&gt;/lib/ext目录中的类库</span><br>     String s = System.getProperty(<span class="hljs-string">"java.ext.dirs"</span>);<br>     File[] dirs;<br>     <span class="hljs-keyword">if</span> (s != <span class="hljs-keyword">null</span>) &#123;<br>         StringTokenizer st =<br>             <span class="hljs-keyword">new</span> StringTokenizer(s, File.pathSeparator);<br>         <span class="hljs-keyword">int</span> count = st.countTokens();<br>         dirs = <span class="hljs-keyword">new</span> File[count];<br>         <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; count; i++) &#123;<br>             dirs[i] = <span class="hljs-keyword">new</span> File(st.nextToken());<br>         &#125;<br>     &#125; <span class="hljs-keyword">else</span> &#123;<br>         dirs = <span class="hljs-keyword">new</span> File[<span class="hljs-number">0</span>];<br>     &#125;<br>     <span class="hljs-keyword">return</span> dirs;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="系统（System）类加载器"><a href="#系统（System）类加载器" class="headerlink" title="系统（System）类加载器"></a>系统（System）类加载器</h2><p>  也称应用程序加载器是指 Sun公司实现的<code>sun.misc.Launcher$AppClassLoader</code>。它负责加载系统类路径<code>java -classpath</code>或<code>-D java.class.path</code>指定路径下的类库，也就是我们经常用到的classpath路径，开发者可以直接使用系统类加载器，一般情况下该类加载是程序中默认的类加载器，通过<code>ClassLoader</code>的<code>getSystemClassLoader()</code>方法可以获取到该类加载器。<br>　 在Java的日常应用程序开发中，类的加载几乎是由上述3种类加载器相互配合执行的，在必要时，我们还可以自定义类加载器，需要注意的是，Java虚拟机对class文件采用的是按需加载的方式，也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象，而且加载某个类的class文件时，Java虚拟机采用的是双亲委派模式即把请求交由父类处理，它一种任务委派模式，下面我们进一步了解它。</p><h1 id="理解双亲委派模式"><a href="#理解双亲委派模式" class="headerlink" title="理解双亲委派模式"></a>理解双亲委派模式</h1><h2 id="双亲委派模式工作原理"><a href="#双亲委派模式工作原理" class="headerlink" title="双亲委派模式工作原理"></a>双亲委派模式工作原理</h2><p>双亲委派模式要求除了顶层的启动类加载器外，其余的类加载器都应当有自己的父类加载器，请注意双亲委派模式中的父子关系并非通常所说的类继承关系，而是采用组合关系来复用父类加载器的相关代码，类加载器间的关系如下：</p><p><img src="/images/2018-07/类加载器关系图.png" alt="类加载器关系图"></p><p>双亲委派模式是在Java 1.2后引入的，其工作原理的是，如果一个类加载器收到了类加载请求，它并不会自己先去加载，而是把这个请求委托给父类的加载器去执行，如果父类加载器还存在其父类加载器，则进一步向上委托，依次递归，请求最终将到达顶层的启动类加载器，如果父类加载器可以完成类加载任务，就成功返回，倘若父类加载器无法完成此加载任务，子加载器才会尝试自己去加载，这就是双亲委派模式，即每个儿子都很懒，每次有活就丢给父亲去干，直到父亲说这件事我也干不了时，儿子自己想办法去完成，这不就是传说中的实力坑爹啊？那么采用这种模式有啥用呢?</p><h2 id="双亲委派模式优势"><a href="#双亲委派模式优势" class="headerlink" title="双亲委派模式优势"></a>双亲委派模式优势</h2><p>采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系，通过这种层级关可以避免类的重复加载，当父亲已经加载了该类时，就没有必要子<code>ClassLoader</code>再加载一次。其次是考虑到安全因素，java核心api中定义类型不会被随意替换，假设通过网络传递一个名为<code>java.lang.Integer</code>的类，通过双亲委托模式传递到启动类加载器，而启动类加载器在核心Java API发现这个名字的类，发现该类已被加载，并不会重新加载网络传递的过来的<code>java.lang.Integer</code>，而直接返回已加载过的Integer.class，这样便可以防止核心API库被随意篡改。可能你会想，如果我们在classpath路径下自定义一个名为<code>java.lang.SingleInterge</code>类(该类是胡编的)呢？该类并不存在java.lang中，经过双亲委托模式，传递到启动类加载器中，由于父类加载器路径下并没有该类，所以不会加载，将反向委托给子类加载器加载，最终会通过系统类加载器加载该类。但是这样做是不允许，因为java.lang是核心API包，需要访问权限，强制加载将会报出如下异常</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">java.lang.SecurityException: Prohibited <span class="hljs-keyword">package</span> name: java.lang<br></code></pre></td></tr></table></figure><p>所以无论如何都无法加载成功的。下面我们从代码层面了解几个Java中定义的类加载器及其双亲委派模式的实现，它们类图关系如下:</p><p><img src="/images/2018-07/类图关系.png" alt="类图关系"></p><p>从图可以看出顶层的类加载器是<code>ClassLoader</code>类，它是一个抽象类，其后所有的类加载器都继承自<code>ClassLoader</code>（不包括启动类加载器），这里我们主要介绍<code>ClassLoader</code>中几个比较重要的方法。</p><ul><li><code>loadClass(String)</code><br>该方法加载指定名称（包括包名）的二进制类型，该方法在JDK1.2之后不再建议用户重写但用户可以直接调用该方法，<code>loadClass()</code>方法是ClassLoader类自己实现的，该方法中的逻辑就是双亲委派模式的实现，其源码如下，<code>loadClass(String name, boolean resolve)</code>是一个重载方法，resolve参数代表是否生成class对象的同时进行解析相关操作：<figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">protected</span> Class&lt;?&gt; loadClass(String name, <span class="hljs-keyword">boolean</span> resolve)<br>      <span class="hljs-keyword">throws</span> ClassNotFoundException<br>  &#123;<br>      <span class="hljs-keyword">synchronized</span> (getClassLoadingLock(name)) &#123;<br>          <span class="hljs-comment">// 先从缓存查找该class对象，找到就不用重新加载</span><br>          Class&lt;?&gt; c = findLoadedClass(name);<br>          <span class="hljs-keyword">if</span> (c == <span class="hljs-keyword">null</span>) &#123;<br>              <span class="hljs-keyword">long</span> t0 = System.nanoTime();<br>              <span class="hljs-keyword">try</span> &#123;<br>                  <span class="hljs-keyword">if</span> (parent != <span class="hljs-keyword">null</span>) &#123;<br>                      <span class="hljs-comment">//如果找不到，则委托给父类加载器去加载</span><br>                      c = parent.loadClass(name, <span class="hljs-keyword">false</span>);<br>                  &#125; <span class="hljs-keyword">else</span> &#123;<br>                  <span class="hljs-comment">//如果没有父类，则委托给启动加载器去加载</span><br>                      c = findBootstrapClassOrNull(name);<br>                  &#125;<br>              &#125; <span class="hljs-keyword">catch</span> (ClassNotFoundException e) &#123;<br>                  <span class="hljs-comment">// ClassNotFoundException thrown if class not found</span><br>                  <span class="hljs-comment">// from the non-null parent class loader</span><br>              &#125;<br><br>              <span class="hljs-keyword">if</span> (c == <span class="hljs-keyword">null</span>) &#123;<br>                  <span class="hljs-comment">// If still not found, then invoke findClass in order</span><br>                  <span class="hljs-comment">// 如果都没有找到，则通过自定义实现的findClass去查找并加载</span><br>                  c = findClass(name);<br><br>                  <span class="hljs-comment">// this is the defining class loader; record the stats</span><br>                  sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);<br>                  sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);<br>                  sun.misc.PerfCounter.getFindClasses().increment();<br>              &#125;<br>          &#125;<br>          <span class="hljs-keyword">if</span> (resolve) &#123;<span class="hljs-comment">//是否需要在加载时进行解析</span><br>              resolveClass(c);<br>          &#125;<br>          <span class="hljs-keyword">return</span> c;<br>      &#125;<br>  &#125;<br></code></pre></td></tr></table></figure></li></ul><ul><li>正如loadClass方法所展示的，当类加载请求到来时，先从缓存中查找该类对象，如果存在直接返回，如果不存在则交给该类加载去的父加载器去加载，倘若没有父加载则交给顶级启动类加载器去加载，最后倘若仍没有找到，则使用findClass()方法去加载（关于findClass()稍后会进一步介绍）。从loadClass实现也可以知道如果不想重新定义加载类的规则，也没有复杂的逻辑，只想在运行时加载自己指定的类，那么我们可以直接使用this.getClass().getClassLoder.loadClass(“className”)，这样就可以直接调用ClassLoader的loadClass方法获取到class对象。</li></ul><ul><li><p><code>findClass(String)</code><br>在JDK1.2之前，在自定义类加载时，总会去继承<code>ClassLoader</code>类并重写loadClass()方法，从而实现自定义的类加载类，但是在JDK1.2之后已不再建议用户去覆盖<code>loadClass()</code>方法，而是建议把自定义的类加载逻辑写在<code>findClass()</code>方法中，从前面的分析可知，<code>findClass()</code>方法是在<code>loadClass()</code>方法中被调用的，当<code>loadClass()</code>方法中父加载器加载失败后，则会调用自己的<code>findClass()</code>方法来完成类加载，这样就可以保证自定义的类加载器也符合双亲委托模式。需要注意的是<code>ClassLoader</code>类中并没有实现<code>findClass()</code>方法的具体代码逻辑，取而代之的是抛出<code>ClassNotFoundException</code>异常，同时应该知道的是findClass方法通常是和defineClass方法一起使用的(稍后会分析)，<code>ClassLoader</code>类中<code>findClass()</code>方法源码如下：</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//直接抛出异常</span><br><span class="hljs-keyword">protected</span> Class&lt;?&gt; findClass(String name) <span class="hljs-keyword">throws</span> ClassNotFoundException &#123;<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ClassNotFoundException(name);<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p><code>defineClass(byte[] b, int off, int len)</code><br><code>defineClass()</code>方法是用来将byte字节流解析成JVM能够识别的Class对象(<code>ClassLoader</code>中已实现该方法逻辑)，通过这个方法不仅能够通过class文件实例化class对象，也可以通过其他方式实例化class对象，如通过网络接收一个类的字节码，然后转换为byte字节流创建对应的Class对象，<code>defineClass()</code>方法通常与<code>findClass()</code>方法一起使用，一般情况下，在自定义类加载器时，会直接覆盖<code>ClassLoader的findClass()</code>方法并编写加载规则，取得要加载类的字节码后转换成流，然后调用<code>defineClass()</code>方法生成类的Class对象，简单例子如下：</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">protected</span> Class&lt;?&gt; findClass(String name) <span class="hljs-keyword">throws</span> ClassNotFoundException &#123;<br>      <span class="hljs-comment">// 获取类的字节数组</span><br>      <span class="hljs-keyword">byte</span>[] classData = getClassData(name);  <br>      <span class="hljs-keyword">if</span> (classData == <span class="hljs-keyword">null</span>) &#123;<br>          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ClassNotFoundException();<br>      &#125; <span class="hljs-keyword">else</span> &#123;<br>          <span class="hljs-comment">//使用defineClass生成class对象</span><br>          <span class="hljs-keyword">return</span> defineClass(name, classData, <span class="hljs-number">0</span>, classData.length);<br>      &#125;<br>  &#125;<br></code></pre></td></tr></table></figure></li></ul><ul><li>需要注意的是，如果直接调用defineClass()方法生成类的Class对象，这个类的Class对象并没有解析(也可以理解为链接阶段，毕竟解析是链接的最后一步)，其解析操作需要等待初始化阶段进行。</li></ul><ul><li><code>resolveClass(Class≺?≻ c)</code><br>使用该方法可以使用类的Class对象创建完成也同时被解析。前面我们说链接阶段主要是对字节码进行验证，为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用。</li></ul><p>上述4个方法是<code>ClassLoader</code>类中的比较重要的方法，也是我们可能会经常用到的方法。接看<code>SercureClassLoader</code>扩展了 <code>ClassLoader</code>，新增了几个与使用相关的代码源(对代码源的位置及其证书的验证)和权限定义类验证(主要指对class源码的访问权限)的方法，一般我们不会直接跟这个类打交道，更多是与它的子类<code>URLClassLoader</code>有所关联，前面说过，<code>ClassLoader</code>是一个抽象类，很多方法是空的没有实现，比如 <code>findClass()</code>、<code>findResource()</code>等。而<code>URLClassLoader</code>这个实现类为这些方法提供了具体的实现，并新增了<code>URLClassPath</code>类协助取得Class字节码流等功能，在编写自定义类加载器时，如果没有太过于复杂的需求，可以直接继承<code>URLClassLoader</code>类，这样就可以避免自己去编写<code>findClass()</code>方法及其获取字节码流的方式，使自定义类加载器编写更加简洁，下面是<code>URLClassLoader</code>的类图(利用IDEA生成的类图)</p><p><img src="/images/2018-07/URLClassLoader类图.png" alt="URLClassLoader类图"></p><p>从类图结构看出<code>URLClassLoader</code>中存在一个<code>URLClassPath</code>类，通过这个类就可以找到要加载的字节码流，也就是说<code>URLClassPath</code>类负责找到要加载的字节码，再读取成字节流，最后通过<code>defineClass()</code>方法创建类的Class对象。从<code>URLClassLoader</code>类的结构图可以看出其构造方法都有一个必须传递的参数URL[]，该参数的元素是代表字节码文件的路径,换句话说在创建URLClassLoader对象时必须要指定这个类加载器的到那个目录下找class文件。同时也应该注意URL[]也是<code>URLClassPath</code>类的必传参数，在创建URLClassPath对象时，会根据传递过来的URL数组中的路径判断是文件还是jar包，然后根据不同的路径创建<code>FileLoader</code>或者<code>JarLoader</code>或默认<code>Loader</code>类去加载相应路径下的class文件，而当JVM调用<code>findClass()</code>方法时，就由这3个加载器中的一个将class文件的字节码流加载到内存中，最后利用字节码流创建类的class对象。请记住，如果我们在定义类加载器时选择继承<code>ClassLoader</code>类而非<code>URLClassLoader</code>，必须手动编写<code>findclass()</code>方法的加载逻辑以及获取字节码流的逻辑。了解完<code>URLClassLoader</code>后接着看看剩余的两个类加载器，即拓展类加载器<code>ExtClassLoader</code>和系统类加载器<code>AppClassLoader</code>，这两个类都继承自URLClassLoader，是<code>sun.misc.Launcher</code>的静态内部类。<code>sun.misc.Launcher</code>主要被系统用于启动主应用程序，<code>ExtClassLoader</code>和<code>AppClassLoader</code>都是由<code>sun.misc.Launcher</code>创建的，其类主要类结构如下：</p><p><img src="/images/2018-07/两Loader类类图.png" alt="两Loader类类图"></p><p>它们间的关系正如前面所阐述的那样，同时我们发现<code>ExtClassLoader</code>并没有重写<code>loadClass()</code>方法，这足矣说明其遵循双亲委派模式，而<code>AppClassLoader</code>重载了<code>loadCass()</code>方法，但最终调用的还是父类<code>loadClass()</code>方法，因此依然遵守双亲委派模式，重载方法源码如下：</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br>  * Override loadClass 方法，新增包权限检测功能<br>  */</span><br> <span class="hljs-function"><span class="hljs-keyword">public</span> Class <span class="hljs-title">loadClass</span><span class="hljs-params">(String name, <span class="hljs-keyword">boolean</span> resolve)</span><br>     <span class="hljs-keyword">throws</span> ClassNotFoundException<br> </span>&#123;<br>     <span class="hljs-keyword">int</span> i = name.lastIndexOf(<span class="hljs-string">'.'</span>);<br>     <span class="hljs-keyword">if</span> (i != -<span class="hljs-number">1</span>) &#123;<br>         SecurityManager sm = System.getSecurityManager();<br>         <span class="hljs-keyword">if</span> (sm != <span class="hljs-keyword">null</span>) &#123;<br>             sm.checkPackageAccess(name.substring(<span class="hljs-number">0</span>, i));<br>         &#125;<br>     &#125;<br>     <span class="hljs-comment">//依然调用父类的方法</span><br>     <span class="hljs-keyword">return</span> (<span class="hljs-keyword">super</span>.loadClass(name, resolve));<br> &#125;<br></code></pre></td></tr></table></figure><p>其实无论是<code>ExtClassLoader</code>还是<code>AppClassLoader</code>都继承<code>URLClassLoader</code>类，因此它们都遵守双亲委托模型，这点是毋庸置疑的。ok~，到此我们对<code>ClassLoader</code>、<code>URLClassLoader</code>、<code>ExtClassLoader</code>、<code>AppClassLoader</code>以及<code>Launcher</code>类间的关系有了比较清晰的了解，同时对一些主要的方法也有一定的认识，这里并没有对这些类的源码进行详细的分析，毕竟没有那个必要，因为我们主要弄得类与类间的关系和常用的方法同时搞清楚双亲委托模式的实现过程，为编写自定义类加载器做铺垫就足够了。ok~，前面出现了很多父类加载器的说法，但每个类加载器的父类到底是谁，一直没有阐明，下面我们就通过代码验证的方式来阐明这答案。</p><h2 id="类加载器间的关系"><a href="#类加载器间的关系" class="headerlink" title="类加载器间的关系"></a>类加载器间的关系</h2><p>我们进一步了解类加载器间的关系(并非指继承关系)，主要可以分为以下4点</p><ul><li>启动类加载器，由C++实现，没有父类。</li><li>拓展类加载器(<code>ExtClassLoader</code>)，由Java语言实现，父类加载器为null</li><li>系统类加载器(<code>AppClassLoader</code>)，由Java语言实现，父类加载器为<code>ExtClassLoader</code></li><li>自定义类加载器，父类加载器肯定为<code>AppClassLoader</code>。</li></ul><p>下面我们通过程序来验证上述阐述的观点</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br> * Created by zejian on 2017/6/18.<br> * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]<br> */</span><br><span class="hljs-comment">//自定义ClassLoader，完整代码稍后分析</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FileClassLoader</span> <span class="hljs-keyword">extends</span>  <span class="hljs-title">ClassLoader</span></span>&#123;<br>    <span class="hljs-keyword">private</span> String rootDir;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">FileClassLoader</span><span class="hljs-params">(String rootDir)</span> </span>&#123;<br>        <span class="hljs-keyword">this</span>.rootDir = rootDir;<br>    &#125;<br>    <span class="hljs-comment">// 编写获取类的字节码并创建class对象的逻辑</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> Class&lt;?&gt; findClass(String name) <span class="hljs-keyword">throws</span> ClassNotFoundException &#123;<br>       <span class="hljs-comment">//...省略逻辑代码</span><br>    &#125;<br>    <span class="hljs-comment">//编写读取字节流的方法</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">byte</span>[] getClassData(String className) &#123;<br>        <span class="hljs-comment">// 读取类文件的字节</span><br>        <span class="hljs-comment">//省略代码....</span><br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ClassLoaderTest</span> </span>&#123;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> ClassNotFoundException </span>&#123;<br><br>             FileClassLoader loader1 = <span class="hljs-keyword">new</span> FileClassLoader(rootDir);<br><br>              System.out.println(<span class="hljs-string">"自定义类加载器的父加载器: "</span>+loader1.getParent());<br>              System.out.println(<span class="hljs-string">"系统默认的AppClassLoader: "</span>+ClassLoader.getSystemClassLoader());<br>              System.out.println(<span class="hljs-string">"AppClassLoader的父类加载器: "</span>+ClassLoader.getSystemClassLoader().getParent());<br>              System.out.println(<span class="hljs-string">"ExtClassLoader的父类加载器: "</span>+ClassLoader.getSystemClassLoader().getParent().getParent());<br><br>            <span class="hljs-comment">/**<br>            输出结果:<br>                自定义类加载器的父加载器: sun.misc.Launcher$AppClassLoader@29453f44<br>                系统默认的AppClassLoader: sun.misc.Launcher$AppClassLoader@29453f44<br>                AppClassLoader的父类加载器: sun.misc.Launcher$ExtClassLoader@6f94fa3e<br>                ExtClassLoader的父类加载器: null<br>            */</span><br><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>代码中，我们自定义了一个<code>FileClassLoader</code>，这里我们继承了<code>ClassLoader</code>而非<code>URLClassLoader</code>,因此需要自己编写<code>findClass()</code>方法逻辑以及加载字节码的逻辑，关于自定义类加载器我们稍后会分析，这里仅需要知道<code>FileClassLoader</code>是自定义加载器即可，接着在main方法中，通过<code>ClassLoader.getSystemClassLoader()</code>获取到系统默认类加载器，通过获取其父类加载器及其父父类加载器，同时还获取了自定义类加载器的父类加载器,最终输出结果正如我们所预料的，<code>AppClassLoader</code>的父类加载器为<code>ExtClassLoader</code>，而<code>ExtClassLoader</code>没有父类加载器。如果我们实现自己的类加载器，它的父加载器都只会是<code>AppClassLoader</code>。这里我们不妨看看<code>Lancher</code>的构造器源码</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Launcher</span><span class="hljs-params">()</span> </span>&#123;<br>        <span class="hljs-comment">// 首先创建拓展类加载器</span><br>        ClassLoader extcl;<br>        <span class="hljs-keyword">try</span> &#123;<br>            extcl = ExtClassLoader.getExtClassLoader();<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InternalError(<br>                <span class="hljs-string">"Could not create extension class loader"</span>);<br>        &#125;<br><br>        <span class="hljs-comment">// Now create the class loader to use to launch the application</span><br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">//再创建AppClassLoader并把extcl作为父加载器传递给AppClassLoader</span><br>            loader = AppClassLoader.getAppClassLoader(extcl);<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InternalError(<br>                <span class="hljs-string">"Could not create application class loader"</span>);<br>        &#125;<br><br>        <span class="hljs-comment">//设置线程上下文类加载器，稍后分析</span><br>        Thread.currentThread().setContextClassLoader(loader);<br><span class="hljs-comment">//省略其他没必要的代码......</span><br>        &#125;<br>    &#125;<br></code></pre></td></tr></table></figure><p>显然Lancher初始化时首先会创建ExtClassLoader类加载器，然后再创建AppClassLoader并把ExtClassLoader传递给它作为父类加载器，这里还把AppClassLoader默认设置为线程上下文类加载器，关于线程上下文类加载器稍后会分析。那ExtClassLoader类加载器为什么是null呢？看下面的源码创建过程就明白，在创建ExtClassLoader强制设置了其父加载器为null。</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//Lancher中创建ExtClassLoader</span><br>extcl = ExtClassLoader.getExtClassLoader();<br><br><span class="hljs-comment">//getExtClassLoader()方法</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ExtClassLoader <span class="hljs-title">getExtClassLoader</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException</span>&#123;<br><br>  <span class="hljs-comment">//........省略其他代码 </span><br>  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ExtClassLoader(dirs);                     <br>  <span class="hljs-comment">// .........</span><br>&#125;<br><br><span class="hljs-comment">//构造方法</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ExtClassLoader</span><span class="hljs-params">(File[] dirs)</span> <span class="hljs-keyword">throws</span> IOException </span>&#123;<br>   <span class="hljs-comment">//调用父类构造URLClassLoader传递null作为parent</span><br>   <span class="hljs-keyword">super</span>(getExtURLs(dirs), <span class="hljs-keyword">null</span>, factory);<br>&#125;<br><br><span class="hljs-comment">//URLClassLoader构造</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">URLClassLoader</span><span class="hljs-params">(URL[] urls, ClassLoader parent,<br>                          URLStreamHandlerFactory factory)</span> </span>&#123;<br></code></pre></td></tr></table></figure><p>显然<code>ExtClassLoader</code>的父类为null，而AppClassLoader的父加载器为<code>ExtClassLoader</code>，所有自定义的类加载器其父加载器只会是<code>AppClassLoader</code>，注意这里所指的父类并不是Java继承关系中的那种父子关系。</p><h1 id="类与类加载器"><a href="#类与类加载器" class="headerlink" title="类与类加载器"></a>类与类加载器</h1><h2 id="类与类加载器-1"><a href="#类与类加载器-1" class="headerlink" title="类与类加载器"></a>类与类加载器</h2><p>在JVM中表示两个class对象是否为同一个类对象存在两个必要条件</p><ul><li>类的完整类名必须一致，包括包名。</li><li>加载这个类的<code>ClassLoader</code>(指ClassLoader实例对象)必须相同。</li></ul><p>也就是说，在JVM中，即使这个两个类对象(class对象)来源同一个Class文件，被同一个虚拟机所加载，但只要加载它们的ClassLoader实例对象不同，那么这两个类对象也是不相等的，这是因为不同的<code>ClassLoader</code>实例对象都拥有不同的独立的类名称空间，所以加载的class对象也会存在不同的类名空间中，但前提是覆写loadclass方法，从前面双亲委派模式对<code>loadClass()</code>方法的源码分析中可以知，在方法第一步会通过<code>Class&lt;?&gt; c = findLoadedClass(name);</code>从缓存查找，类名完整名称相同则不会再次被加载，因此我们必须绕过缓存查询才能重新加载class对象。当然也可直接调用<code>findClass()</code>方法，这样也避免从缓存查找，如下</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java">String rootDir=<span class="hljs-string">"/Users/zejian/Downloads/Java8_Action/src/main/java/"</span>;<br><span class="hljs-comment">//创建两个不同的自定义类加载器实例</span><br>FileClassLoader loader1 = <span class="hljs-keyword">new</span> FileClassLoader(rootDir);<br>FileClassLoader loader2 = <span class="hljs-keyword">new</span> FileClassLoader(rootDir);<br><span class="hljs-comment">//通过findClass创建类的Class对象</span><br>Class&lt;?&gt; object1=loader1.findClass(<span class="hljs-string">"com.zejian.classloader.DemoObj"</span>);<br>Class&lt;?&gt; object2=loader2.findClass(<span class="hljs-string">"com.zejian.classloader.DemoObj"</span>);<br><br>System.out.println(<span class="hljs-string">"findClass-&gt;obj1:"</span>+object1.hashCode());<br>System.out.println(<span class="hljs-string">"findClass-&gt;obj2:"</span>+object2.hashCode());<br><br><span class="hljs-comment">/**<br>  * 直接调用findClass方法输出结果:<br>  * findClass-&gt;obj1:723074861<br>    findClass-&gt;obj2:895328852<br>    生成不同的实例<br>  */</span><br></code></pre></td></tr></table></figure><p>如果调用父类的loadClass方法，结果如下，除非重写loadClass()方法去掉缓存查找步骤，不过现在一般都不建议重写loadClass()方法。</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//直接调用父类的loadClass()方法</span><br>Class&lt;?&gt; obj1 =loader1.loadClass(<span class="hljs-string">"com.zejian.classloader.DemoObj"</span>);<br>Class&lt;?&gt; obj2 =loader2.loadClass(<span class="hljs-string">"com.zejian.classloader.DemoObj"</span>);<br><br><span class="hljs-comment">//不同实例对象的自定义类加载器</span><br>System.out.println(<span class="hljs-string">"loadClass-&gt;obj1:"</span>+obj1.hashCode());<br>System.out.println(<span class="hljs-string">"loadClass-&gt;obj2:"</span>+obj2.hashCode());<br><span class="hljs-comment">//系统类加载器</span><br>System.out.println(<span class="hljs-string">"Class-&gt;obj3:"</span>+DemoObj.class.hashCode());<br><br><span class="hljs-comment">/**<br>* 直接调用loadClass方法的输出结果,注意并没有重写loadClass方法<br>* loadClass-&gt;obj1:1872034366<br>  loadClass-&gt;obj2:1872034366<br>  Class-&gt;    obj3:1872034366<br>  都是同一个实例<br>*/</span><br></code></pre></td></tr></table></figure><p>所以如果不从缓存查询相同完全类名的class对象，那么只有<code>ClassLoader</code>的实例对象不同，同一字节码文件创建的class对象自然也不会相同。</p><h2 id="了解class文件的显示加载与隐式加载的概念"><a href="#了解class文件的显示加载与隐式加载的概念" class="headerlink" title="了解class文件的显示加载与隐式加载的概念"></a>了解class文件的显示加载与隐式加载的概念</h2><p>所谓class文件的显示加载与隐式加载的方式是指JVM加载class文件到内存的方式，显示加载指的是在代码中通过调用<code>ClassLoader</code>加载class对象，如直接使用<code>Class.forName(name)</code>或<code>this.getClass().getClassLoader().loadClass()</code>加载class对象。而隐式加载则是不直接在代码中调用<code>ClassLoader</code>的方法加载class对象，而是通过虚拟机自动加载到内存中，如在加载某个类的class文件时，该类的class文件中引用了另外一个类的对象，此时额外引用的类将通过JVM自动加载到内存中。在日常开发以上两种方式一般会混合使用，这里我们知道有这么回事即可。</p><h1 id="编写自己的类加载器"><a href="#编写自己的类加载器" class="headerlink" title="编写自己的类加载器"></a>编写自己的类加载器</h1><p>通过前面的分析可知，实现自定义类加载器需要继承<code>ClassLoader</code>或者<code>URLClassLoader</code>，继承<code>ClassLoader</code>则需要自己重写<code>findClass()</code>方法并编写加载逻辑，继承<code>URLClassLoader</code>则可以省去编写<code>findClass()</code>方法以及class文件加载转换成字节码流的代码。那么编写自定义类加载器的意义何在呢？</p><ul><li>当class文件不在ClassPath路径下，默认系统类加载器无法找到该class文件，在这种情况下我们需要实现一个自定义的<code>ClassLoader</code>来加载特定路径下的class文件生成class对象。</li><li>当一个class文件是通过网络传输并且可能会进行相应的加密操作时，需要先对class文件进行相应的解密后再加载到JVM内存中，这种情况下也需要编写自定义的<code>ClassLoader</code>并实现相应的逻辑。</li><li>当需要实现热部署功能时(一个class文件通过不同的类加载器产生不同class对象从而实现热部署功能)，需要实现自定义<code>ClassLoader</code>的逻辑。</li></ul><h2 id="自定义File类加载器"><a href="#自定义File类加载器" class="headerlink" title="自定义File类加载器"></a>自定义File类加载器</h2><p>这里我们继承ClassLoader实现自定义的特定路径下的文件类加载器并加载编译后DemoObj.class，源码代码如下</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DemoObj</span> </span>&#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span><span class="hljs-params">()</span> </span>&#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">"I am DemoObj"</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.zejian.classloader;<br><br><span class="hljs-keyword">import</span> java.io.*;<br><br><span class="hljs-comment">/**<br> * Created by zejian on 2017/6/21.<br> * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]<br> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FileClassLoader</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ClassLoader</span> </span>&#123;<br>    <span class="hljs-keyword">private</span> String rootDir;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">FileClassLoader</span><span class="hljs-params">(String rootDir)</span> </span>&#123;<br>        <span class="hljs-keyword">this</span>.rootDir = rootDir;<br>    &#125;<br><br>    <span class="hljs-comment">/**<br>     * 编写findClass方法的逻辑<br>     * <span class="hljs-doctag">@param</span> name<br>     * <span class="hljs-doctag">@return</span><br>     * <span class="hljs-doctag">@throws</span> ClassNotFoundException<br>     */</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> Class&lt;?&gt; findClass(String name) <span class="hljs-keyword">throws</span> ClassNotFoundException &#123;<br>        <span class="hljs-comment">// 获取类的class文件字节数组</span><br>        <span class="hljs-keyword">byte</span>[] classData = getClassData(name);<br>        <span class="hljs-keyword">if</span> (classData == <span class="hljs-keyword">null</span>) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ClassNotFoundException();<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-comment">//直接生成class对象</span><br>            <span class="hljs-keyword">return</span> defineClass(name, classData, <span class="hljs-number">0</span>, classData.length);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">/**<br>     * 编写获取class文件并转换为字节码流的逻辑<br>     * <span class="hljs-doctag">@param</span> className<br>     * <span class="hljs-doctag">@return</span><br>     */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">byte</span>[] getClassData(String className) &#123;<br>        <span class="hljs-comment">// 读取类文件的字节</span><br>        String path = classNameToPath(className);<br>        <span class="hljs-keyword">try</span> &#123;<br>            InputStream ins = <span class="hljs-keyword">new</span> FileInputStream(path);<br>            ByteArrayOutputStream baos = <span class="hljs-keyword">new</span> ByteArrayOutputStream();<br>            <span class="hljs-keyword">int</span> bufferSize = <span class="hljs-number">4096</span>;<br>            <span class="hljs-keyword">byte</span>[] buffer = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[bufferSize];<br>            <span class="hljs-keyword">int</span> bytesNumRead = <span class="hljs-number">0</span>;<br>            <span class="hljs-comment">// 读取类文件的字节码</span><br>            <span class="hljs-keyword">while</span> ((bytesNumRead = ins.read(buffer)) != -<span class="hljs-number">1</span>) &#123;<br>                baos.write(buffer, <span class="hljs-number">0</span>, bytesNumRead);<br>            &#125;<br>            <span class="hljs-keyword">return</span> baos.toByteArray();<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;<br>    &#125;<br><br>    <span class="hljs-comment">/**<br>     * 类文件的完全路径<br>     * <span class="hljs-doctag">@param</span> className<br>     * <span class="hljs-doctag">@return</span><br>     */</span><br>    <span class="hljs-function"><span class="hljs-keyword">private</span> String <span class="hljs-title">classNameToPath</span><span class="hljs-params">(String className)</span> </span>&#123;<br>        <span class="hljs-keyword">return</span> rootDir + File.separatorChar<br>                + className.replace(<span class="hljs-string">'.'</span>, File.separatorChar) + <span class="hljs-string">".class"</span>;<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> ClassNotFoundException </span>&#123;<br>        String rootDir=<span class="hljs-string">"/Users/zejian/Downloads/Java8_Action/src/main/java/"</span>;<br>        <span class="hljs-comment">//创建自定义文件类加载器</span><br>        FileClassLoader loader = <span class="hljs-keyword">new</span> FileClassLoader(rootDir);<br><br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">//加载指定的class文件</span><br>            Class&lt;?&gt; object1=loader.loadClass(<span class="hljs-string">"com.zejian.classloader.DemoObj"</span>);<br>            System.out.println(object1.newInstance().toString());<br><br>            <span class="hljs-comment">//输出结果:I am DemoObj</span><br>        &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>显然我们通过<code>getClassData()</code>方法找到class文件并转换为字节流，并重写<code>findClass()</code>方法，利用<code>defineClass()</code>方法创建了类的class对象。在main方法中调用了<code>loadClass()</code>方法加载指定路径下的class文件，由于启动类加载器、拓展类加载器以及系统类加载器都无法在其路径下找到该类，因此最终将有自定义类加载器加载，即调用<code>findClass()</code>方法进行加载。如果继承<code>URLClassLoader</code>实现，那代码就更简洁了，如下：</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br> * Created by zejian on 2017/6/21.<br> * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]<br> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FileUrlClassLoader</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">URLClassLoader</span> </span>&#123;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">FileUrlClassLoader</span><span class="hljs-params">(URL[] urls, ClassLoader parent)</span> </span>&#123;<br>        <span class="hljs-keyword">super</span>(urls, parent);<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">FileUrlClassLoader</span><span class="hljs-params">(URL[] urls)</span> </span>&#123;<br>        <span class="hljs-keyword">super</span>(urls);<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">FileUrlClassLoader</span><span class="hljs-params">(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory)</span> </span>&#123;<br>        <span class="hljs-keyword">super</span>(urls, parent, factory);<br>    &#125;<br><br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> ClassNotFoundException, MalformedURLException </span>&#123;<br>        String rootDir=<span class="hljs-string">"/Users/zejian/Downloads/Java8_Action/src/main/java/"</span>;<br>        <span class="hljs-comment">//创建自定义文件类加载器</span><br>        File file = <span class="hljs-keyword">new</span> File(rootDir);<br>        <span class="hljs-comment">//File to URI</span><br>        URI uri=file.toURI();<br>        URL[] urls=&#123;uri.toURL()&#125;;<br><br>        FileUrlClassLoader loader = <span class="hljs-keyword">new</span> FileUrlClassLoader(urls);<br><br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">//加载指定的class文件</span><br>            Class&lt;?&gt; object1=loader.loadClass(<span class="hljs-string">"com.zejian.classloader.DemoObj"</span>);<br>            System.out.println(object1.newInstance().toString());<br><br>            <span class="hljs-comment">//输出结果:I am DemoObj</span><br>        &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>非常简洁除了需要重写构造器外无需编写<code>findClass()</code>方法及其class文件的字节流转换逻辑。</p><h2 id="自定义网络类加载器"><a href="#自定义网络类加载器" class="headerlink" title="自定义网络类加载器"></a>自定义网络类加载器</h2><p>自定义网络类加载器，主要用于读取通过网络传递的class文件（在这里我们省略class文件的解密过程），并将其转换成字节流生成对应的class对象，如下</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br> * Created by zejian on 2017/6/21.<br> * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]<br> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NetClassLoader</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ClassLoader</span> </span>&#123;<br><br>    <span class="hljs-keyword">private</span> String url;<span class="hljs-comment">//class文件的URL</span><br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">NetClassLoader</span><span class="hljs-params">(String url)</span> </span>&#123;<br>        <span class="hljs-keyword">this</span>.url = url;<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> Class&lt;?&gt; findClass(String name) <span class="hljs-keyword">throws</span> ClassNotFoundException &#123;<br>        <span class="hljs-keyword">byte</span>[] classData = getClassDataFromNet(name);<br>        <span class="hljs-keyword">if</span> (classData == <span class="hljs-keyword">null</span>) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ClassNotFoundException();<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-keyword">return</span> defineClass(name, classData, <span class="hljs-number">0</span>, classData.length);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">/**<br>     * 从网络获取class文件<br>     * <span class="hljs-doctag">@param</span> className<br>     * <span class="hljs-doctag">@return</span><br>     */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">byte</span>[] getClassDataFromNet(String className) &#123;<br>        String path = classNameToPath(className);<br>        <span class="hljs-keyword">try</span> &#123;<br>            URL url = <span class="hljs-keyword">new</span> URL(path);<br>            InputStream ins = url.openStream();<br>            ByteArrayOutputStream baos = <span class="hljs-keyword">new</span> ByteArrayOutputStream();<br>            <span class="hljs-keyword">int</span> bufferSize = <span class="hljs-number">4096</span>;<br>            <span class="hljs-keyword">byte</span>[] buffer = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[bufferSize];<br>            <span class="hljs-keyword">int</span> bytesNumRead = <span class="hljs-number">0</span>;<br>            <span class="hljs-comment">// 读取类文件的字节</span><br>            <span class="hljs-keyword">while</span> ((bytesNumRead = ins.read(buffer)) != -<span class="hljs-number">1</span>) &#123;<br>                baos.write(buffer, <span class="hljs-number">0</span>, bytesNumRead);<br>            &#125;<br>            <span class="hljs-comment">//这里省略解密的过程.......</span><br>            <span class="hljs-keyword">return</span> baos.toByteArray();<br>        &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">private</span> String <span class="hljs-title">classNameToPath</span><span class="hljs-params">(String className)</span> </span>&#123;<br>        <span class="hljs-comment">// 得到类文件的URL</span><br>        <span class="hljs-keyword">return</span> url + <span class="hljs-string">"/"</span> + className.replace(<span class="hljs-string">'.'</span>, <span class="hljs-string">'/'</span>) + <span class="hljs-string">".class"</span>;<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>比较简单，主要是在获取字节码流时的区别，从网络直接获取到字节流再转车字节数组然后利用defineClass方法创建class对象，如果继承<code>URLClassLoader</code>类则和前面文件路径的实现是类似的，无需担心路径是filePath还是Url，因为<code>URLClassLoader</code>内的<code>URLClassPath</code>对象会根据传递过来的URL数组中的路径判断是文件还是jar包，然后根据不同的路径创建<code>FileLoader</code>或者<code>JarLoader</code>或默认类<code>Loader</code>去读取对于的路径或者url下的class文件。</p><h2 id="热部署类加载器"><a href="#热部署类加载器" class="headerlink" title="热部署类加载器"></a>热部署类加载器</h2><p>所谓的热部署就是利用同一个class文件不同的类加载器在内存创建出两个不同的class对象(关于这点的原因前面已分析过，即利用不同的类加载实例)，由于JVM在加载类之前会检测请求的类是否已加载过(即在<code>loadClass()</code>方法中调用<code>findLoadedClass()</code>方法)，如果被加载过，则直接从缓存获取，不会重新加载。注意同一个类加载器的实例和同一个class文件只能被加载器一次，多次加载将报错，因此我们实现的热部署必须让同一个class文件可以根据不同的类加载器重复加载，以实现所谓的热部署。实际上前面的实现的<code>FileClassLoader</code>和<code>FileUrlClassLoader</code>已具备这个功能，但前提是直接调用<code>findClass()</code>方法，而不是调用<code>loadClass()</code>方法，因为<code>ClassLoader</code>中<code>loadClass()</code>方法体中调用<code>findLoadedClass()</code>方法进行了检测是否已被加载，因此我们直接调用<code>findClass()</code>方法就可以绕过这个问题，当然也可以重新<code>loadClass()</code>方法，但强烈不建议这么干。利用<code>FileClassLoader</code>类测试代码如下：</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> ClassNotFoundException </span>&#123;<br>        String rootDir=<span class="hljs-string">"/Users/zejian/Downloads/Java8_Action/src/main/java/"</span>;<br>        <span class="hljs-comment">//创建自定义文件类加载器</span><br>        FileClassLoader loader = <span class="hljs-keyword">new</span> FileClassLoader(rootDir);<br>        FileClassLoader loader2 = <span class="hljs-keyword">new</span> FileClassLoader(rootDir);<br><br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">//加载指定的class文件,调用loadClass()</span><br>            Class&lt;?&gt; object1=loader.loadClass(<span class="hljs-string">"com.zejian.classloader.DemoObj"</span>);<br>            Class&lt;?&gt; object2=loader2.loadClass(<span class="hljs-string">"com.zejian.classloader.DemoObj"</span>);<br><br>            System.out.println(<span class="hljs-string">"loadClass-&gt;obj1:"</span>+object1.hashCode());<br>            System.out.println(<span class="hljs-string">"loadClass-&gt;obj2:"</span>+object2.hashCode());<br><br>            <span class="hljs-comment">//加载指定的class文件,直接调用findClass(),绕过检测机制，创建不同class对象。</span><br>            Class&lt;?&gt; object3=loader.findClass(<span class="hljs-string">"com.zejian.classloader.DemoObj"</span>);<br>            Class&lt;?&gt; object4=loader2.findClass(<span class="hljs-string">"com.zejian.classloader.DemoObj"</span>);<br><br>            System.out.println(<span class="hljs-string">"loadClass-&gt;obj3:"</span>+object3.hashCode());<br>            System.out.println(<span class="hljs-string">"loadClass-&gt;obj4:"</span>+object4.hashCode());<br><br>            <span class="hljs-comment">/**<br>             * 输出结果:<br>             * loadClass-&gt;obj1:644117698<br>               loadClass-&gt;obj2:644117698<br>               findClass-&gt;obj3:723074861<br>               findClass-&gt;obj4:895328852<br>             */</span><br><br>        &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br></code></pre></td></tr></table></figure><h1 id="双亲委派模型的破坏者-线程上下文类加载器"><a href="#双亲委派模型的破坏者-线程上下文类加载器" class="headerlink" title="双亲委派模型的破坏者-线程上下文类加载器"></a>双亲委派模型的破坏者-线程上下文类加载器</h1><p>在Java应用中存在着很多服务提供者接口（Service Provider Interface，SPI），这些接口允许第三方为它们提供实现，如常见的 SPI 有 JDBC、JNDI等，这些 SPI 的接口属于 Java 核心库，一般存在rt.jar包中，由Bootstrap类加载器加载，而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下，由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法，但SPI的核心接口类是由引导类加载器来加载的，而Bootstrap类加载器无法直接加载SPI的实现类，同时由于双亲委派模式的存在，Bootstrap类加载器也无法反向委托<code>AppClassLoader</code>加载器SPI的实现类。在这种情况下，我们就需要一种特殊的类加载器来加载第三方的类库，而线程上下文类加载器就是很好的选择。 </p><p>线程上下文类加载器（<code>ContextClassLoader</code>）是从 JDK 1.2 开始引入的，我们可以通过j<code>ava.lang.Thread</code>类中的<code>getContextClassLoader()</code>和 <code>setContextClassLoader(ClassLoader cl)</code>方法来获取和设置线程的上下文类加载器。如果没有手动设置上下文类加载器，线程将继承其父线程的上下文类加载器，初始线程的上下文类加载器是系统类加载器（<code>AppClassLoader</code>）,在线程中运行的代码可以通过此类加载器来加载类和资源，如下图所示，以jdbc.jar加载为例</p><p><img src="/images/2018-07/jdbc.jar加载.png" alt="jdbc.jar加载"></p><p>从图可知rt.jar核心包是有Bootstrap类加载器加载的，其内包含SPI核心接口类，由于SPI中的类经常需要调用外部实现类的方法，而jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径)无法通过Bootstrap类加载器加载，因此只能委派线程上下文类加载器把jdbc.jar中的实现类加载到内存以便SPI相关类使用。显然这种线程上下文类加载器的加载方式破坏了“双亲委派模型”，它在执行过程中抛弃双亲委派加载链模式，使程序可以逆向使用类加载器，当然这也使得Java类加载器变得更加灵活。为了进一步证实这种场景，不妨看看<code>DriverManager</code>类的源码，<code>DriverManager</code>是Java核心rt.jar包中的类，该类用来管理不同数据库的实现驱动即Driver，它们都实现了Java核心包中的<code>java.sql.Driver</code>接口，如mysql驱动包中的<code>com.mysql.jdbc.Driver</code>，这里主要看看如何加载外部实现类，在DriverManager初始化时会执行如下代码</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//DriverManager是Java核心包rt.jar的类</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DriverManager</span> </span>&#123;<br>    <span class="hljs-comment">//省略不必要的代码</span><br>    <span class="hljs-keyword">static</span> &#123;<br>        loadInitialDrivers();<span class="hljs-comment">//执行该方法</span><br>        println(<span class="hljs-string">"JDBC DriverManager initialized"</span>);<br>    &#125;<br><br><span class="hljs-comment">//loadInitialDrivers方法</span><br> <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">loadInitialDrivers</span><span class="hljs-params">()</span> </span>&#123;<br>     sun.misc.Providers()<br>     AccessController.doPrivileged(<span class="hljs-keyword">new</span> PrivilegedAction&lt;Void&gt;() &#123;<br>            <span class="hljs-function"><span class="hljs-keyword">public</span> Void <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;<br>                <span class="hljs-comment">//加载外部的Driver的实现类</span><br>                ServiceLoader&lt;Driver&gt; loadedDrivers = ServiceLoader.load(Driver.class);<br>              <span class="hljs-comment">//省略不必要的代码......</span><br>            &#125;<br>        &#125;);<br>    &#125;<br></code></pre></td></tr></table></figure><p>在<code>DriverManager</code>类初始化时执行了<code>loadInitialDrivers()</code>方法,在该方法中通过<code>ServiceLoader.load(Driver.class);</code>去加载外部实现的驱动类，<code>ServiceLoader</code>类会去读取mysql的jdbc.jar下META-INF文件的内容，如下所示</p><p><img src="/images/2018-07/META-INF文件内容.png" alt="META-INF文件内容"></p><p>而<code>com.mysql.jdbc.Driver</code>继承类如下：</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Driver</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">com</span>.<span class="hljs-title">mysql</span>.<span class="hljs-title">cj</span>.<span class="hljs-title">jdbc</span>.<span class="hljs-title">Driver</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Driver</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> SQLException </span>&#123;<br>        <span class="hljs-keyword">super</span>();<br>    &#125;<br><br>    <span class="hljs-keyword">static</span> &#123;<br>        System.err.println(<span class="hljs-string">"Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. "</span><br>                + <span class="hljs-string">"The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary."</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>从注释可以看出平常我们使用<code>com.mysql.jdbc.Driver</code>已被丢弃了，取而代之的是<code>com.mysql.cj.jdbc.Driver</code>，也就是说官方不再建议我们使用如下代码注册mysql驱动</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//不建议使用该方式注册驱动类</span><br>Class.forName(<span class="hljs-string">"com.mysql.jdbc.Driver"</span>);<br>String url = <span class="hljs-string">"jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8"</span>;<br><span class="hljs-comment">// 通过java库获取数据库连接</span><br>Connection conn = java.sql.DriverManager.getConnection(url, <span class="hljs-string">"root"</span>, <span class="hljs-string">"root@555"</span>);<br></code></pre></td></tr></table></figure><p>而是直接去掉注册步骤，如下即可</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">String url = <span class="hljs-string">"jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8"</span>;<br><span class="hljs-comment">// 通过java库获取数据库连接</span><br>Connection conn = java.sql.DriverManager.getConnection(url, <span class="hljs-string">"root"</span>, <span class="hljs-string">"root@555"</span>);<br></code></pre></td></tr></table></figure><p>这样<code>ServiceLoader</code>会帮助我们处理一切，并最终通过<code>load()</code>方法加载，看看<code>load()</code>方法实现</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;S&gt; <span class="hljs-function">ServiceLoader&lt;S&gt; <span class="hljs-title">load</span><span class="hljs-params">(Class&lt;S&gt; service)</span> </span>&#123;<br>     <span class="hljs-comment">//通过线程上下文类加载器加载</span><br>      ClassLoader cl = Thread.currentThread().getContextClassLoader();<br>      <span class="hljs-keyword">return</span> ServiceLoader.load(service, cl);<br>  &#125;<br></code></pre></td></tr></table></figure><p>很明显了确实通过线程上下文类加载器加载的，实际上核心包的SPI类对外部实现类的加载都是基于线程上下文类加载器执行的，通过这种方式实现了Java核心代码内部去调用外部实现类。我们知道线程上下文类加载器默认情况下就是<code>AppClassLoader</code>，那为什么不直接通过<code>getSystemClassLoader()</code>获取类加载器来加载classpath路径下的类的呢？其实是可行的，但这种直接使用<code>getSystemClassLoader()</code>方法获取<code>AppClassLoader</code>加载类有一个缺点，那就是代码部署到不同服务时会出现问题，如把代码部署到Java Web应用服务或者EJB之类的服务将会出问题，因为这些服务使用的线程上下文类加载器并非<code>AppClassLoader</code>，而是Java Web应用服自家的类加载器，类加载器不同。，所以我们应用该少用<code>getSystemClassLoader()</code>。总之不同的服务使用的可能默认<code>ClassLoader</code>是不同的，但使用线程上下文类加载器总能获取到与当前程序执行相同的<code>ClassLoader</code>，从而避免不必要的问题。ok~.关于线程上下文类加载器暂且聊到这，前面阐述的<code>DriverManager</code>类，大家可以自行看看源码，相信会有更多的体会，另外关于<code>ServiceLoader</code>本篇并没有过多的阐述，毕竟我们主题是类加载器，但<code>ServiceLoader</code>是个很不错的解耦机制，大家可以自行查阅其相关用法。</p><p>ok~，本篇到此告一段落，如有误处，欢迎留言，谢谢。</p><blockquote><p>本文转载自:<br><a href="https://blog.csdn.net/javazejian/article/details/73413292#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E7%9A%84%E6%9C%BA%E5%88%B6%E7%9A%84%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84" target="_blank" rel="noopener">https://blog.csdn.net/javazejian/article/details/73413292#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E7%9A%84%E6%9C%BA%E5%88%B6%E7%9A%84%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84</a><br>作者：zejian<br>声明：<br>感谢原作者的辛勤付出。转载文章一般是由于原文章的样式不符合自己喜好，为了更方便于自己阅读故转载到自己博客。如果侵权，麻烦告知删除。</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;介绍了JVM类加载的原理。&lt;br&gt;
    
    </summary>
    
      <category term="JVM" scheme="https://soso.github.io/categories/JVM/"/>
    
    
      <category term="JVM" scheme="https://soso.github.io/tags/JVM/"/>
    
      <category term="转载" scheme="https://soso.github.io/tags/%E8%BD%AC%E8%BD%BD/"/>
    
  </entry>
  
  <entry>
    <title>Hello World</title>
    <link href="https://soso.github.io/2018/07/17/hello-world/"/>
    <id>https://soso.github.io/2018/07/17/hello-world/</id>
    <published>2018-07-17T00:00:00.000Z</published>
    <updated>2018-07-17T02:34:40.000Z</updated>
    
    <content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/" target="_blank" rel="noopener">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html" target="_blank" rel="noopener">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues" target="_blank" rel="noopener">GitHub</a>.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="hljs highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo new <span class="hljs-string">"My New Post"</span><br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/writing.html" target="_blank" rel="noopener">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="hljs highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo server<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/server.html" target="_blank" rel="noopener">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="hljs highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo generate<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/generating.html" target="_blank" rel="noopener">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="hljs highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo deploy<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/deployment.html" target="_blank" rel="noopener">Deployment</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Welcome to &lt;a href=&quot;https://hexo.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hexo&lt;/a&gt;! This is your very first post. Check &lt;a href=&quot;https://hexo.
      
    
    </summary>
    
    
  </entry>
  
</feed>
