
Java扩展第三方Jar包
前言
今天在B站大学学习并发编程的时候,老师引用了一个第三方的jar包(jol-core),maven坐标如下,来打印锁对象的Mark Word字节码,从而更直观察地多线程下加偏向锁的情况。
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.17</version>
</dependency>
这些都是挺常规的操作,接着老师的骚操作来了,扩展了jar包里面的方法。弹幕里面都是惊呼声,老师改了jar包,我们学习者没法复现了呀。
我这人就喜欢钻牛角尖,评论区翻遍了也没见得有这个Jar包修改方法的 好心人,那就只能自己动手咯。
视频地址
Jol-Core依赖加载失败
如果你没有这个问题可直接跳过
在加载Maven依赖的时候,Jol-Core这个Jar包就是拉不下来,因此我重新更新了一下Maven settings.xml
中的 mirror,有需要的直接在你的 mirrors中追加即可
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/central</url>
<mirrorOf>central</mirrorOf>
</mirror>
<mirror>
<id>sprintio</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>https://repo.spring.io/libs-snapshot/</url>
</mirror>
<mirror>
<id>huaweicloud</id>
<name>mirror from maven huaweicloud</name>
<url>https://mirror.huaweicloud.com/repository/maven/</url>
<mirrorOf>central</mirrorOf>
</mirror>
<mirror>
<id>maven-default-http-blocker</id>
<mirrorOf>external:http:*</mirrorOf>
<name>Pseudo repository to mirror external repositories initially using HTTP.</name>
<url>http://0.0.0.0/</url>
<blocked>true</blocked>
</mirror>
拉取源码
我这边目前是用的最新版本,直接在Maven除选中依赖右击 Download Soures
在左侧项目的外部库下面展开对应jar包,然后右键选打开于Explore
找到jol-core-0.17-sources.jar
包右击解压出来,我这边是用的7-Zip
新建项目
新建一个项目
将解压出来的
org
文件夹复制到新建项目中的src/main/Java
下将
META-INF
文件夹放入resource
文件夹中将
META-INF
下的pom.xml
文件复制到项目根目录下加载pom.xml, 运行Maven加载依赖即可
修改源码
打开ClassLayout
文件,双击两下shift
可快速查找文件,在Ctrl+O
查找toPrintable
方法,可以看到默认没有改源码之前只有一个toPrintable
方法
这里我是参考toPrintable()
方法进行重写的,看似吊炸天其实也就这样,没啥太多的代码,也就一百多行而已,那么我就来带大家一起来解读一下这个方法的作用吧
源码解读
如果您不需要解读请往下滑
toPrintable
主要用于输出一个对象的内部布局信息,包括对象的标头(Mark Word 和 Class Word)、数组长度(如果对象是数组),字段信息以及空间损失等。
public String toPrintable(Object instance) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
// 计算最长的类型名称
int maxTypeLen = "TYPE".length();
for (FieldLayout f : fields()) {
maxTypeLen = Math.max(f.typeClass().length(), maxTypeLen);
}
maxTypeLen += 2;
// 定义一些描述信息的字符串
String MSG_OBJ_HEADER = "(object header)";
String MSG_MARK_WORD = "(object header: mark)";
String MSG_CLASS_WORD = "(object header: class)";
String MSG_ARR_LEN = "(array length)";
String MSG_FIELD_GAP = "(alignment/padding gap)";
String MSG_OBJ_GAP = "(object alignment gap)";
// 计算最长的描述信息的长度
int maxDescrLen = "DESCRIPTION".length();
maxDescrLen = Math.max(maxDescrLen, MSG_OBJ_HEADER.length());
maxDescrLen = Math.max(maxDescrLen, MSG_MARK_WORD.length());
maxDescrLen = Math.max(maxDescrLen, MSG_CLASS_WORD.length());
maxDescrLen = Math.max(maxDescrLen, MSG_FIELD_GAP.length());
maxDescrLen = Math.max(maxDescrLen, MSG_OBJ_GAP.length());
for (FieldLayout f : fields()) {
maxDescrLen = Math.max(f.shortFieldName().length(), maxDescrLen);
}
maxDescrLen += 2;
// 定义格式化字符串
String format = "%3d %3d %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n";
String formatS = "%3s %3s %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n";
// 检查传入的实例是否为null
if (instance != null) {
try {
// 加载类,用于检查实例类型是否匹配
Class<?> klass = ClassUtils.loadClass(classData.name());
if (!klass.isAssignableFrom(instance.getClass())) {
throw new IllegalArgumentException("Passed instance type " + instance.getClass() + " is not assignable from " + klass + ".");
}
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Class is not found: " + classData.name() + ".");
}
}
// 输出类的名称和对象内部信息的标题行
pw.println(classData.name() + " object internals:");
pw.printf(formatS, "OFF", "SZ", "TYPE", "DESCRIPTION", "VALUE");
// 初始化标头、类标头和数组长度的字符串
String markStr = "N/A";
String classStr = "N/A";
String arrLenStr = "N/A";
// 获取标头、类标头和数组长度的大小
int markSize = model.markHeaderSize();
int classSize = model.classHeaderSize();
int arrSize = model.arrayLengthHeaderSize();
// 计算标头、类标头和数组长度的偏移
int markOffset = 0;
int classOffset = markOffset + markSize;
int arrOffset = classOffset + classSize;
// 如果实例不为null,获取标头信息
if (instance != null) {
VirtualMachine vm = VM.current();
if (markSize == 8) {
long mark = vm.getLong(instance, markOffset);
String decoded = (classSize > 0) ? parseMarkWord(mark) : "(Lilliput)";
markStr = toHex(mark) + " " + decoded;
} else if (markSize == 4) {
int mark = vm.getInt(instance, markOffset);
String decoded = (classSize > 0) ? parseMarkWord(mark) : "(Lilliput)";
markStr = toHex(mark) + " " + decoded;
}
// 如果类标头有大小,获取类标头信息
if (classSize == 8) {
classStr = toHex(vm.getLong(instance, classOffset));
} else if (classSize == 4) {
classStr = toHex(vm.getInt(instance, classOffset));
}
// 如果类是数组,获取数组长度信息
if (classData.isArray()) {
arrLenStr = Integer.toString(vm.getInt(instance, arrOffset));
}
}
// 输出标头、类标头和数组长度的信息
pw.printf(format, markOffset, markSize, "", MSG_MARK_WORD, markStr);
if (classSize > 0) {
pw.printf(format, classOffset, classSize, "", MSG_CLASS_WORD, classStr);
}
if (classData.isArray()) {
pw.printf(format, arrOffset, arrSize, "", MSG_ARR_LEN, arrLenStr);
}
long nextFree = headerSize();
// 输出字段信息
for (FieldLayout f : fields()) {
if (f.offset() > nextFree) {
pw.printf(format, nextFree, (f.offset() - nextFree), "", MSG_FIELD_GAP, "");
}
Field fi = f.data().refField();
pw.printf(format,
f.offset(),
f.size(),
f.typeClass(),
f.shortFieldName(),
(instance != null && fi != null) ? ObjectUtils.safeToString(ObjectUtils.value(instance, fi)) : "N/A"
);
nextFree = f.offset() + f.size();
}
long sizeOf = (instance != null) ? VM.current().sizeOf(instance) : instanceSize();
// 输出总大小和空间损失信息
if (sizeOf != nextFree) {
pw.printf(format, nextFree, lossesExternal, "", MSG_OBJ_GAP, "");
}
pw.printf("Instance size: %d bytes%n", sizeOf);
pw.printf("Space losses: %d bytes internal + %d bytes external = %d bytes total%n", lossesInternal, lossesExternal, lossesTotal);
pw.close();
return sw.toString();
}
重构方法
可以看出toPrintable
方法不止输出了MarkWord信息还有 Class Word、数组长度(如果对象是数组),字段信息以及空间损失等。
所以我们新增一个方法参考上述方法写就行了
public String toPrintableSimpleSerMs() {
return toPrintableSimpleSerMs(classData.instance());
}
private String toPrintableSimpleSerMs(Object instance) {
StringBuilder sb = new StringBuilder();
String markStr = "";
String remind = "";
// 获取标头的大小
int markSize = model.markHeaderSize();
// 设置标头的偏移
int markOffset = 0;
// 如果传入的实例不为null,获取标头信息
if (instance != null) {
VirtualMachine vm = VM.current();
if (markSize == 8) {
// 如果标头大小为8字节,获取标头信息并将其转换为二进制字符串
long mark = vm.getLong(instance, markOffset);
markStr = Long.toBinaryString(mark);
// 解析标头,获取额外的信息
remind = parseMarkWord(mark);
} else if (markSize == 4) {
// 如果标头大小为4字节,获取标头信息并将其转换为二进制字符串
int mark = vm.getInt(instance, markOffset);
markStr = Integer.toBinaryString(mark);
// 解析标头,获取额外的信息
remind = parseMarkWord(mark);
}
}
// 高位补0,确保二进制字符串长度为标头大小的倍数
int i = 1;
for (; i <= 8 * markSize - markStr.length(); i++) {
sb.append('0');
if (i % 8 == 0) {
sb.append(" ");
}
}
// 将标头的二进制字符串添加到输出中
for (; i <= 8 * markSize; i++) {
sb.append(markStr.charAt(i - (8 * markSize - markStr.length()) - 1));
if (i % 8 == 0) {
sb.append(" ");
}
}
// 添加解析后的标头信息
sb.append(remind);
// 返回包含标头信息的字符串
return sb.toString();
}
另外我还写了另外的一个简约版本, 只会输出MarkWord 头中的最后三位,也就是锁和锁的状态
/**
* 重写toPrintable方法,只输出Mark word二进制形式 Opt优化
*
* @return
*/
public String toPrintableSimpleSimplicity() {
return toPrintableSimpleSimplicity(classData.instance());
}
/**
* 重写toPrintable方法,只输出Mark word二进制形式 Opt优化
*
* https://serms.top
*
* @param instance
* @return
*/
public String toPrintableSimpleSimplicity(Object instance) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
if (instance != null) {
VirtualMachine vm = VM.current();
long markWord = vm.getLong(instance, 0); // Assuming the Mark Word is a long (64 bits)
pw.println("Mark Word Simplicity (binary):");
pw.println(toBinary(markWord));
} else {
pw.println("Mark Word: N/A");
}
pw.close();
return sw.toString();
}
private String toBinary(long value) {
return Long.toBinaryString(value);
}
重构Jar包
修改完代码之后
Ctrl+ F9
编译,编译之后找到原来的Jar包打开找到
ClassLayout.class
文件删除将修改好的
ClassLayout.class
文件复制进去
测试代码
方法一
调用新增方法
public static void main(String[] args) throws NoSuchFieldException, InterruptedException, IllegalAccessException {
test1();
}
public static void test1() {
A a = new A();
out.println(ClassLayout.parseInstance(a).toPrintableSimpleSerMs());
synchronized (a) {
out.println(ClassLayout.parseInstance(a).toPrintableSimpleSerMs());
}
out.println(ClassLayout.parseInstance(a).toPrintableSimpleSerMs());
}
输出如下:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 (biasable; age: 0)
00000000 00000000 00000001 11111011 01001010 10110100 01100000 00000101 (biased: 0x000000007ed2ad18; epoch: 0; age: 0)
00000000 00000000 00000001 11111011 01001010 10110100 01100000 00000101 (biased: 0x000000007ed2ad18; epoch: 0; age: 0)
方法二
上述说到我写了两个方法,一个是打印得比较全得,一个是简约的,这里做个对比
public static void test2() throws NoSuchFieldException, IllegalAccessException, InterruptedException {
A a = new A();
out.println("befor hash");
//没有计算HASHCODE之前的对象头
out.println(ClassLayout.parseInstance(a).toPrintableSimpleSimplicity());
out.println(ClassLayout.parseInstance(a).toPrintableSimpleSerMs());
//JVM 计算的hashcode
out.println("jvm‐‐‐‐‐‐‐‐‐‐‐‐0x" + Integer.toHexString(a.hashCode()));
//当计算完hashcode之后,我们可以查看对象头的信息变化
out.println("after hash");
out.println(ClassLayout.parseInstance(a).toPrintableSimpleSimplicity());
out.println(ClassLayout.parseInstance(a).toPrintableSimpleSerMs());
synchronized (a) {
out.println("对象a 已加锁 ---------");
out.println(ClassLayout.parseInstance(a).toPrintableSimpleSimplicity());
out.println(ClassLayout.parseInstance(a).toPrintableSimpleSerMs());
}
out.println("对象a 解锁 ---------");
out.println(ClassLayout.parseInstance(a).toPrintableSimpleSimplicity());
out.println(ClassLayout.parseInstance(a).toPrintableSimpleSerMs());
}
打印结果:
toPrintableSimpleSimplicity()方法打印在上
toPrintableSimpleSerMs()方法打印在下
befor hash
Mark Word Simplicity (binary):
101
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 (biasable; age: 0)
jvm‐‐‐‐‐‐‐‐‐‐‐‐0x573f2bb1
after hash
Mark Word Simplicity (binary):
101011100111111001010111011000100000001
00000000 00000000 00000000 01010111 00111111 00101011 10110001 00000001 (hash: 0x573f2bb1; age: 0)
对象a 已加锁 ---------
Mark Word Simplicity (binary):
1101110100101111111111111001000111000
00000000 00000000 00000000 00011011 10100101 11111111 11110010 00111000 (thin lock: 0x0000001ba5fff238)
对象a 解锁 ---------
Mark Word Simplicity (binary):
101011100111111001010111011000100000001
00000000 00000000 00000000 01010111 00111111 00101011 10110001 00000001 (hash: 0x573f2bb1; age: 0)
Process finished with exit code 0
导出Jar包
Maven直接Install然后引入到项目中即可
- 感谢你赐予我前进的力量