- 浏览: 103563 次
- 性别:
- 来自: 长沙
文章分类
最新评论
-
mandrakeli:
不错,第一个问题解决了
hadoop错误汇总 -
jenew:
楼主 我要对比两个特征向量的相似度 该怎么做啊?谢谢
OpenCV——Mat、CvMat、IplImage类型浅析 -
luliangy:
哥哥,HDFS的GC好像是move到Trash目录下,然后三天 ...
突然发现的HDFS与GFS在实现上的一些不同 -
白粥若水:
ihopethatwell 写道楼主,如果Mat srcMat ...
OpenCV——Mat、CvMat、IplImage类型浅析 -
ihopethatwell:
楼主,如果Mat srcMat(Size(width, hei ...
OpenCV——Mat、CvMat、IplImage类型浅析
LastUpdataTime:11.11.14
首先,要弄清楚的是,本文记录的是“类型”的生命周期,而非“对象”的生存周期方面的笔记。当然,对象的生存周期实际上属于类型的生命周期问题的一部分。
何为类型的生命周期?
简单的讲,就是Java类型(类或接口)进入JVM开始到最终退出。从大体上讲,可以分为三个部分:开始阶段的装载、连接、初始化,占绝大多数时间的对象实例化、对象的垃圾回收、对象的终止,然后是退出阶段,即类型的卸载。
一、类型装载、连接与初始化
我们知道,类型数据通常保存在class文件中,JVM要使用某个类型,必须将这段信息“读取”进来。
1)装载。将二进制的类型数据读入JVM
很明显,要装载类型,必须要一份二进制的数据流。需要注意的是,这个二进制数据流不只是Java class文件,也可能遵守其他格式的文件。不同是JVM有不同的实现。
然后,JVM还要解析所读取的二进制文件的内部数据结构。
最后,如果解析无错误,JVM会为这个类创建一个Java.lang.Class类的一个对象,来映射读取的类型。
2)验证。确保类型数据格式正确,并且能为JVM使用
关于这一点,JVM规范并没有限定JVM的实现的具体做法,它仅仅是提供了一个接口而已。不同的JVM可能与有不一样的实现。
验证部分,按操作时间,实际上分为三种。
一种验证的是二进制文件的结构的正确性。同样的,JVM规范中并没有显式的定义二进制文件(大所数是class文件)的格式,而是又具体设计者自己设定。需要注意的是,这部分的验证过程是在装载过程中完成的,但在逻辑上还是属于验证部分。
另一种验证是真正的在"验证"过程是实现的它可能做以下事情:
检查final的类不能拥有子类
检查final方法不能被覆盖
检验常量池中的所有特殊字符串是否合乎格式
检查字节码的完整性
等等
还有一种验证是在"验证"过程以外完成的,就是符号引用的验证。
3)准备。负责为类型(包括类变量和类方法等等)分配内存
在准备阶段,JVM为类变量分配内存,并设置为默认值。如int变量赋0,String变量赋null。但是,在进入初始化阶段之前,变量都不会被初始化为真正的初始值,这是因为在准备阶段是不会执行Java代码的。
4)解析。将常量池中的符号引用变为直接引用。
与其他步骤不同,这个过程并不是JVM实现连接过程中的"必选"内容。JVM规范赋予每个JVM实现推迟解析的权利。
2、3、4步一起构成连接过程。
5)初始化。这个类型引入中的最后一个步骤,也就是为类型的每个静态变量或者静态块中的变量赋予"正确"的初始值。
二、何时初始化与自定义ClassLoader
OK,我们现在知道了一个类是怎样开始的,那么,我们现在自然而然的就会有这样一个疑问:一个类是在什么时候开始装载、连接以及初始化(下面我将这3个步骤统一称为装载)的呢?
这是一个很重要的问题,因为,类型的装载是完全由JVM操作的,对于一般的程序员而言是透明的。然而,下面还会继续提到,类型的装载时间是可控的。
在《The Java Language Specification》中给出了这样的建议:装载必须在连接之前,连接必须在初始化之前,唯一的硬性规定是:在首次主动使用时初始化。
何为主动使用,有下面6种情形:
1)创建新的实例,如:new Object();
2)调用静态方法,如:AnyClass.staticFun();
3)使用静态字段,如:AnyClass.staticField;
4)涉及到该类的反射机制,如:Class.forName("NameOfAnyClass");
5)初始化该类的子类,如:new SubClass();
6)JVM启动时那么包含main方法的类
上面给出的建议粗看起来像是废话,但仔细一看就会发现很多东西。
想一想,Java语言的实现只是要求我们在第一次使用某个类之前对其进行初始化就行了。那么,我们是不是要在程序运行之初就装载所有要用到的类呢?显然不要。
OK,假设这么个情形:有这么一个类,它的作用是为程序的安全退出做工作的,因此,在程序运行开始之后的很长一段时间JVM都不需要触碰到它。那么,理论上我们可以推迟它的装载时间----在这个类要用的时候再装载
这是不是和Java的反射机制很相似:在程序运行时再选择装载某个类。事实上,自定义ClassLoader 来动态的选择装载某个类和以前学习的反射是Java实现动态特性的两种方法。
看下面实现自定义ClassLoader 的代码:
package 番外篇_类的生存周期与类加载器管理; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class MyClassLoader extends ClassLoader{ private String basePath; //装载路径,类似于我们配置的Path变量 public MyClassLoader(String baseParh){ this.basePath = baseParh; } public MyClassLoader(ClassLoader parent,String basePath){ super(parent); this.basePath = basePath; } protected Class<?> findClass(String className) throws ClassNotFoundException { byte[] classData; classData = getTypeFromBasePath(className); if(null == classData){ throw new ClassNotFoundException("未能找到正确的二进制文件信息"); } //创建一个class对象并返回 //在define()内部会解析数据流为方法区的内部数据结构 //该方法是final的,不能重写 :问题,那么我们能不能定义我们自己格式的class文件呢? return defineClass(className,classData,0,classData.length); } /** * 读取类型信息(byte[]) * @param typeName ; 类的权限名 * @return */ private byte[] getTypeFromBasePath(String typeName){ FileInputStream fis; //通过类名获得路径名 比如在window中:java.lang.Object => java/lang/Object String fileName = basePath + File.separatorChar + typeName.replace('.', File.separatorChar)+".class"; try { fis = new FileInputStream(fileName); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } BufferedInputStream bis = new BufferedInputStream(fis); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { int c = bis.read(); while (c != -1) { out.write(c); c = bis.read(); } } catch (IOException e) { return null; } return out.toByteArray(); } }
首先,自定义类装载器必须继承java.lang.ClassLoader类。然后看构造器,这里实现了2个构造器,第二个构造器有2个参数,前一个参数parent是指定该ClassLoader 的父装载器,我们知道ClassLoader在装载类型的时候会先询问它的父装载器能否装载这个类型,当它的所有超类都不能装载时猜轮到自己。而第一个构造器实际上是指定bootClassLoader,即启动类装载器为父装载器。
接下来看方法find();它接受一个类型的权限定名(如:java.lang.NameOfClass)为参数。在这个方法的内部就做了2件事,一是读取文件(注意,我这里的实现只能读取.class结尾的文件,实际上你也可以用任意的文件后缀名,甚至不判断),二是将读取的byte[]类型数据传给defineClass()方法,并返回一个Class对象。defineClass()方法比较特殊,查看源代码可以看到,其最终是用一个native方法实现的,意思是说,它最终是由本地方法(通常是C或者C++)来支持的。
细心的同学可能发现,实际上这个方法只需要返回一个Class对象就行了,至于怎么获得返回的Class对象并没有规定。这个大家可以自己去测试,看看又什么问题。
当我们创建了一个自定义的ClassLoader对象之后,调用load(String NameOfClass)方法就可以装载某个类了,注意,这里传递的是权限定名,而不是简化类名。
下面看一看怎么用自定义的ClassLoader来实现简单的动态编程。
package 番外篇_类的生存周期与类加载器管理; import java.util.Scanner; public class TestLoadClass { public static void main(String args[]) throws InstantiationException, IllegalAccessException{ MyClassLoader classLoader = new MyClassLoader("D:\\"); Scanner sc = new Scanner(System.in); System.out.println("Please put the name of the Class you want load:"); String className = sc.next(); try { Class c = classLoader.loadClass(className); Object o = c.newInstance(); iCanLoaded ic = (iCanLoaded)o; //iCanLoaded是一个自定义的接口,定义了一个beLoaded()方法 ic.beLoaded(); // //forget all the information about the Class // classLoader = null; // o = null; // c = null; // ic = null; } catch (ClassNotFoundException e) { System.out.println("未找到需要装载的二进制文件"); } } }
先不要看代码,运行一下程序,如果不出意外的话,程序会在输出了一句:Please put the name of the Class you want load:之后阻塞住
现在,我们再编写一个CanLoadedClass类,继承iCanLoaded接口(没给出代码,很简单,自己实现),实现beLoaded()。
注意,直到这个时候,程序都没有退出,一直阻塞着。而且,更重要的是,我们并没有得到一个实现了iCanLoaded接口的class文件。
编译CanLoadedClass类之后,将得到的class文件放到D盘目录。在程序中输入"番外篇_类的生存周期与类加载器管理.CanLoadedClass"----这是类型的权限定名,请原谅我使用了中文包名。回车之后,程序执行了CanLoadedClass对象的beLoaded方法。
看到么,这就是典型的动态编程:在程序运行期间再慢悠悠的写代码
三、对象的生存周期
对象始于类型的实例化。它有很多种实现方法,比如最简单的 new Object()。
同类型一样,一个新对象也要经过分配内存、变量赋默认值、变量赋初始值等过程,当然,还有一个很重要的方法初始化问题。这个比较复杂,以后慢慢学习。
有人说,Java语言的两大核心是JVM和垃圾收集器(GC)。GC就是处理对象的终结任务的——当一个对象不再为程序所引用时。这个算法是一个很重要的东西,以后有时间单独列一篇总结出来。
四、类型的卸载与热更新(未完待续)
A class or interface may be unloaded if and only if its defining class loader may be reclaimed by the garbage collector Classes and interfaces loaded by the bootstrap loader may not be unloaded. --摘自《The Java Language Specification》
首先应该清楚,Java语言中是没有提供显式的卸载类型信息的方法的,不是么?那么怎样卸载类型的信息呢?
在讨论卸载类型之前,让我们先看一看类型卸载的一个重要特性----对程序透明。
我们知道,类型信息的本质不过是一块内存而已,类型的卸载是一种对内存的优化选项。根据程序设计的基本思想,一个依赖于系统对内存优化的程序不是一个好的程序(试想一下,一个程序能在高性能计算机上运行,却不能在老旧计算机上运行,能是好程序么?)。所以说,类型卸载是对程序透明的,这也是为什么Java设计者没有提供相应接口的原因(更大一部分是出于安全考虑)。
要卸载类型,就要回到上面的英文,翻译是这样的:当且仅当装载装载类型的类装载器为GC不可达的时,该类才可以被卸载。被根类装载器装载的类不会被卸载。
这就提供了一个思路:当我们让装载某个类的类装载器被GC回收,那么不就可以卸载类了么? 当然,前提是那个类装载器不是bootClassLoader。看最近贴上去的代码,那个被注释掉的地方就是,让装载ICanLoaded类的MyClassLoader对象classLoader = null ,这时由于classLoader 的引用计数器= 0,会被GC回收,那么它装载的类型信息就会被卸载。
这就完了?当然不是。作为一个程序员,面面俱到的周全考虑是一个好习惯,让我们想一想类型重载问题。
在Java规范当中,给出了类型卸载的建议,但是,注意,这仅仅是一个建议而已。谁都无法保证我们没有错误的卸载一个类型,或者说,谁读没有保证在我们卸载一个类型之后不需要用到这个类型。
类型的重载会导致一系列的问题,比如,考虑这么一种情况:
我们使用某个类的静态成员i并改变它的值,然后“不小心”卸载了该类,那么当我们重新加载这个类的时候,i的值会赋值为原来的初始值,而不是我们想要的改变后的值。事实上,这样的例子还很多,多涉及到类的静态属性,静态方法,静态块等问题。简而言之,类的重载没有想象中的那么简单----想卸载就卸载,想重载就重载。
类的重载对程序而言是不透明的,如果我们要卸载一个类,我们要考虑到之后的很多问题。这是一个很深、很值得研究的地方,而且,还衍生出类装载器管理这样一个领域。有兴趣的朋友可以研究研究,反正我是不懂的,orz
最后一个重要问题--热更新
当程序还在运行的时候,我们突然想更新某个模块,可以么?
这看起来和动态扩展有某些相似性,其实不然,动态扩展是在程序运行期间添加某些功能(实际上就是装载某些原来没有的类),而热更新是在程序运行期间更新程序已经装载了的类。
原理其实很简单,就是卸载该类,然后重载更新后的类(同名)。
发表评论
-
软件测试战略_测试那些事
2012-03-21 16:35 1734这几天看了一些关于 ... -
软件测试战略_初学者的看法
2012-03-21 16:04 5这几天看了一些关于软件工程里面软件测试方面的书籍,感觉蛮有收 ... -
《Thinking in Java》_类型信息与反射机制
2011-11-01 21:15 3639首先介绍一个本文后面会频繁提到的概念:RTTI(Runtim ... -
《TCP/IP详解》_卷一_主机对接收帧的过滤
2011-09-01 22:19 898前面写了一些关于广播和多播的笔记,这里加上一点主机在信道的帧过 ... -
《TCP/IP详解》_卷一_广播和多播
2011-09-01 20:21 1854在前面学习IP地址的概 ... -
《TCP/IP详解》_卷一_ARP和RARP协议
2011-08-31 20:44 1292首先,我们要弄明白 ... -
《TCP/IP详解》_卷一_IP与路由的选择
2011-08-05 21:58 1411毫无疑问,IP是整个TCP/IP体系中最为重要的,也是最核心的 ... -
《TCP/IP详解》_卷一_链路层及其协议简述
2011-07-28 20:53 1667链路层,有时也被称为数据链路层或网络接口层。它是TCP/IP协 ... -
《TCP/IP详解》_卷一_TCP连接的正常建立与关闭
2011-07-26 21:23 1301TCP是一个面向连接的协议。这就意味着,通信双方之间有一个虚拟 ... -
《TCP/IP详解》_卷一_TCP简介和报文段结构简介
2011-07-26 20:08 3218此乃《TCP/IP详解》这本书的第一篇笔记。 关于概述 ... -
《深入Java虚拟机》_平台无关性与程序的最佳可移植性
2011-07-23 20:51 1858在前面的日志中, ... -
《深入Java虚拟机》_Java体系结构
2011-07-21 21:06 1354Java体系结构: 当程序员编写和运行一个J ... -
平台无关性——七个步骤保证程序的最佳可移植性
2011-05-27 00:43 9081、选择程序要运行的主机和设备的集合(你的“目标宿主机”) ... -
平台无关性——Java体系结构对平台无关性的支持
2011-05-27 00:42 883Java体系从四个方面对它的平台无关性进行了支持 1、Jav ...
相关推荐
深入理解 Java 虚拟机笔记 Java 虚拟机(JVM)是 Java 语言的运行环境,它负责解释和执行 Java 字节码。下面是 Java 虚拟机相关的知识点: 虚拟机内存结构 Java 虚拟机的内存结构主要包括以下几个部分: * 方法...
Java对象在C/C++中需要使用局部引用和全局引用管理生命周期,以防止垃圾回收过早释放对象;如果多线程环境下调用Java方法,还需要注意线程安全问题。 总的来说,通过JNI,开发者可以在C/C++代码中无缝地调用Java...
第二行`java -jar testjar.jar`是实际运行Java程序的命令,`-jar`参数告诉Java虚拟机(JVM)去执行指定的JAR文件,即`testjar.jar`。 在执行这个批处理文件之前,确保系统环境变量`JAVA_HOME`和`PATH`已正确设置。`...
通过实践和使用这些命令,初学者可以更好地理解Java程序的生命周期,从编写源代码到编译、运行、调试和优化的全过程。在阅读《Java2语言命令详解》这样的资料时,应注重每个命令的语法、参数以及其在实际开发中的...
JDK提供了一个Java虚拟机(JVM),使得Maven能够运行在任何支持Java的平台上。通常,Maven的可执行文件(如`mvn`或`mvn.bat`)会查找系统的JAVA_HOME环境变量来确定Java的安装位置。 ### Maven的配置与运行 在...
- **跨平台性**:Java 代码(字节码)能够在任何安装了Java虚拟机(JVM)的平台上运行,从而实现了“一次编写,到处运行”的理念。 - **实现方式**:通过Java虚拟机(JVM)实现跨平台性。Java源代码被编译成与平台...
- Java的生态环境:JVM(Java虚拟机)、JDK(Java开发工具包) 2. **第二章:Java环境配置** - 下载与安装JDK - 设置环境变量:JAVA_HOME、PATH、CLASSPATH - 验证Java安装:`java -version` 和 `javac -...
Java是一种广泛使用的编程语言和计算平台,它拥有丰富的命令行工具集合,这些工具支持从开发到部署的整个Java应用程序生命周期。这些工具包含在JDK(Java Development Kit)中,是开发者日常工作的基础。下面将对...
- **JDK(Java Development Kit)**: 开发Java应用程序的基础工具包,包含Java运行环境(JRE)、Java虚拟机(JVM)以及编译器等。 - **安装步骤**: - 下载对应操作系统的JDK安装包; - 安装并配置环境变量(JAVA_...
- **命令**:查看当前Java虚拟机版本的命令为`java -version`。 #### 1.15 编译与运行Java程序 - **编译命令**:编译源文件`J-Test.java`的命令为`javac J-Test.java`。 - **运行命令**:运行编译后的程序命令为`...
首先,我们需要理解Java程序的生命周期,它从编写源代码(.java文件)开始,然后通过Java编译器(javac)将源代码编译成字节码(.class文件),最后由Java虚拟机(JVM)执行这些字节码。 一、Java源代码与编译 1. ...
理解线程的生命周期,同步机制(synchronized关键字、wait()、notify()和notifyAll()),以及ExecutorService和Future接口的使用。 8. **反射机制**:反射允许在运行时检查类的信息,例如类名、方法名和参数类型,...
它们各有特色,Java以其稳定性和广泛的社区支持而著名,Scala则以其强大的函数式编程特性及对Java虚拟机(JVM)的无缝集成受到青睐。当一个项目中同时包含Java和Scala代码时,如何有效地管理和编译这些代码就成为了...
《深入解析Java虚拟机JVisualVM与VisualGC插件》 在Java开发中,理解JVM(Java Virtual Machine)的工作原理对于优化程序性能至关重要。本文将聚焦于Java虚拟机的一个强大工具——JVisualVM,以及它的一个重要插件...
根据提供的文件信息,可以看出这份文件的内容涉及了Java程序的运行机制、Java字节码、跨平台性、JAR文件以及JAD文件的相关知识点。...这些步骤共同构成了Java应用程序的完整生命周期,从开发到部署再到运行。
- **栈内存**:存放基本类型变量和方法调用时的局部变量,内存分配速度快,生命周期短。 以上是Java编程的基础知识,包括开发环境设置、程序编译与运行、面向对象特性、文档生成、数据类型以及内存管理等方面。...
Java虚拟机(JVM)是运行Java字节码的虚拟环境,它为Java应用程序提供了一个独立于平台的执行环境。JVM的主要职责包括内存管理、垃圾回收、安全性和多线程支持等。通过JVM,Java程序可以在不同的操作系统上运行而...
- Java线程的生命周期包括新建、就绪、运行、阻塞、死亡五个阶段。 - 线程可以通过`start()`方法启动。 - **线程的同步**: - 线程同步是指控制多个线程对共享资源的访问。 - Java提供了多种同步机制,如`...
- **概述:** 本章主要介绍了如何搭建Java开发环境,包括Java软件开发工具包(SDK)的下载与安装、环境变量的配置、Java虚拟机(JVM)的安装以及集成开发环境JCreator的使用。 - **SDK的下载与安装:** - 访问Oracle...