拥有您的堆:使用JVMTI迭代类实例

今天,我想谈一谈我们大多数人每天都不会看到和使用的另一种Java,更确切地说,是有关较低级别的绑定,一些本机代码以及如何执行一些小的魔术。 尽管我们不会在JVM上找到真正的魔力源,但是在单个帖子的范围内可以实现一些小奇迹。

我花了整整一天的时间在ZeroTurnaround的RebelLabs团队进行研究,编写和编码,该公司为Java开发人员创建工具,这些工具主要以javaagents的身份运行。 通常情况下,如果您想在不重写JVM的情况下增强JVM或在JVM上获得任何不错的功能,则必须深入研究Java代理的美丽世界。 这些有两种风格:Java javaagents和本机Javaagents。 在这篇文章中,我们将集中讨论后者。


注意, XRebel产品负责人Anton Arhipov的这个GeeCON Prague演示文稿是学习完全用Java编写的javaagents的一个很好的起点: 与Javassist一起玩 。

在本文中,我们将创建一个小型的本机JVM代理,探讨将本机方法公开到Java应用程序中的可能性,并了解如何利用Java虚拟机工具接口 。

如果您想从这篇文章中找到实用的方法,我们将能够在扰乱警报的情况下计算堆中存在给定类的实例数量。

想象一下,您是圣诞老人值得信赖的黑客精灵,而这位大人物对您来说面临以下挑战:

圣诞老人: 我亲爱的Hacker Elf,您能否编写一个程序来指出JVM堆中当前隐藏了多少个Thread对象?

另一个不愿意挑战自己的小精灵会回答: 这很容易直接,对吗?

return Thread.getAllStackTraces().size();

但是,如果我们要过度设计解决方案以能够回答有关任何给定类的问题,该怎么办? 说我们要实现以下接口?

public interface HeapInsight {int countInstances(Class klass);
}

是的,那是不可能的,对吧? 如果您将String.class作为参数接收怎么办? 不用担心,我们只需要更深入地研究JVM的内部结构。 JVMTI作者可以使用的一件事是JVMTI (Java虚拟机工具接口)。 它是很久以前添加的,许多看似神奇的工具都在使用它。 JVMTI提供两件事:

  • 本机API
  • 一种工具API,用于监视和转换装入JVM的类的字节码。

就我们的示例而言,我们需要访问本机API。 我们要使用的是IterateThroughHeap函数,该函数使我们可以提供一个自定义回调,以对给定类的每个对象执行该回调。

首先,让我们创建一个本地代理,该代理将加载和回显某些内容,以确保我们的基础架构能够正常工作。

本机代理是用C / C ++编写的东西,并编译成一个动态库,在我们甚至开始考虑Java之前就已经加载了。 如果您不精通C ++,请不要担心,没有很多精灵,也不会很难。 我使用C ++的方法包括2种主要策略:巧合编程和避免段错误。 因此,由于我设法编写了这篇文章的示例代码并对其进行了注释,因此,我们可以一起研究一下。 注意:以上段落应作为免责声明,请勿将此代码置于任何对您有价值的环境中。

这是创建第一个本机代理的方法:

#include 
#include using namespace std;JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved)
{cout << "A message from my SuperAgent!" << endl;return JNI_OK;
}

该声明的重要部分是声明一个名为Agent_OnLoad的函数,该函数遵循动态链接的代理的文档 。

将文件另存为例如native-agent.cpp ,让我们看看我们可以做些什么来变成一个库。

我在OSX上,因此我使用clang对其进行编译,以节省一些时间,下面是完整的命令:

clang -shared -undefined dynamic_lookup -o agent.so -I /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/include/ -I /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/include/darwin native-agent.cpp

这将创建一个agent.so文件,该文件是准备为我们服务的库。 为了测试它,让我们创建一个虚拟的hello world Java类。

package org.shelajev;
public class Main {public static void main(String[] args) {System.out.println("Hello World!");}
}

当你用正确的-agentpath选项指向agent.so运行它,你应该看到下面的输出:

java -agentpath:agent.so org.shelajev.Main
A message from my SuperAgent!
Hello World!

做得好! 现在,我们拥有一切使之真正有用的地方。 首先,我们需要一个jvmtiEnv实例,当我们位于Agent_OnLoad中时 ,可以通过JavaVM * jvm获得该实例 ,但以后将不可用。 因此,我们必须将其存储在可全局访问的位置。 我们通过声明一个全局结构来存储它。

#include 
#include using namespace std;typedef struct {jvmtiEnv *jvmti;
} GlobalAgentData;static GlobalAgentData *gdata;JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved)
{jvmtiEnv *jvmti = NULL;jvmtiCapabilities capa;jvmtiError error;// put a jvmtiEnv instance at jvmti.jint result = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1);if (result != JNI_OK) {printf("ERROR: Unable to access JVMTI!\n");}// add a capability to tag objects(void)memset(∩a, 0, sizeof(jvmtiCapabilities));capa.can_tag_objects = 1;error = (jvmti)->AddCapabilities(∩a);// store jvmti in a global datagdata = (GlobalAgentData*) malloc(sizeof(GlobalAgentData));gdata->jvmti = jvmti;return JNI_OK;
}

我们还更新了代码,以添加标记对象的功能,这是我们遍历堆所需的。 现在准备工作已经完成,我们已经初始化了JVMTI实例并且可供我们使用。 让我们通过JNI将其提供给我们的Java代码。

JNI代表Java本机接口 ,这是将本机代码调用包含到Java应用程序中的一种标准方式。 Java部分将非常简单,将以下countInstances方法定义添加到Main类:

package org.shelajev;public class Main {public static void main(String[] args) {System.out.println("Hello World!");int a = countInstances(Thread.class);System.out.println("There are " + a + " instances of " + Thread.class);}private static native int countInstances(Class klass);
}

为了适应本机方法,我们必须更改本机代理代码。 我将在稍后解释,但现在在其中添加以下函数定义:

extern "C"
JNICALL jint objectCountingCallback(jlong class_tag, jlong size, jlong* tag_ptr, jint length, void* user_data) 
{int* count = (int*) user_data;*count += 1; return JVMTI_VISIT_OBJECTS;
}extern "C"
JNIEXPORT jint JNICALL Java_org_shelajev_Main_countInstances(JNIEnv *env, jclass thisClass, jclass klass) 
{int count = 0;jvmtiHeapCallbacks callbacks;
(void)memset(&callbacks, 0, sizeof(callbacks));
callbacks.heap_iteration_callback = &objectCountingCallback;jvmtiError error = gdata->jvmti->IterateThroughHeap(0, klass, &callbacks, &count);return count;
}

Java_org_shelajev_Main_countInstances在这里更有趣,它的名称遵循约定,以Java_开头,然后是_分隔的完全限定的类名,然后是Java代码中的方法名。 另外,请不要忘记JNIEXPORT声明,该声明指出该函数已导出到Java世界中。

Java_org_shelajev_Main_countInstances内部,我们将objectCountingCallback函数指定为回调,并使用Java应用程序中的参数调用IterateThroughHeap

请注意,我们的本机方法是静态的,因此C副本中的参数为:

JNIEnv *env, jclass thisClass, jclass klass

对于实例方法,它们将有所不同:

JNIEnv *env, jobj thisInstance, jclass klass

这里的thisInstance指向Java方法调用的this对象。

现在, objectCountingCallback的定义直接来自文档 。 身体无非就是增加一个int。

繁荣! 全做完了! 感谢您的耐心等待。 如果您仍在阅读本文,则可以测试上面的所有代码。

再次编译本机代理并运行Main类。 这是我看到的:

java -agentpath:agent.so org.shelajev.Main
Hello World!
There are 7 instances of class java.lang.Thread

如果我添加一个线程t = new Thread(); 行到main方法,我在堆上看到8个实例。 听起来好像真的可行。 您的线程数几乎肯定会有所不同,不用担心,这是正常现象,因为它确实计入了JVM簿记线程,进行编译,GC等操作。

现在,如果我想计算堆上String实例的数量,只需更改参数类即可。 我希望圣诞老人是一个真正通用的解决方案。

哦,如果您有兴趣,它会为我找到2423个String实例。 对于小型应用程序来说,这个数字相当高。 也,

return Thread.getAllStackTraces().size();

给我5个而不是8个,因为它不包括簿记线程! 谈论琐碎的解决方案,是吗?

现在,您已经掌握了这些知识,并且知道了本教程,并不是说您已经准备好编写自己的JVM监视或增强工具,但这绝对是一个开始。

在本文中,我们从零开始编写了本机Java代理,该代理成功编译,加载和运行。 它使用JVMTI来获取对JVM的了解,否则无法访问。 相应的Java代码调用本机库并解释结果。

这通常是最神奇的JVM工具所采用的方法,我希望其中的一些魔术已为您揭开神秘面纱。

翻译自: https://www.javacodegeeks.com/2014/12/own-your-heap-iterate-class-instances-with-jvmti.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/360985.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Dojo高级Web2.0 UI组件库---Tree组件

Tree组件可以把有层次关系的数据用树状结构展现出来&#xff0c;就如同 Windows 系统的资源浏览器。Tree有两个模板&#xff0c;一个是Tree模板&#xff0c;一个是TreeNode模板&#xff0c;应该说Tree模板就是一个容器&#xff0c;里面有很多TreeNode.而初始化树&#xff0c;打…

使用Java 8防止日志过宽

一些日志将被机器消耗并永久保存。 其他日志仅用于调试和供人类使用。 在后一种情况下&#xff0c;您通常要确保您不会产生过多的日志&#xff0c;尤其是不会产生太宽的日志&#xff0c;因为一旦行长超过一定大小&#xff08; 例如&#xff0c;此Eclipse bug &#xff09;&…

[one day one question] webpack打包压缩 ES6 js、.vue报错

问题描述&#xff1a; 报错&#xff1a; ERROR in js/test.js from UglifyJs Unexpected token punc ?(?, expected punc ?:? [js/test.js:1374,5]&#xff0c;这怎么破&#xff1f; 解决方案&#xff1a; 配置babel&#xff0c;把配置放到文件【.babelrc】中{"prese…

UVA10236 斐波那契素数

题意&#xff1a;任取斐波那契数列中一项f[i],若对于所有j 解法&#xff1a;这题的理论分析在黑书上有&#xff0c;结论是从第五项开始下标为素数的斐波那契数都是斐波那契素数 #include <stdio.h> #include <string.h> const int MAXN 250010;; int prime[25010]…

[one day one question] safari缓存太厉害

问题描述&#xff1a; safari缓存太厉害&#xff0c;这怎么破&#xff1f; 解决方案&#xff1a; window.onpageshow function(event) {if (event.persisted) {window.location.reload() }}; 君生我未生&#xff0c;我生君已老 君恨我生迟&#xff0c;我恨君生早 君生我未生&…

Android 网络编程之Http通信

Android中提供的HttpURLConnection和HttpClient接口可以用来开发HTTP程序。以下是本人在学习中的总结与归纳。1. HttpURLConnection接口 首先需要明确的是&#xff0c;Http通信中的POST和GET请求方式的不同。GET可以获得静态页面&#xff0c;也可以把参数放在URL字符串后面&…

EE Servlet 3:在Servlet中生成HTML输出

如果您只需要在EE Web模块中处理少量请求URI&#xff0c;则在Servlet代码中生成自己HTML响应可能比使用完整的模板库更容易。 作为示例的一部分&#xff0c;我尝试了一个非常简单的Java DSL&#xff0c;该Java DSL在编写自己的Serlvet时生成html输出。 代码如下&#xff1a; p…

[one day one question] express 不缓存如何实现

问题描述&#xff1a; express 默认缓存&#xff0c;这怎么破&#xff1f; 解决方案&#xff1a; apiRoutes.use(function (req, res, next) {res.setHeader(Cache-Control, no-cache, no-store, must-revalidate);res.setHeader(Pragma, no-cache);res.setHeader(Expires, 0)…

正则表达式摘录

正则表达式到底是什么东西&#xff1f;在编写处理字符串的程序或网页时&#xff0c;经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说&#xff0c;正则表达式就是记录文本规则的代码。常用元字符 代码说明.匹配除换行符以外的任意…

Java生态系统– 2014年我的5大亮点

1. 2月1日-RedMonk Analyst公司宣布Java比以往任何时候都更加流行和多样化&#xff01; 2014年&#xff0c;随着FOSDEM的Free Java会议室的年会&#xff0c;Java生态系统开始轰轰烈烈地响起。 除了关于OpenJDK和相关主题的许多精深的技术讲座外&#xff0c;Steve OGrady&#x…

WEB前端大神之路之基础篇

CSS篇&#xff1a; 1.CSS权重&#xff1a; 不重复造轮子啦&#xff0c;直接传送门&#xff08;CSS选择器的权重与优先规则&#xff09; JavaScript篇&#xff1a; 1.this关键字&#xff1a; 它是一种引用&#xff08;referent&#xff09;。指向的是当前上下文&#xff08;con…

与Java EE和Camel的轻量级集成

Enterprise Java具有不同的风格和观点。 从简单的平台技术开始&#xff0c;即众所周知的Java EE&#xff0c;再到不同的框架和集成方面&#xff0c;最后是涉及以数据为中心的用户界面或特定可视化效果的用例。 Java EE本身无法解决的最突出的问题是“集成”。 有许多来自知名供…

java=====Striing date 转化

java中string与date(日期)格式之间的转换 经常遇到string和date之间的转换&#xff0c;把相关的内容总结在这里吧&#xff1a; 1.string格式转化为Date对象&#xff1a; //把string转化为dateDateFormat fmt new SimpleDateFormat("yyyy-MM-dd"); Date date fmt.par…

oracel Pipelined pipe row的用法

oracle的管道也可以返回集合类型&#xff0c;跟游标类似 CREATE TYPE my_type AS OBJECT ( field1 NUMBER, field2 VARCHAR2 (50) ); CREATE TYPE my_typelist AS TABLE OF my_type; CREATE OR REPLACE FUNCTION pipelineme RETURN my_typelist PIPELINED IS -…

在Spring中使用Netflix Hystrix批注

除了在主页上引述之外&#xff0c;我想不出更好的方式来描述Netflix Hystrix库的特定功能&#xff1a; 延迟和容错方式&#xff1a; 停止级联故障。 后备和正常降级。 无法快速快速恢复。 使用断路器隔离线程和信号量。 我看到了Josh Long&#xff08; starbuxman &#xff0…

[工具库]JFileDownloader工具类——多线程下载网络文件,并保存在本地

本人大四即将毕业的准程序员&#xff08;JavaSE、JavaEE、android等&#xff09;一枚&#xff0c;小项目也做过一点&#xff0c;于是乎一时兴起就写了一些工具。 我会在本博客中陆续发布一些平时可能会用到的工具。 代码质量可能不是很好&#xff0c;大家多担待&#xff01; 代…

使用JAX-RS和Spring构建HATEOAS API

在我以前的博客文章中&#xff0c;我展示了如何使用Spring Boot配置Jersey多么容易。 我对Spring Boot和Jersey的探索并没有结束&#xff0c;我研究了在Spring Boot应用程序中将Spring HATEOAS和Jersey一起使用的可能性。 Spring HATEOS允许创建遵循HATEOAS原理的REST表示形式&…

企业中的微服务:敌是友?

宏观问题的微观解决方法&#xff1f; 微服务的炒作无处不在&#xff0c;尽管业界似乎无法就确切的定义达成共识&#xff0c;但我们一再被告知&#xff0c;从单一应用程序转向由小型服务组成的面向服务的架构&#xff08;SOA&#xff09;是正确的方法。构建和发展软件系统。 但是…

自定义百度地图气泡

自定义百度地图气泡 http://www.cnblogs.com/jz1108/archive/2011/09/15/2152122.html 转载于:https://www.cnblogs.com/ygm900/archive/2013/02/26/2933000.html

使用Bean验证扩展PrimeFaces CSV

你们中有些人已经知道我和我的合著者Mertalışkan正在研究PrimeFaces Cookbook的2.版。 Packt Publishing允许我从新章节“客户端验证”的一个食谱中摘录一小部分摘录。 这将有助于使读者知道这本书的内容。 在此博客文章中&#xff0c;我想讨论使用Bean验证扩展的PrimeFaces客…