- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
本周协助测试同事对一套测试环境进行扩容,我们扩容很原始,就是新申请一台机器,直接把jdk、resin容器(一款servlet容器)、容器中web应用所在的目录,全拷贝到新机器上,servlet容器和其中的应用启动没问题。以为ok了,等到测试时,web应用报错,初始化某个类出错。报错的类长下面这样:
com.thinkive.tbascli.TBASCli
static {
String os = "win";
String pathSep = System.getProperty("path.separator");
if (pathSep.equalsIgnoreCase(":")) {
os = "linux";
}
try {
// 1
System.loadLibrary("TBASClientJNI");
} catch (SecurityException var5) {
var5.printStackTrace();
System.out.println("Load TBASClientJNI Library Failed");
} catch (UnsatisfiedLinkError var6) {
URL url = TBASCli.class.getClassLoader().getResource("");
String path = (new File(URI.create(url.toExternalForm()))).getAbsolutePath();
if (os.equalsIgnoreCase("win")) {
// 2
loadDLL(path);
} else if (os.equalsIgnoreCase("linux")) {
// 3
loadSO(path);
} else {
loadDLL(path);
}
}
...
}
简单来说,就是处理请求的代码用到这个类,然后类加载,执行static,结果执行System.loadLibrary失败了.
失败了也没啥,问题是,这个类是个底层框架里的类,然后失败原因也不打日志.
当时已经心里骂过人了,现在就不说啥了,说说当时处理过程.
当时以为是 System.loadLibrary("TBASClientJNI"); 失败,抛了异常,进了catch分支,以为会进 。
loadDLL(path);
loadSO(path);
private static void loadSO(String path) throws SecurityException, UnsatisfiedLinkError {
String sep = System.getProperty("file.separator");
System.load(path + sep + "libTBASClient.so");
System.load(path + sep + "libTBASClientJNI.so");
}
这里看到最终会调System.load方法,就想用arthas的watch观察下参数:类似于下面这样:
watch java.lang.System load '{params, target, returnObj, throwExp}' -x 2
结果工具报错:
[arthas@110269]$ watch java.lang.System load
Affect(class count: 0 , method count: 0) cost in 31 ms, listenerId: 1
No class or method is affected, try:
1. Execute `sm CLASS_NAME METHOD_NAME` to make sure the method you are tracing actually exists (it might be in your parent class).
2. Execute `options unsafe true`, if you want to enhance the classes under the `java.*` package.
3. Execute `reset CLASS_NAME` and try again, your method body might be too large.
按照工具的第二条提示,设置了,也还是报错,反正,当时这条路是没有走下去.
当时也试了去watch当前类的loadSO方法,不知道为啥,也是没观察到东西,我们用的jdk1.7,不清楚有没有影响.
上面报错这个类,在我们的TBASClientJNI-2.2.0.jar中,我想着还是覆盖框架类,加点日志试试吧,于是在应用中,新增了一个包名类名都一致的类:com.thinkive.tbascli.TBASCli,修改了其中的代码:
我们的应用,打出来的jar是在test-web.jar中,最终部署的时候,应用jar和依赖的框架jar是在同一个文件夹下,在同一个文件夹下的话,类加载的顺序是没法保证的,所以,我当时在开发环境验证了下,发现日志能看到,结果等我把改后的jar放到测试环境时,发现完全没生效,看不到日志,应该就是优先加载了旧的class.
当时也看了下,类加载的一个情况,利用arthas查看类来自哪个jar的哪个文件(以下截图来自开发环境,当时没截图)
这里也扩展下,其他类加载相关的命令:
查看类加载器及hash 。
classloader -l 。
查看类加载器的加载路径 。
classloader -c 3bd3ac38 。
然后暂时也没啥好办法,看看是不是两边是不是漏复制了啥,或者不小心改到什么地方了。把两边的几个文件夹仔细对比了下,没发现啥问题.
也对比了一些环境变量,比如linux默认会 。
后面在两台机器上各种排查,命令一顿敲,后面发现,在原机器上执行lsof -p pid,查看进程打开的so文件时,发现两边不太一样.
这里还要补充解释下,前面大家以为我们只是一个so文件,其实是两个,如下,其中一个是xxxJNI.so,我们代码里也是去加载这个,而不带JNI的这个so,是xxxJNI.so的内部依赖的so.
[root@xxx-access ~]# ll /test-web/WebRoot/WEB-INF/classes |grep TBA
-rw-r--r-- 1 root root 20704 Feb 20 13:19 libTBASClientJNI.so
-rw-r--r-- 1 root root 103904 Feb 20 13:19 libTBASClient.so
所以,实际上来说,我们的jdk必须加载了这两个so,请求才能正常处理.
我在原机器上执行lsof的结果是:
[root@server172 ~]# lsof -p 28644|grep TBA
java 28644 root mem REG 253,1 103904 101004125 /usr/lib64/libTBASClient.so
java 28644 root mem REG 253,0 20704 21663079 /test-web/WebRoot/WEB-INF/classes/libTBASClientJNI.so
而在新机器执行的结果是:
[root@xxx-access ~]# lsof -p 110269|grep \\.so |grep TBA
java 110269 root mem REG 253,0 20704 34821647 /test_web/WebRoot/WEB-INF/classes/libTBASClientJNI.so
可以发现,原来的机器虽然正常运行,但是,加载的so竟然在不同文件夹下,带JNI的这个libTBASClientJNI.so,确实用的是项目路径下的;而那个libTBASClient.so,居然是/usr/lib64下的,我们确实没拷贝/usr/lib64下的那个so到新机器,估计就是这个原因了.
新机器上呢,只加载了一个so,少了一个so,估计这也就是问题原因了.
我在新机器上,试了两种改法,都有效果:
在/usr/lib64下放上那个libTBASClient.so 。
修改/etc/profile,设置:
export LD_LIBRARY_PATH=/test-web/WEB-INF/classes/:$LD_LIBRARY_PATH
为啥改了有效果呢,下面看看原理.
回到报错的那行代码:
System.loadLibrary("TBASClientJNI");
了解它的最好的办法,还是在本地debug.
java.lang.System#loadLibrary
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
java.lang.Runtime#loadLibrary0
synchronized void loadLibrary0(Class fromClass, String libname) {
ClassLoader.loadLibrary(fromClass, libname, false);
}
内部实现如下:
static void loadLibrary(Class fromClass, String name,
boolean isAbsolute) {
// 1 获取classloader
ClassLoader loader =
(fromClass == null) ? null : fromClass.getClassLoader();
// 2 用系统property的值来初始化field:usr_paths/sys_paths
if (sys_paths == null) {
usr_paths = initializePath("java.library.path");
sys_paths = initializePath("sun.boot.library.path");
}
// 3 外部传参为false,进不去本分支,不管
if (isAbsolute) {
if (loadLibrary0(fromClass, new File(name))) {
return;
}
throw new UnsatisfiedLinkError("Can't load library: " + name);
}
if (loader != null) {
// 4 由调用本方法的类的classloader来负责查找library,由具体classloader覆写
String libfilename = loader.findLibrary(name);
if (libfilename != null) {
File libfile = new File(libfilename);
if (!libfile.isAbsolute()) {
throw new UnsatisfiedLinkError(
"ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
}
if (loadLibrary0(fromClass, libfile)) {
return;
}
throw new UnsatisfiedLinkError("Can't load " + libfilename);
}
}
// 5 根据上文初始化处,这里即是从sun.boot.library.path 这个变量中加载
for (int i = 0 ; i < sys_paths.length ; i++) {
File libfile = new File(sys_paths[i], System.mapLibraryName(name));
if (loadLibrary0(fromClass, libfile)) {
return;
}
}
// 6 根据上文初始化处,这里即是从java.library.path 这个变量中加载
if (loader != null) {
for (int i = 0 ; i < usr_paths.length ; i++) {
File libfile = new File(usr_paths[i],
System.mapLibraryName(name));
if (loadLibrary0(fromClass, libfile)) {
return;
}
}
}
// Oops, it failed
throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
}
可以看到上面注释,加载也就是从3个地方加载; 。
4处,首先从classloader加载 。
[arthas@110269]$ classloader -c 3bd3ac38
file:/test-web/WebRoot/WEB-INF/classes/
...
5处,从sun.boot.library.path加载 。
[arthas@110269]$ sysprop sun.boot.library.path
KEY VALUE
sun.boot.library.path /usr/local/java/jdk1.7.0_80/jre/lib/amd64
6处,从java.library.path加载 。
sysprop java.library.path 。
所以,我们就能解释如下的结果了:
[root@xxx-access ~]# lsof -p 110269|grep \\.so |grep TBA
java 110269 root mem REG 253,0 20704 34821647 /test_web/WebRoot/WEB-INF/classes/libTBASClientJNI.so
应该就是走了4处的逻辑,才加载到这个JNI的so.
那么,为啥又没加载到libTBASClient.so呢,我在网上看到的解释是,so内部加载其他依赖的so,这时候,内部已经不是java代码了,不可能走这段 java.lang.ClassLoader#loadLibrary 逻辑,所以,此时,就不是这段逻辑了.
那么,对于libTBASClientJNI.so依赖的so,又是去哪里加载呢,这块呢,我的理解不是很深入,我的理解是,在windos机器,会去PATH环境变量中加载;在linux,会去环境变量LD_LIBRARY_PATH中指定的路径加载.
但根据我这边的现象看,比如最终是在/usr/lib64中找到了libTBASClientJNI.so,但我的LD_LIBRARY_PATH并没有设置/usr/lib64,所以,jvm的实现中估计还会根据 java.library.path 这个属性中的路径去查找。因为我程序中,查看arthas的sysprop,只有它下面有/usr/lib64这个路径.
sysprop java.library.path即可看到.
java -XshowSettings:properties 。
在windows下,java.library.path初始值来自PATH环境变量.
linux下,有默认值,如上面这几个路径;另外,如果有设置LD_LIBRARY_PATH环境变量,那么java.library.path的值就等于默认的几个路径(/usr/lib64、/lib64、/lib、/usr/lib) + LD_LIBRARY_PATH的值.
java加载第一层的so,主要是根据classloader去加载、其次是 sun.boot.library.path 、再其次是java.library.path.
加载第一层so依赖的so,在jdk中貌似也是根据java.library.path;如果是非jdk,应该是根据LD_LIBRARY_PATH环境变量.
而java.library.path的默认值(不显示设置的情况下),在windows下就是来源于PATH,在linux下来源于LD_LIBRARY_PATH和几个默认路径(/usr/lib64、/lib64、/lib、/usr/lib),具体可以执行 java -XshowSettings:properties 查看.
参考:
https://stackoverflow.com/questions/29968292/what-is-java-library-path-set-to-by-default 。
https://stackoverflow.com/questions/20038789/default-java-library-path 。
https://stackoverflow.com/questions/16227045/how-to-add-so-file-to-the-java-library-path-in-linux 。
通过arthas的vmtool查看内存对象 。
https://arthas.aliyun.com/doc/vmtool.html 。
https://blog.csdn.net/qq_40911404/article/details/121741268 。
最后此篇关于JDK中动态库加载路径问题,一文讲清的文章就讲到这里了,如果你想了解更多关于JDK中动态库加载路径问题,一文讲清的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
BufferedImage image = ImageIO.read(SpriteSheet.class.getResource(path)); BufferedImage image = Image
希望有人能够帮助我解决将我的 React 应用程序推送到 Heroku 时遇到的问题。 heroku 日志反复显示以下错误。 at=error code=H10 desc="App crashed"
我是 Kotlin 的新手,我正在经历这样的例子。 . . package com.example.lambda1 import spark.Spark.get fun main(args: Arra
如果您已经安装了 32 位 JDK,请在中定义一个 JAVA_HOME 变量 Computer>System Properties>System Setting>Enviorment VAriable
我正在开发一个独立于平台的应用程序。我收到一个文件 URL*。在 Windows 上,这些是: file:///Z:/folder%20to%20file/file.txt file://host/f
我在 OSX、Objective-C 上。 我有一个像 这样的路径/NSURL /Users/xxx/Desktop/image2.png 但我将它传递给第三方应用程序,该应用程序会像 excpect
我已经安装了 Android studio 和插件的 DART,FLUTTER 来启动 flutter,但是因为我在创建我的第一个 flutter 项目时无法提供 sdk 路径。 最佳答案 我试图找出
127.0.0.1:8000/api/仅包含来自第二个应用程序的 url,但我将两个 url 模块链接到相同的模式。甚至有可能做到这一点吗? 第一个应用程序: from django.urls imp
对于大量图像(大约 1k,加上相同数量的拇指,在大约 500 个文件夹中),我们要求网站上使用的所有图像 URI 都必须具有 SEO 优化路径。它们已经准备好并提供完整的路径结构(每个文件夹包含一个具
为什么 f 不是一个文件?什么可能导致这种情况? String currentPhotoPath = "file:/storage/sdcard0/Pictures/someFileName.
Gradle 中的项目名称或路径中允许使用哪些字符? 它是否与特定操作系统的目录名称中允许的字符相同(例如: http://en.wikipedia.org/wiki/Filename#Reserve
我有一个包含文件夹路径的表格。我需要找到层次结构中这些文件夹之间的所有“差距”。我的意思是,如果表格包含这 3 个文件夹: 'A' 'A\B\C' 'A\B\C\D\E\F\G' 我需要在层次结构中找
我在 Linux 服务器上的/home/subversion 中安装了 svn - 那里有一个 ROOT 文件夹,其中包含 db 和 conf 等文件夹。没有映射到项目名称的文件夹,请有人告诉我如何列
对于我的图像位置:/src/assets/bitmap/sample.jpg 给出了关键配置: context: resolve('src') output: { path: resolve('b
我需要创建带有圆角的 SVG 路径,以将它们导出到 DXF 进行切割。我的问题是角应该是圆弧,而不是贝塞尔曲线。 使用 arc 命令相对容易处理直角,因为半径也是从拐角到圆弧起点的距离。对于其他角度,
大家好,我正在玩 Airflow,我正在阅读这篇很有帮助的 tutorial .我正在寻求帮助以更好地了解 Admin->Connection 如何在 Conn Type: File (path) 方
我的目标是定义R将用于安装和搜索库的单个路径。我read可以通过更改Rprofile.site安装路径中的R文件来完成。我在那里尝试了两个命令: .libPaths("D:/RLibrary") .L
我有一个问题:当我在一个页面中时,我想返回到上一页。我使用 $routeProvider。如何读取之前的 url? 我尝试在我的 Controller 中使用此代码但不起作用... angular.m
我正在尝试将一个文件从我的主干合并到一个分支(wc),并且对于看起来位于当前合并操作中不涉及的分支上的路径出现奇怪的未找到路径错误。 例如,在我们的 svn 项目中,我们有: 分行 分支 0 分支 1
我有一个树数据序列化如下: 关系:P到C是“一对多”,C到P是“一对一”。所以列 P 可能有重复的值,但列 C 有唯一的值。 P, C 1, 2 1, 3 3, 4 2, 5 4, 6 # in da
我是一名优秀的程序员,十分优秀!