后端技术JavaJava扩展第三方Jar包
SerMs前言
今天在B站大学学习并发编程的时候,老师引用了一个第三方的jar包(jol-core),maven坐标如下,来打印锁对象的Mark Word字节码,从而更直观察地多线程下加偏向锁的情况。
1 2 3 4 5
| <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中追加即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <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)、数组长度(如果对象是数组),字段信息以及空间损失等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
| 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";
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;
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、数组长度(如果对象是数组),字段信息以及空间损失等。
所以我们新增一个方法参考上述方法写就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| 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;
if (instance != null) { VirtualMachine vm = VM.current(); if (markSize == 8) { long mark = vm.getLong(instance, markOffset); markStr = Long.toBinaryString(mark); remind = parseMarkWord(mark); } else if (markSize == 4) { int mark = vm.getInt(instance, markOffset); markStr = Integer.toBinaryString(mark); remind = parseMarkWord(mark); } }
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 头中的最后三位,也就是锁和锁的状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
public String toPrintableSimpleSimplicity() { return toPrintableSimpleSimplicity(classData.instance()); }
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);
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
文件复制进去
测试代码
方法一
调用新增方法
1 2 3 4 5 6 7 8 9 10 11
| 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()); }
|
输出如下:
1 2 3
| 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)
|
方法二
上述说到我写了两个方法,一个是打印得比较全得,一个是简约的,这里做个对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static void test2() throws NoSuchFieldException, IllegalAccessException, InterruptedException { A a = new A(); out.println("befor hash"); out.println(ClassLayout.parseInstance(a).toPrintableSimpleSimplicity()); out.println(ClassLayout.parseInstance(a).toPrintableSimpleSerMs()); out.println("jvm‐‐‐‐‐‐‐‐‐‐‐‐0x" + Integer.toHexString(a.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()方法打印在下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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然后引入到项目中即可