源码编译OpenJdk 8,Netbeans调试Java原子类在JVM中的实现(Ubuntu 16.04)
<h1>一、前言</h1><p>前一阵子比较好奇,想看到底层(虚拟机、汇编)怎么实现的java 并发那块。</p>
<p>volatile是在汇编里加了lock前缀,因为volatile可以通过查看JIT编译器的汇编代码来看。</p>
<p>但是原子类,本来在jvm中就是汇编实现的,反而没法看。如果能实际跟踪一下断点,应该也算实际验证了。</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604104923343-1538479633.png" alt=""></p>
<p> </p>
<p>这边基本参照下面文章来的,补充了很多让初学者头疼的细节,并拓展了一部分,</p>
<p>包括调试java 原子类在jvm中的实现的一些细节。</p>
<p>https://marcin-chwedczuk.github.io/debugging-openjdk8-with-netbeans-on-ubuntu</p>
<p> </p>
<p>源码编译OpenJDK8,主要有以下几个步骤:</p>
<ul>
<li>下载Ubuntu</li>
<li>下载OpenJdk源码</li>
<li>下载Boot JDK,一般要比当前要编译的版本低</li>
<li>安装必要的依赖</li>
<li>configure && make</li>
</ul>
<p>上面几步搞完,基本虚拟机就可用了。但离调试,还有一点点距离。</p>
<p>用NetBeans调试JVM代码,有以下几个步骤:</p>
<ul>
<li>下载NetBeans</li>
<li>配置OpenJdk工程</li>
<li>配置Java工程</li>
<li>Debug OpenJdk(即虚拟机源码)</li>
</ul>
<p> </p>
<h1>二、源码编译OpenJDK8</h1>
<h2>1、下载Ubuntu</h2>
<p>我用的16.04,链接地址:https://www.ubuntu.com/download/alternative-downloads</p>
<p>我是用vmvare装的,配置建议给高一点。</p>
<p> </p>
<h2>2、下载OpenJdk源码</h2>
<p>据原文说法,OpenJDK 使用Mercurial进行版本管理。另外一个名叫AdoptOpenJDK project.提供了OpenJDK的镜像,可以让我们用git下载。</p>
<p>站点的官网如下:https://adoptopenjdk.net/about.html </p>
<p>主页上说他们的目标就是:</p>
<p>Provide a reliable source of OpenJDK binaries for all platforms, for the long term future.</p>
<p>据我的使用体验来说,之前编译过一次OpenJDK,各种报错,各种改源码才能编译通过。这次确实编译很顺,代码一句没改。</p>
<p>看起来,代码还是比较可靠的。</p>
<p>不扯别的了,直接clone搞下来吧,我这边是直接在/home/ckl目录下执行的shell:</p>
<div class="cnblogs_code">
<pre>git clone --depth <span style="color: rgba(128, 0, 128, 1)">1</span> -b master https:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">github.com/AdoptOpenJDK/openjdk-jdk8u.git</span></pre>
</div>
<p> </p>
<h2>3、下载Boot JDK</h2>
<p>编译过jdk的同学应该知道,我们得先有只母鸡才能编译openJDK源码。</p>
<p>这边我用的oracle的jdk 1.7,这边贴个csdn的下载链接,我那天弄的时候官网速度太慢。</p>
<p>https://download.csdn.net/download/qq_33499492/10288883</p>
<p> </p>
<p>怎么安装就不说了,我解压后放在/usr/local</p>
<p>记得修改环境变量(/ect/profile):</p>
<div class="cnblogs_code">
<pre>export JAVA_HOME=/usr/local/jdk1.<span style="color: rgba(128, 0, 128, 1)">7</span><span style="color: rgba(0, 0, 0, 1)">.0_80
export JRE_HOME</span>=${JAVA_HOME}/<span style="color: rgba(0, 0, 0, 1)">jre
export CLASSPATH</span>=.:${JAVA_HOME}/lib:${JRE_HOME}/<span style="color: rgba(0, 0, 0, 1)">lib
export PATH</span>=${JAVA_HOME}/bin:$PATH</pre>
</div>
<p>然后,source /ect/profile 使之生效。</p>
<p> </p>
<h2>4、安装依赖</h2>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">sudo</span> apt <span style="color: rgba(0, 0, 255, 1)">install</span><span style="color: rgba(0, 0, 0, 1)"> \
libx11</span>-<span style="color: rgba(0, 0, 0, 1)">dev \
libxext</span>-<span style="color: rgba(0, 0, 0, 1)">dev \
libxrender</span>-<span style="color: rgba(0, 0, 0, 1)">dev \
libxtst</span>-<span style="color: rgba(0, 0, 0, 1)">dev \
libxt</span>-<span style="color: rgba(0, 0, 0, 1)">dev \
libcups2</span>-<span style="color: rgba(0, 0, 0, 1)">dev \
libfreetype6</span>-<span style="color: rgba(0, 0, 0, 1)">dev \
libasound2</span>-dev</pre>
</div>
<p> </p>
<p>这个依赖不够,我这边装的时候,还报了一些依赖缺失,直接安装报错提示里的执行命令下载就完了。</p>
<p>我这里遇到比较坑的一点是(当然我对ubuntu完全不熟),一开始用的是官方的repository 源,后来换成阿里云的,各种报错。</p>
<p>吓得我赶紧改回来了,就没问题了。</p>
<p>这里遇到问题可以咨询我。</p>
<p> </p>
<h2>5、配置脚本</h2>
<p>在/home/ckl/openjdk-jdk8u下,新建脚本build.sh:</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604091109871-23614449.png" alt=""></p>
<p>build.sh:</p>
<div class="cnblogs_code">
<pre>bash ./configure --with-target-bits=<span style="color: rgba(128, 0, 128, 1)">64</span> --with-boot-jdk=/usr/local/jdk1.<span style="color: rgba(128, 0, 128, 1)">7</span>.0_80/ --with-debug-level=slowdebug --enable-debug-symbols ZIP_DEBUGINFO_FILES=<span style="color: rgba(128, 0, 128, 1)">0</span>
<span style="color: rgba(0, 0, 255, 1)">make</span> all ZIP_DEBUGINFO_FILES=<span style="color: rgba(128, 0, 128, 1)">0</span></pre>
</div>
<p>给build.sh增加可执行权限并执行:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">chmod</span> +x build.<span style="color: rgba(0, 0, 255, 1)">sh</span><span style="color: rgba(0, 0, 0, 1)">
.</span>/build.<span style="color: rgba(0, 0, 255, 1)">sh</span></pre>
</div>
<p> </p>
<h2>6、编译成功的效果</h2>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604093806819-511639195.png" alt=""></p>
<p> </p>
<p> 切换到对应目录下:</p>
<div class="cnblogs_code">
<pre>/home/ckl/openjdk-jdk8u/build/linux-x86_64-normal-server-slowdebug/jdk/<span style="color: rgba(0, 0, 0, 1)">bin
.</span>/java -version</pre>
</div>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604094023936-1639944068.png" alt=""></p>
<p> </p>
<p>在该目录下,新建个HelloWorld来运行一下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">public class HelloWorld {
public static void main(String[] args) {
System.out.println(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hello world</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}</span></pre>
</div>
<p> </p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604094207127-2064359878.png" alt=""></p>
<p> </p>
<h1>三、Netbeans调试JVM</h1>
<h2>1、下载NetBeans 8.2</h2>
<p>下载安装主要参考:</p>
<p>https://netbeans.org/community/releases/82/install.html</p>
<p> </p>
<p>NetBeans主页上,最新版本出到11.0了,但是在网上看到都是用的NetBeans 8开头版本的,有时间再折腾吧。</p>
<p>我这里下载的是8.2,链接:</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604094912029-655162425.png" alt=""></p>
<p>https://netbeans.org/downloads/8.2/</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604095038770-986637464.png" alt=""></p>
<p>因为OpenJDK是c++写的,所以我们必须选择带C/C++支持的,我这里直接选All。</p>
<p>另外,注意选linux平台,最好选英语,免得出幺蛾子。</p>
<p> </p>
<h2>2、下载oracle jdk 1.8</h2>
<p>为啥又要下载jdk? 这个jdk不是编译openJdk源码用的那个,这个是运行NetBeans 8.2需要。</p>
<blockquote>
<p>The Java SE Development Kit (JDK) 8 is required to install NetBeans IDE.</p>
</blockquote>
<p>下完安装后,把环境变量里设的jdk路径改掉吧:</p>
<div class="cnblogs_code">
<pre>export JAVA_HOME=/usr/local/jdk1.<span style="color: rgba(128, 0, 128, 1)">8</span><span style="color: rgba(0, 0, 0, 1)">.0_211
export JRE_HOME</span>=${JAVA_HOME}/<span style="color: rgba(0, 0, 0, 1)">jre
export CLASSPATH</span>=.:${JAVA_HOME}/lib:${JRE_HOME}/<span style="color: rgba(0, 0, 0, 1)">lib
export PATH</span>=${JAVA_HOME}/bin:$PATH</pre>
</div>
<div class="cnblogs_code">
<pre>source /etc/profile</pre>
</div>
<p> </p>
<h2>3、安装net beans</h2>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604095856977-1059896173.png" alt=""></p>
<p> </p>
<p> 安装:</p>
<div class="cnblogs_code">
<pre>./netbeans</pre>
</div>
<p> </p>
<p>安装过程,记得不要全部默认,可以自己定制化:</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604100349969-1924436333.png" alt=""></p>
<p> </p>
<p> <img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604100403418-1772857728.png" alt=""></p>
<p> </p>
<h2>4、配置Open JDK工程</h2>
<p>安装完成后,在桌面上,会生成一个图标,直接双击启动。</p>
<p>后面的步骤,可以参考:</p>
<p>https://marcin-chwedczuk.github.io/debugging-openjdk8-with-netbeans-on-ubuntu</p>
<p> </p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604100722419-972950632.png" alt=""></p>
<p> </p>
<p> <img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604100808420-928905594.png" alt=""></p>
<p> </p>
<p> <img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604101100941-1689199255.png" alt=""></p>
<p> </p>
<p>然后接下来几步都可以直接next,最后点finish会开始configure。</p>
<p> </p>
<h2>5、运行HelloWorld</h2>
<p>配置我们的openjdk工程去运行之前写的HelloWorld程序。</p>
<p> </p>
<p>对着openjdk-jdk8u工程,右键选择properties:</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604101803843-141929661.png" alt=""></p>
<p> </p>
<p>选择编译出的jdk来运行我们的class:</p>
<p> </p>
<p> <img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604101900691-1409054637.png" alt=""></p>
<p> </p>
<p>应该能看到输出Hello World了。</p>
<p> </p>
<h2>6、调试Hello World</h2>
<p>System.out.println(...)会调用jdk/src/share/native/java/io/io_util.c的writeBytes</p>
<p>直接在该函数打个断点,然后debug</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604102301934-469925428.png" alt=""></p>
<p> </p>
<p>不出意外,应该会停到该断点。</p>
<p> </p>
<h2>7、在netbeans中新建java工程,并调试jvm</h2>
<p>我们来调一个有用点的程序,看看原子类在jvm中的实现到底是不是像网上的博客那样运行的。</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604102521233-254467541.png" alt=""></p>
<p> </p>
<p>先像上面这样,建个java工程,写点代码,然后运行。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.util.concurrent.atomic.AtomicBoolean;
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.util.concurrent.atomic.AtomicInteger;
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
*
* </span><span style="color: rgba(128, 128, 128, 1)">@author</span><span style="color: rgba(0, 128, 0, 1)"> ckl
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> TestSample {
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> AtomicInteger stop = <span style="color: rgba(0, 0, 255, 1)">new</span> AtomicInteger(12<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
* </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> args the command line arguments
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args) {
Boolean result </span>= stop.compareAndSet(1314, 1413<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> TODO code application logic here</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (result){
System.out.println(</span>" true result "<span style="color: rgba(0, 0, 0, 1)">);
}</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
System.out.println(</span>"false result"<span style="color: rgba(0, 0, 0, 1)">);
}
}
}</span></pre>
</div>
<p> </p>
<p> </p>
<p> 调试jvm:</p>
<p>对着openjdk-jdk8u工程点右键,properties。</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604102918736-670138112.png" alt=""></p>
<p> </p>
<p>建立断点,cas调用一般会调用unsafe.cpp的以下代码:</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604103152809-1051955571.png" alt=""></p>
<p> </p>
<p>断点ok了,然后点选中openjdk-jdk8u工程后,点击debug按钮:</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604103014004-751839113.png" alt=""></p>
<p> </p>
<p> 果然,程序马上就停在这了。但是,cas操作可能在很多地方都调用了,所以我们要仔细观察Variables窗口,看看是不是我们发起的那个调用:</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604103354528-470098026.png" alt=""></p>
<p> </p>
<p>跳过了十多次以后。。。</p>
<p>。。。</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604103521204-754366728.png" alt=""></p>
<p> </p>
<p> 稍微跟一下:</p>
<p><img src="https://img2018.cnblogs.com/blog/519126/201906/519126-20190604103606841-1982525325.png" alt=""></p>
<p>直接进到这段汇编了,用了cmpxchg指令来实现cas,还加了lock前缀(mp为1)。lock前缀下次讲。主要是锁总线,或者锁缓存,达到原子操作的目的。</p>
<p> </p>
<p>有问题欢迎留言,如果有问题,也可以加我微信交流。</p>
<p> </p><br><br>
来源:https://www.cnblogs.com/grey-wolf/p/10971741.html
頁:
[1]