网站公告列表

  没有公告

加入收藏
设为首页
联系站长
您现在的位置: 网络学院 >> 程序设计 >> Java编程 >> 文章正文
  类装入问题解密,第 1 部分: 类装入和调试工具介绍            【字体:
类装入问题解密,第 1 部分: 类装入和调试工具介绍
作者:佚名    文章来源:不详    点击数:    更新时间:2007-7-28    
类装入组件是 Java™ 虚拟机的基础。
正在装载数据……
虽然开发人员一般对类装入的基础有良好的掌握,但是当问题发生时,在诊断问题和确定解决方案方面可能还要有一定的困难。

类装入的阶段

类的装入实际上可以分成三个阶段:装入、链接和初始化。

虽然不是所有的问题,但至少大多数与类装入有关的问题都可以追溯到在这三个阶段中发生的某个问题。所以,对于每一阶段的深入理解有助于对类装入问题的诊断。图 2 显示了这三个阶段:


图 2. 类装入的阶段
类装入问题解密,第 1 部分: 类装入和调试工具介绍(图二)

装入 阶段包括:找到必要的类(通过查找每个类路径)并装入字节码。在 JVM 中,装入阶段为类对象提供了非常基本的内存结构。在这一阶段不处理方法、字段和引用的其他类。所以,类还不能使用。

链接 是三个阶段中最复杂的一个。可以把它分成三个主要阶段:

  • 字节码验证。 类装入器对于类的字节码要做许多检测,以确保格式正确、行为正确。
  • 类准备。 这个阶段准备代表每个类中定义的字段、方法和实现接口所必需的数据结构。
  • 解析。 在这个阶段,类装入器装入类所引用的其他所有类。可以用许多方式引用类:
    • 超类
    • 接口
    • 字段
    • 方法签名
    • 方法中使用的本地变量

初始化 阶段,类中包含的静态初始化器都被执行。在这一阶段末尾,静态字段被初始化成默认值。

在这三个阶段末尾,类被完整地装入,可以使用了。请注意可以用惰性方式执行类装入,所以类装入过程的某些部分可能在第一次使用类的时候才执行,而不是在装入时执行。

显式装入与隐式装入

类装入的方式有两种 —— 显式隐式,两者之间有些细微差异。

显式 类装入发生在使用以下方法调用装入的类的时候:

  • cl.loadClass()cljava.lang.ClassLoader 的实例)
  • Class.forName()(启动的类装入器是当前类定义的类装入器)

当调用其中一个方法的时候,指定的类(以类名为参数)由类装入器装入。如果类已经装入,那么只是返回一个引用;否则,装入器会通过委托模型装入类。

隐式 类装入发生在由于引用、实例化或继承导致装入类的时候(不是通过显式方法调用)。在每种情况下,装入都是在幕后启动的,JVM 会解析必要的引用并装入类。与显式类装入一样,如果类已经装入了,那么只是返回一个引用;否则,装入器会通过委托模型装入类。

类的装入通常组合了显式和隐式类装入。例如,类装入器可能先显式地装入一个类,然后再隐式地装入它引用的所有类。


类装入问题解密,第 1 部分: 类装入和调试工具介绍(图三)
类装入问题解密,第 1 部分: 类装入和调试工具介绍(图四)
回页首


JVM 的调试特性

前面一节介绍了类装入的基本原则。这一节介绍 IBM JVM 中内置的帮助调试的特性。其他 JVM 也有类似的调试特性;请参阅相关文档来了解细节。

详细输出

可以用 -verbose 命令行选项打开 IBM JVM 的详细输出。当某些事件发生的时候(例如,类装入时),详细输出会在控制台上显示信息。要想得到额外的类装入信息,可以用详细类输出。可以用 -verbose:class 选项启动这个模式。

解释详细输出
详细输出列出已经打开的所有 JAR 文件,包括到这些 JAR 的完整路径。下面是一个示例:

...[Opened D:\jre\lib\core.jar in 10 ms][Opened D:\jre\lib\graphics.jar in 10 ms]...

所有装入的类都已经列出,同时还指出它们是从哪个 JAR 文件或目录装入的。例如:

...[Loaded java.lang.NoClassDefFoundError from D:\jre\lib\core.jar][Loaded java.lang.Class from D:\jre\lib\core.jar][Loaded java.lang.Object from D:\jre\lib\core.jar]...

详细类输出显示额外信息,例如在装入超类的时候,在运行静态初始化器的时候。下面是一些示例输出:

...[Loaded HelloWorld from file:/C:/myclasses/][Loading superclass and interfaces of HelloWorld][Loaded HelloWorldInterface from file:/C:/myclasses/][Loading superclass and interfaces of HelloWorldInterface][Preparing HelloWorldInterface][Preparing HelloWorld][Initializing HelloWorld][Running static initializer for HelloWorld]...

详细输出还显示一些内部抛出的异常(如果发生的话),包含堆栈跟踪。

用 -verbose 解决问题
详细输出有助于解决类路径问题,例如没有打开 JAR 文件(因此不在类路径中)或从错误的位置装入了类。

IBM 详细类装入

知道类装入器在哪里寻找类、特定的类是由哪个类装入器装入的,通常很有用。可以用 IBM 详细类装入命令行选项得到这个信息:-Dibm.cl.verbose=<class name>。可以用正则表达式声明类的名称,例如 Hello* 会跟踪所有以 Hello 开头的名称。

这个选项也可用于用户定义的类装入器,只要它们直接或间接地扩展了 java.net.URLClassLoader

解释 IBM 详细类装入的输出
IBM 详细类装入的输出显示了要装入指定类的类装入器以及它们查找的位置。例如,假设用以下命令行:

java -Dibm.cl.verbose=ClassToTrace MainClass

在这里,MainClass 在它的主方法中引用了 ClassToTrace。这会形成像 这里 一样的输出。

在列出类装入器的时候,父类在子女之前列出,因为标准的委托模型的工作方式是父类优先。

请注意,引导类装入器没有输出。只有扩展了 java.net.URLClassLoader 的类装入器才有输出。还请注意,类装入器列在它们的类名之下;如果类装入器有两个实例,那么可能无法区分它们。

用 IBM 详细类装入解决问题
IBM 详细类装入选项是检查所有类装入器设置的类路径的好方法。它还可以指出指定类是由哪个类装入器装入的、从哪里装入的。这样就可以容易地看出是否装入了类的正确版本。

Javadump

Javadump(也称为 Javacore)是另一个很有用的 IBM 诊断工具;要了解它,请参阅 IBM Diagnostics Guides(请参阅 参考资料 中的链接)。当发生以下事件时,JVM 会生成 Javadump:

  • 发生致命的本机异常
  • JVM 用光了堆空间
  • 向 JVM 发送了一个信号(例如,在 Windows 上按下了 Control-Break 或在 Linux 上按下了 Control-\)
  • 调用了 com.ibm.jvm.Dump.JavaDump() 方法

触发 Javadump 的时候,会把详细信息记录到在当前工作目录下保存的一个有日期戳的文本文件中。信息包括线程、锁、堆栈等方面的数据,以及关于系统中类装入器的丰富信息。

解释 Javadump 中的类装入部分
Javadump 文件中提供的确切信息取决于 JVM 在哪个平台上运行。类装入器部分包括:

  • 定义的类装入器和它们之间的关系
  • 每个类装入器装入的类的列表

以下是从 Javadump 提取的类装入器信息的快照:

CL subcomponent dump routine============================ Classpath Z(D:\jre\lib\core.jar),... Oldjava mode false Bootstrapping false Verbose class dependencies false Class verification VERIFY_REMOTE Namespace to classloader 0x00000000 Start of cache entry pool 0x44D85430 Start of free cache entries 0x44D86204 Location of method table 0x44C23AA0 Global namespace anchor 0x00266894 System classloader shadow 0x00376068 Classloader shadows 0x44D7BA60 Extension loader 0x00ADB830 System classloader 0x00ADB7B0 Classloader summaries 12345678: 1=primordial,2=extension,3=shareable,4=middleware, 5=system,6=trusted,7=application,8=delegating -----ta- Loader sun/misc/Launcher$AppClassLoader(0x44D7BA60), Shadow 0x00ADB7B0, Parent sun/misc/Launcher$ExtClassLoader(0x00ADB830) Number of loaded classes 1 Number of cached classes 260 Allocation used for loaded classes 1 Package owner 0x00ADB7B0 -xh-st-- Loader sun/misc/Launcher$ExtClassLoader(0x44D71288), Shadow 0x00ADB830, Parent *none*(0x00000000) Number of loaded classes 0 Number of cached classes 0 Allocation used for loaded classes 3 Package owner 0x00ADB830 p-h-st-- Loader *System*(0x00376068), Shadow 0x00000000 Number of loaded classes 304 Number of cached classes 304 Allocation used for loaded classes 3 Package owner 0x00000000 ClassLoader loaded classes Loader sun/misc/Launcher$AppClassLoader(0x44D7BA60) HelloWorld(0x00ACF0E0) Loader sun/misc/Launcher$ExtClassLoader(0x44D71288) Loader *System*(0x00376068) java/io/WinNTFileSystem(0x002CD118) java/lang/Throwable(0x002C03A8) java/lang/IndexOutOfBoundsException(0x44D45208) java/lang/UnsatisfiedLinkError(0x44D42D38) ....................classes left out to save space........................ [Ljava/lang/Class;(0x002CA9E8) java/io/InputStream(0x002C9818) java/lang/Integer$1(0x002C83E8) java/util/Dictionary(0x002C4298)

在这个示例中,只有三个标准类装入器:

  • 系统类装入器(sun/misc/Launcher$AppClassLoader
  • 扩展类装入器(sun/misc/Launcher$ExtClassLoader
  • 引导类装入器(*System*

Classloader 汇总部分提供了系统中每个类装入器的细节。在这个系列的文章中,感兴趣的类型是基本、扩展、系统、应用程序委托(用在反射中)。其他类型(共享的、中间件信任的)用在 Persistent Reusable JVM 中,它们超出了这个文章系列的范围(请参阅 Persistent Reusable JVM User Guide 以获得更多信息;在下面的 参考资料 一节中有一个链接)。这个汇总部分还显示了父类类装入器:系统类装入器的父类是 sun/misc/Launcher$ExtClass loader(0x00ADB830)。这个父类地址对应于父类类装入器的原始数据结构(叫作 shadow)。

类装入器装入的类部分列出了每个类装入器装入的类。在这个示例中,系统类装入器只装入了一个类 HelloWorld(在地址 0x00ACF0E0 上)。

用 Javadump 解决问题
使用 Javadump 提供的信息,可以确定系统中存在哪些类装入器。这包括任何用户自定义的类装入器。从装入的类列表中,可以找出特定的类是由哪个类装入器装入的。如果找不到某个类,说明系统中的任何一个类装入器都没有装入它(通常会形成 ClassNotFoundException 异常)。

可以用 Javadump 诊断的其他类型的问题包括:

  • 类装入器命名空间问题。 类装入器的命名空间是类装入器和它装入的所有类的组合。例如,如果某个类存在,但是由错误的类装入器装入(有时会造成 NoClassDefFoundError 异常),那么命名空间就是错误的 —— 也就是说,类在错误的类路径中。为了纠正这种问题,可以试着把类放到不同的位置(例如放在正常的 Java 类路径中),并确保由系统类装入器装入它。

  • 类装入器约束问题。 在这个系列的最后一篇文章中将讨论这种问题的一个示例。

Java 方法跟踪

IBM JVM 有一个内置的方法跟踪工具。这样,不需要修改 Java 代码,就可以跟踪任何 Java 代码(包括核心系统)中的方法。因为这个工具可以提供大量数据,所以可以控制跟踪的级别,只获取需要的信息。

启动跟踪的选项取决于 JVM 的发行版。关于这些选项的细节,请参阅 IBM Diagnostics Guides(请参阅 参考资料 中的链接)。

下面是一些命令行示例:

在 IBM Java 1.4.2 中运行 HelloWorld 时要跟踪所有 java.lang.ClassLoader

java -Dibm.dg.trc.methods=java/lang/ClassLoader.*() -Dibm.dg.trc.print=mt HelloWorld

跟踪 ClassLoader 中的 loadClass() 方法和 HelloWorld 中的方法,也在 IBM Java 1.4.2 中:

java -Dibm.dg.trc.methods=java/lang/ClassLoader.loadClass(),HelloWorld.*() -Dibm.dg.trc.print=mt HelloWorld

解释方法跟踪的输出
这里 是方法跟踪输出的一个示例(用前面一段的第二个命令行)。

跟踪的每一行都提供了比上面显示的更多的信息。我们来完整地看看上面的一行:

12:57:41.277 0x002A23C0 04000D > java/lang/ClassLoader.loadClass Bytecode method, This = 0x00D2B830, Signature: (Ljava/lang/String;Z)Ljava/lang/Class;

这个跟踪包括:

  • 12:57:41.277:方法进入或退出的时间戳。

  • 0x002A23C0:线程 ID。

  • 04000D:某些高级诊断使用的内部 JVM 跟踪点。

  • 余下的信息显示是进入(>)还是退出了(<)方法,后面跟着方法的细节。

用方法跟踪解决问题
可以用方法跟踪解决不同类型的问题,包括:

  • 性能热点: 使用时间戳,可以发现需要花费相当多时间来执行的方法。
  • 挂起: 最后的方法项通常是很好的线索,可以指明应用程序是否挂起。
  • 错误对象: 使用地址,通过与对象的构建函数调用的地址进行比对,可以检查出是不是在正确的对象上调用方法 。
  • 意外的代码路径: 通过跟踪进入和退出点,可以看出程序是否采用了意外的代码路径。
  • 其他错误: 最后的方法项是对错误发生位置的良好提示。
站内文章搜索 高级搜索
文章录入:admin    责任编辑:admin 
  • 上一篇文章:

  • 下一篇文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
    最新热点 最新推荐 相关文章
     用java实现web服务器
     用java快速开发linux gu…
     正则表达式分解siemens交…
     [portal参考手册]目录
     jsp中调用oracle存储过程…
  • [ZT]应聘Java笔试时可能出现…

  • 面试问题回答!^-^

  • [原创]使用RemObjects Hydra…

  • JAVA加密解密:自定义类加载…

  • JSP页面不能刷新、防止刷新的…

  • web开发中的缓存问题的研究

  • Java路径问题最终解决方案—…

  • 面试题解答

  • 总结 重复提交、重复刷新、防…

  • Java单例对象同步问题探讨

  •   网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
    网络学院©2007 www.23book.net
    为您提供web编程,vb编程,vc编程,服务器架设管理,数据库设计等方面的知识 站长:David