- 从进程角度解释JVM,虚拟机是运行在操作系统中的,而操作系统的执行单位是进程。所以可以理解为:当JVM运行的时候,它是操作系统中的进程实例。当其没有运行的时候,作为可执行文件放在文件系统中,可以称之为程序。
—— [ 摘自 ]https://blog.csdn.net/zhangjg_blog/article/details/20380971
以HelloWorld为例:
public class HelloWorld{
public static void main(String args[]){
System.out.println("hello world!");
}
}
第一步:编译
- javac HelloWorld.java
- 结果生成两个文件:
HelloWorld.class
HelloWorld.java
第二步:执行
- java HelloWorld
- 之所以不直接运行helloWorld.class,是因为class文件并不能被操所系统识别为可执行文件。首先敲入java命令,说明最先启动的是一个叫做java的程序:而这个运行起来的java程序,其实就是一个java虚拟机。java虚拟机读取参数HelloWorld。把它当做初始类加载到内存,对这个类进行初始化和动态链接,然后从类的main方法开始执行。
- 也就是说:.class文件并非直接被系统加载后在CPU上运行,而是被java虚拟机这个进程托管运行。
—— [ 摘自 ]https://blog.csdn.net/zhangjg_blog/article/details/20380971
- 开发人员需要对Java程序进行诊断和调试,比如掌握程序运行状况,提高程序执行效率,分析程序出现的问题。而就如前面所说,java程序是托管运行在java虚拟机上的,所以要做到这些,就需要和虚拟机打交道。利用虚拟机提供的本地代码接口(Java Virtual Machine Tool Interface)来获取并且返回当前虚拟机的状态,或者转发控制命令。
—— [ 摘自 ]https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/
- agent就是JVMTI客户端。
agent是基于JVMTI开发的,以c/c++语言编写的动态共享库的形式存在。当JAVA启动或者运行的时候,JVM会动态加载一个外部的agent,然后触发JVM源生线程Attach Listener执行agent中的回调函数。在函数体内,可以获取各种各样的VM信息,注册感兴趣的VM时间,控制VM的行为。
——[ 摘自 ] https://www.jianshu.com/p/df17a9b0c45c
tips:两个软件的位数必须和系统保持一致!!
JDK就在ORACLE官网下载即可,安装后记得将路径添加到环境变量。
由于eclipse的安装很简单,所以这里主要说明怎么安装CodeBlocks及相应配置。
由于CodeBlocks只是一个集成开发环境,本身并不提供编译功能,所以需要自己安装第三方编译代码。官方的集成编译器为mingw。如果系统是32位的可以直接安装mingw,但是由于我的系统和eclipse都是64位的,所以不安装mingw,而是安装mingw-w64。
安装mingw-w64
- 非常重要!!!
CodeBlocks默认编译器mingw编译出来的动态链接库dll是32位的,无法被64位的java程序调用!所以一定要安装mingw-w64mingw-w64下载地址 : https://sourceforge.net/projects/mingw-w64/
点击安装,在如下界面中architechture根据自己的系统选择x86_64。
配置CodeBlocks
- 打开CodeBlocks,点击顶部菜单栏中的settings——>compiler——>ToolChain executables
- 在如下界面中,编辑Compiler’s installation directory(位置选择:mingw-w64的安装路径\bin)
- 依次将C compiler ,C++ compiler,Linker for dynamic libs,Linker for static libs,Resource compiler,Make program依次改成“mingw-w64的安装路径\bin”中对应的程序,如图所示,然后选择OK保存更改。
- 再次选择settings——>Debugger
- 在GDB/CDB Debugger中点击Create Config
- 在所弹出的框中填写gdb64,然后选择OK,在接下来的界面中,编辑gdb64,将Executable path改成之前的“mingw-w64的安装路径\bin”,然后选择OK
- 继续点击顶部菜单栏中的settings——>compiler——>ToolChain executables,将Debugger设置为刚刚编辑好的gbd64,如下图所示。
在这篇博客中,实现的agent功能是:输出当前JVM中所有加载任意指定类的个数。
- 新建java工程:打开Eclipse ——> File ——> New ——> Java Project
- 在Package Explorer点击刚刚创建Java Project,并且在src选项下新建class
- 假设创建的工程名是myAgent,包名是com.agent,类名是MyAgent,功能是输出Thread.class数目
/*这一部分是Java代码*/
package com.agent;
public class MyAgent{
public static void main(String[] args){
int a = countInstances(Thread.class);
System.out.println(a + " instances");
}
/*
*申明native方法countInstances()
*使用native关键字代表countInstances()是原生函数,这个方法是用C/C++实现的,并且被编译成了DLL供Java调用,不必在Java中声明函数体。
*/
private native static int countInstances(Class class);
static{
System.loadLibrary("MyAgent");
}
}
- 打开windows cmd
- 编译Java源文件:javac MyAgent.java
(这一步最好在MyAgent.java所在路径下执行,否则会找不到MyAgent类文件)- 生成.h文件:javah -jni com.agent.MyAgent
(这一步在src文件路径下执行,否则会找不到类文件,同时,不要在MyAgent.java所在路径下直接执行javah -jni MyAgent,也会找不到类文件,这是因为Java文件中有包名,具体原因可以google)- 可以看见在myAgent工程文件夹下生成了一个com_agent_MyAgent的.h文件(就是你Java工程所在的文件夹中:myAgent/src),在codeBlocks中打开这个文件,可以看到如下内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_agent_MyAgent */
#ifndef _Included_com_agent_MyAgent
#define _Included_com_agent_MyAgent
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_agent_MyAgent
* Method: countInstances
* Signature: (Ljava/lang/Class;)I
*/
JNIEXPORT jint JNICALL Java_com_agent_MyAgent_countInstances
(JNIEnv *, jclass, jclass);
#ifdef __cplusplus
}
#endif
#endif
- 打开CodeBlocks,选择File ——> Projects ——> Dynamic Link Library,然后依据步骤填好工程名(我的工程名是agentTest)
- 在左边的management框中,可以看到新建的工程,将Sources和Headers中的main.c以及head.h都删除,然后在Sources中新建一个myAgent.c的文件,并在Headers中打开之前生成的com_agent_MyAgent.h
- 在Sources中,实现agent以及com_agent_MyAgent.h
- 重要!!!
记得在CodeBlocks菜单栏中选择Project ——> build options ——> Search directories,选择add添加jvmti.h和jni_md.h以及com_agent_MyAgent.h的搜索路径
jvmti.h在安装的JDK路径\include里
jni_md.h在安装的JDK路径\include\win32里
com_agent_MyAgent.h在Java工程里
(注:我在添加jni_md.h以及com_agent_MyAgent.h的时候,即使添加了搜索路径也依然搜索不到这些头文件。如果jni_md.h无法搜索到,解决方案是将jni_md.h拷贝到jvmti.h所在的文件夹中;如果com_agent_MyAgent.h无法搜索到,解决方案是将com_agent_MyAgent.h添加到agentTest工程的文件夹中)
/*agentTest.c文件*/
#include
#include
#include
#include
#include
#include
/**< 此处定义结构体GobalAgentData用于保存jvmtiEnv指针 */
typedef struct{
jvmtiEnv * jvmti;
}GlobalAgentData;
static GlobalAgentData * gdata;
/** JNIEXPORT JNICALL均定义在jni_md.h中,是用于标识函数用途的两个宏
* 在jni_md.h中,
* #define JNIEXPORT _declspec(dllexport)
* #define JNICALL _stdcall
* JNIEXPORT:如果动态库中的函数要被外部调用,需要在函数声明中添加_declspec(dllexport)标识
* JNICALL : 用于约束函数入栈顺序和栈堆清理的规则
*/
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char * options, void *reserved){
jvmtiEnv * jvmti = NULL;
jvmtiCapabilities capa;
jvmtiError error;
jint result = (*jvm)->GetEnv(jvm,(void**)&jvmti, JVMTI_VERSION_1_1);
if(result != JNI_OK){
printf("ERROR : Unable to access JVMTI! \n");
}
(void)memset(&capa,0,sizeof(jvmtiCapabilities));
/**< can_tag_objects : can set and get tags, as described in the Heap category
* 通过将capa_can_tag_objects设置为1,代表enable this capability
*/
capa.can_tag_objects = 1;
/** \jvmtiError AddCapabilities(jvmtiEnv* env, const jvmtiCapabilities* capabilities_ptr)
*
* 设置新的capabilities,之前的capabilities也会被保留
*
*/
error = (*jvmti)->AddCapabilities(jvmti,&capa);
/**< store jvmtiEnv in a global data */
gdata = (GlobalAgentData*)malloc(sizeof(GlobalAgentData));
gdata->jvmti = jvmti;
return JNI_OK;
}
/*< JVMTI_VISIT_OBJECTS是Heap Visit Control Flag,由heap回调函数返回
* 如果访问object并且FllowReferences已经初始化callback函数,并且遍历了object的引用
*
* class_tag : 访问的object的class的tag(之前提到过:tag是用来唯一标识一个object的value)
* size : object的size
* tag_ptr : object的tag是存放在别的地方,object经过tag_ptr得到这个tag
* length : 如果object是个array,那么length就代表这个array的长度,否则均为-1
* user_date : the user supplied data that was passed into the iteration function
*/
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;
}
/**< 函数的命名规则:Java_packagename_classname_methodname */
JNIEXPORT jint JNICALL Java_com_agent_MyAgent_countInstances(JNIEnv * env, jclass thisClass, jclass klass){
int count = 0;
/**< jvmtiHeapCallbacks : heap callback function structure */
jvmtiHeapCallbacks callbacks;
(void)memset(&callbacks, 0, sizeof(callbacks));
/**< heap_iteration_callback是用于描述heap中object的回调函数,used by the iterateThroughHeap function
* 这一步如果看不懂,可以去了解一下函数指针的概念
*/
callbacks.heap_iteration_callback = &objectCountingCallback;
/**< jvmtiError IterateThroughHeap(jvmtiEnv* env, jint heap_filter, jclass klass, const jvmtiHeapCallbacks* callbacks, const void* user_data)
* initiate an iteration over all objects in the heap, including both reachable and unreachable objects
*/
jvmtiError error = (*(gdata->jvmti))->IterateThroughHeap(gdata->jvmti,0, klass, &callbacks, &count);
return count;
}
- 首先,修改刚刚的Java源文件,删除static这一部分
static{
System.loadLibrary("MyAgent");
}
- 然后打开windows cmd
- 重新编译java源文件:javac MyAgent.java
- 调用动态链接库:java -agentpath:agentTest动态链接库路径\agentTest.dll com.agent.MyAgent
(current directory是java project path/src,如果current directory是java project path/src/com/agent,并且执行java -agentpath:agentTest动态链接库路径\agentTest.dll MyAgent,会出现NoClassDefFoundError)
如果一言不合就出现找不到类文件错误,十有八九是执行路径有问题。如果要保留java源文件中包名package com.agent,这个问题也没办法,只能注意执行路径,还有什么时候用com.agent.MyAgent.java,什么时候直接用MyAgent.java。如果嫌太麻烦,可以直接删除java源文件中package com.agent这一行,这样编译也不会出错。