不知道哪一次更新,也不知道因为更新了什么,PC 上的 IGV 突然就不能用了。除了换一台 Mac 以外还能怎么办。下文记录了 debug 的过程以及一点思考。
前奏
IGV 这个工具因为是 Java 全平台适配的,一般不太容易出现 bug。在 Windows 上常见的问题是由于 Java 32位和64位版本问题造成的。在 64 位的电脑上安装了 32 位的 Java(通常默认就是32位)后,如果给 IGV 分配的内存超过了 2G,就会报内存错误,直接体现为 igb.bat 点击后无法打开。最直接的方法就是把 Java 升级到64位。
然而,不知道是哪一次更新,不知道是因为更新了什么,我的 bug 表现是 IGV 可以正常启动但启动后只要进行任意的一次点击就会闪退。最初我的猜测是因为某一个内容的更新,导致了Java,Windows 和 IGV 三者不兼容。于是我分别更新了最新版的 IGV 和最新版的 Java,然后还升级过一次电脑系统,问题都没有得到解决。当时时间有限,debug 的最直接方式就是绕开 bug。
替代方案
必须要用的软件突然不能用了又急着用,立刻买个 Mac 就可以避免 Windows 上的问题,虽然钱不是问题,但问题是没钱,所以只能寻找其它替代方案。因为 Java 本身是全平台通用的,所以在服务器上下载了 IGV linux 版本,然后通过 Xming 在本地电脑上调用 Linux 开启的 IGV 图形操作界面。
这个方法解决了燃眉之急,但是从服务器通过 Xming 在本地进行点选,很多操作会用明显的卡顿和延迟而且分辨率很低。并非长久之计。
dubug 过程
首先需要看一下相关 IGV 报错信息
A fatal error has been detected by the Java Runtime Environment:
EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffe51899e54, pid=8364, tid=0x0000000000004590
JRE version: Java(TM) SE Runtime Environment (8.0_191-b12) (build 1.8.0_191-b12)
Java VM: Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode windows-amd64 compressed oops)
Problematic frame:
C [atig6pxx.dll+0x9e54]
Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
If you would like to submit a bug report, please visit:
http://bugreport.Java.com/bugreport/crash.jsp
The crash happened outside the Java Virtual Machine in native code.
See problematic frame for where to report the bug.
阅读报错信息
因为我本身不懂 Java,只能根据报错提取自己认为关键的内容进行检索。
从以往的经验来看,首先要重点关注哪里失败或者不能启动。在 log 文件 header 部分恰好出现了一句话「Failed to write core dump. Minidumps are not enabled by default on client versions of Windows」,这个信息看起来非常关键,很可能是因为 Windows 不可以写入 core dump 导致的问题,那么自然应该首先开启 core dump 试试。
在 SO 上查到了 core dump 的开启方式,对于Java 8 使用 -XX:+CreateMinidumpOnCrash
参数即可。修改 igv.bat 文件再命令行里添加该参数后重新运行,问题依旧存在,只不过这次除了会生成 log 文件以外,还会生成一个大小几 G 的 core dump 文件。看来这个问题并不是 IGV 闪退的原因。
在检索的过程中,有中文帖记录出现这样的问题是因为「JRE version 和 JDK 不一样」。所以重新下载一个版本对应的 JDK 或者 JRE 就可以解决。虽然我一开始其实也分不清他来有什么区别,也不确定自己是不是同时装了这两个东西,但是抱着试试又不会挂的心态,还是再一次卸载了电脑里的Java。
因为担心是之前 Java 升级有问题,我还专门下载了官方的Java 卸载工具,想把自己电脑里所有和 Java 相关的东西都卸载个干干净净,重新来过。
按照 IGV 网站上给的 Java 8 下载链接,我又一次重新安装了 Java,运行之后真的还没有出现闪退的情况,因为根本就打不开了。其实这就是上文提到的因为 IGV 网站上的 Java 下载链接引导我下载了 32 位 Java 版本,但是 IGV.bat 中使用的内存配置是4G,超过了限额地缘故。我又不得不再一次卸载 32 位重新安装了 64 位Java,闪退问题依旧存在。
不过,至此我确认了电脑不存在所谓的「JRE和JDK版本不一致的问题」,版本信息如下:
# JRE version: Java(TM) SE Runtime Environment (8.0_191-b12) (build 1.8.0_191-b12)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode windows-amd64 compressed oops)
控制变量
Debug 一个非常重要的经验是需要 控制变量。如果有条件,不妨找一台能够正确运行某个程序的机器,看看它和自己报错的机器有什么差别。
为此,我分别查看其他人几台可以正确运行 IGV 的 PC,发现有人的IGV版本较低,有人的 Java 版本较低,但是大家都可以正常使用,于是我又分别测试了几个低版本的 Java 或者 IGV,然并卵。考虑自己的笔记本系统和软件版本与我的工作 PC 高度一致,又用最新版的 IGV 和 Java 8 在笔记本上进行了测试,神奇的是在笔记本却可以正常运行。
至此,我基本懵逼。因为Windows 系统和 IGV 版本以及 Java 版本完全一致的情况下,笔记本可以正常运行IGV,但是 PC 却不可以。这里面一定有某些不可告人的力量。
理解报错信息
通过上面几步折腾,问题的关键应该不在于解决 「Failed to write core dump」而是需要再向前回溯,这个时候就要理解 Java 生成的log文件究竟传达了哪些信息。
通常只有严重的错误引起Java进程非正常退出,出现了Crash,才会产生一个文件名为err+pid number 的日志文件。
检索后发现很多 Java 的报错都会出现 EXCEPTION_ACCESS_VIOLATION(0xc0000005)
这一句话,他意味着Java 应用 Crash 时正在运行 JVM 自己的代码,而不是外部的 Java 代码。在这个位置还可能出现 SIGSEGV(0xb)
或者 EXCEPTION_STACK_OVERFLOW
这样的内容。
再往下看另一个重要信息点是
Problematic frame:
C [atig6pxx.dll+0x9e54]
这里的信息是显示 Crash 时 JVM 正在从哪个库文件执行代码。我遇到问题是 C,还可能是 V 和 J 等等。他们的意思如下
FrameType Description:
C: Native C frame
j: Interpreted Java frame
V: VMframe
v: VMgenerated stub frame
J: Other frame types, including compiled Java frames
接下来我又阅读了一下 Java 的 debug 指南,里面的分析思路都是要看 C 后面提示了什么信息,然后给了一些 debug 的建议:
The first step to solving a crash in a native library is to investigate the source of the native library where the crash occurred.
If the native library is provided by your application, then investigate the source code of your native library. A significant number of issues with JNI code can be identified by running the application with the
-Xcheck:jni
option added to the command line. See The -Xcheck:jni Option.If the native library has been provided by another vendor and is used by your application, then file a bug report against this third-party application and provide the fatal error log information.
If the native library where the crash occurred is part of the Java Runtime Environment (JRE) (for example awt.dll, net.dll, and so forth), then it is possible that you have encountered a library or API bug. If so, gather as much data as possible and submit a bug or report, indicating the library name. You can find JRE libraries in the jre/lib or jre/bin directories of the JRE distribution. See Submit a Bug Report.
仔细阅读之后发现基本没读出啥东西,但是现在从报错信息中我已经明确了问题应该指向 atig6pxx.dll
。
指果所因
既然定位到了atig6pxx.dll ,就要查查它是什么,搜索 atig6pxx.dll windows10
在前面几条结果中,我看到了这样几个信息
AMD Graphics Driver not working/incompatible with Win 10 Technical Preview
OpenGl for crimson drivers
如此看来,这个 atig6pxx.dll 和AMD 的显卡驱动有关,而我的两个显示器之一显卡确实是AMD的,从查询结果来看,AMD 显卡驱动的这个 atig6pxx.dll 和某些 win10 版本搭配在一起就存在问题(我用的就是有问题的版本?)。
接下来再试试 atig6pxx.dllJava
的搜索结果,真还搜到了相关的一个帖子,官方给的回复大意是要发帖子的人详细确定显卡和升级之类的各种信息,看来这个问题确实是 Java 和 PC 的显卡驱动有 bug。同时又联想到我的控制变量实验,笔记本是NVIDIA显卡而不是AMD,进一步相信问题可能真出自 AMD 显卡上。
解决问题
确定了bug的原因,接下来就要考虑如何解决问题。
首先需要知道 Java 和显卡驱动是怎么能联系到一起的,检索一下 Javagraphics
找到了 官网的一些说明。 这里主要涉及 Java 2D 这么一个东西,在搜索的过程中又找到了 Java 2D 的一些选项。在检索的过程中,还从 SO 看到了一个稍微相关的帖子,其中一个建议也是对显卡进行一系列的测试,其中提到了参数 -Dsun.Java2d.d3d=false
。这里的参数和Java 2D 的参数吻合而且有一点眼熟,看到这里就要回过头重新看看 igv.bat 文件。
官方提供的 igv.bat 实际命令如下
::Get the current batch file's short path
for %%x in (%0) do set BatchPath=%%~dpsx
for %%x in (%BatchPath%) do set BatchPath=%%~dpsx
Java -Xmx4g -Dproduction=true -DJava.net.preferIPv4Stack=true -Dsun.Java2d.noddraw=true -jar %BatchPath%\lib\IGV.jar %*
仔细看一下,官方这条命令实际调用的程序来自 lib 目录的 igv.jar,只不过添加了几个平时自己根本不会在意的参数,其中 -Dsun.Java2d.noddraw=true
和之前 SO 中建议添加的参数非常类似,而这个参数又是一个和显示性能相关的参数。隐约感觉问题就出在这里。
继续贯彻控制变量的思想,首先不加任何参数在 lib 目录下直接运行 igv.jar,嗯,真的没有闪退。回到 bat 依次修改上面三个参数进行测试,直到我把 -Dsun.Java2d.noddraw=true
改为 false 后,bug 消失,至此问题解决。
复盘与反思
综上,通过学习 log 文件,查找关键内容,控制变量测试,一步一步把 Java 的运行错误和显卡联系起来。不过我是万万没想到这错误竟然是和显卡相关。
进一步了解下Dsun.Java2d.noddraw这个参数的作用
The following list describes some useful properties on Windows platforms.
The DirectDraw/GDI pipeline is the default pipeline for Windows. Change this default as follows:
-Dsun.Java2d.noddraw=true
Disable the use of DirectDraw pipeline. GDI will be used instead.
-Dsun.Java2d.noddraw=false
Enable the use of DirectDraw pipeline.
-Dsun.Java2d.d3d=false
Disable the use of Direct3D pipeline.
也就是说,因为AMD 显卡驱动中 atig6pxx.dll
的问题,我目前的电脑并不能只使用 GDI 来进行渲染,而是必须要开启 DirectDraw。至于更深次的原因暂时没有深究的必要。
回顾整个 debug 的过程,虽然一直在尝试使用控制变量的思路,但是具体的测试顺序不对。
使用别人的电脑来控制变量,不相关的因素太多,各种软件版本的不一致只是表象,并没有指明到是真正的显卡差别;而使用自己的笔记本和台式机做比较,才排除了软件版本的关系。
最后进行的控测试才是变量最可控的,应该首先就进行的在同一个电脑上对同样的软件进行不同的操作。其中一个操作是官方并不推荐的直接运行 lib 中的 igv.jar 程序,另一个操作是运行官方推荐的添加了参数的 igv.bat 。如果把这个测试提到第一步来做,就会节省大量的测试时间。
最后,对于 debug 的思考:
仔细阅读报错信息确定关键词
成熟的工具一定有着非常详细的报错信息,而我们要做的是从详细的报错信息中提炼关键词进行检索
按照从小到大的顺序尽量找到可以正确运行的方式
想要找到问题所在很关键的一点知道谁或者在哪里可以成功。这一步对之后的 debug 过程十分重要。
所谓从小到大的意思是正确运行和错误运行之间的差异从小到大。最好可以在一台电脑上完成,只是改变运行方式或者参数;如果不行就找配置尽量类似的电脑或服务器进行测试,以此类推。如果我首先能想到抛弃官方的建议打开方式去运行 jar 程序,直接就可以锁定参数问题。
控制变量是进行测试应该遵循的首要思路
通过检索,我们可以找到大量的相关信息,可能会存在各种五花八门的解决方法。如何判断哪些值得尝试哪些不值得尝试非常重要。对于本文的 bug,即便检索到了很多关于软件版本的问题,只要我在版本相同的另一台电脑运行成功,那么所有和 Java 版本,Windows 版本以及 IGV 版本相关的内容都可以忽略掉,就省去了多次卸载安装这样的无用功。同时根据检索信息再结合控制变量的思想就更加容易锁定问题,进而找到解决方式。