The original link: http://zeroturnaround.com/rebellabs/reloading-objects-classes-classloaders/

A Bird's Eye View

鸟瞰

The first thing to understand when talking about reloading Java code is the relation between classes and objects. All Java code is associated with methods contained in classes. Simplified, you can think of a class as a collection of methods, that receive "this" as the first argument. The class with all its methods is loaded into memory and receives a unique identity. In the Java API this identity is represented by an instance of java.lang.Class that you can access using the MyObject.class expression.

关于reload Java代码需要了解的第一件事就是class和object的关系.所有的java代码都和包含在classes里的方法联系在一起.简单地说, 你可以认为class是一个方法的集合,他们接收"this"作为第一个参数.这个包含所有方法的class被载入到内存中并由一个唯一ID标识. 在Java API里这个ID由一个java.lang.Class的实例表示. 你可以通过 MyObject.class 表达式来访问它.

Every object created gets a reference to this identity accessible through the Object.getClass()method. When a method is called on an object, the JVM consults the class reference and calls the method of that particular class. That is, when you call mo.method() (where mo is an instance ofMyObject), then the JVM will call mo.getClass().getDeclaredMethod("method").invoke(mo) (this is not what the JVM actually does, but the result is the same).

每个object 在创建时都会获得一个指向这个ID的引用, 我们可以通过Object.getClass()访问到它.当一个object调用一个方法,JVM会查询class引用并且调用这个class的方法.也就是说,当你调用 mo.method()(mo是一个MyObject实例),JVM就会调用mo.getClass().getDeclaredMethod("method").invoke(mo)(JVM实际上并不是这么做,但是结果是一样的)(这不是反射么?!)

Every Class object is in turn associated with its classloader (MyObject.class.getClassLoader()). The main role of the class loader is to define a class scope -- where the class is visible and where it isn't. This scoping allows for classes with the same name to exist as long as they are loaded in different classloaders. It also allows loading a newer version of the class in a different classloader.

每一个Class object反过来也会和它的classloader联系起来(MyObject.class.getClassLoader()).class loader的主要作用是定义class的作用于 -- 即class在哪里可见,在哪里不可见.这个作用于允许class使用通用的名字存在, 只要他们是被不同的classloader载入的.同时它也允许在不同的classloader中载入一个新版本的class

The main problem with code reloading in Java is that although you can load a new version of a class, it will get a completely different identity and the existing objects will keep referring the previous version of the class. So when a method is called on those objects it will execute the old version of the method.

在java中reload一个class最主要的问题是虽然你可以载入一个新版本的class,他会拿到一个全新的class id,但是已经存在的object的引用却只想旧版本的class.所以当你调用那些已存在的object的方法的时候,他还是会执行旧版本的方法.

Let's assume that we load a new version of the MyObject class. Let's refer to the old version asMyObject_1 and to the new one as MyObject_2. Let's also assume that MyObject.method() returns "1" in MyObject_1 and "2" in MyObject_2. Now if mo2 is an instance of MyObject_2:

假设我们加入了一个新版本的MyObject class. MyObject_1是旧版本的class id, MyObject_2是新版本的class id.并且MyObject_1的MyObject.method()方法返回"1", MyObject_2的这个方法返回"2".mo2是一个MyObject_2的实例,则有如下:

  • mo.getClass() != mo2.getClass()
  • mo.getClass().getDeclaredMethod("method").invoke(mo)
    != mo2.getClass().getDeclaredMethod("method").invoke(mo2)
  • mo.getClass().getDeclaredMethod("method").invoke(mo2) throws a ClassCastException, because the Class identities of mo and mo2 do no match.

This means that any useful solution must create a new instance of mo2 that is an exact copy of moand replace all references to mo with it. To understand how hard it is, remember the last time you had to change your phone number. It's easy enough to change the number itself, but then you have to make sure that everyone you know will use the new number, which is quite a hassle. It's just as difficult with objects (in fact, it's actually impossible, unless you control the object creation yourself), and we're talking about many objects that you must update at the same time.

这意味着必须创建一个新的从mo精确拷贝过来的mo2实例,并将所有mo的引用替换成mo2.要明白这有多难,回想一下上你换手机号码的时候, 换手机号码很容易,但是你要让所有你认识的人都用你这个新的号码还是有点难度.对于object来说难度也是一样的(事实上,这几乎是不可能的,除非你自己控制了对象的创建过程), 当然我们关心的问题是你需要同时更新很多object.

Down and Dirty

丑陋

Let's see how this would look in code. Remember, what we're trying to do here is load a newer version of a class, in a different classloader. We'll use an Example class that looks like this:

让我们看看这样的代码是怎么样的. 记住, 我们要做的事情是在一个不同的classloader载入一个新版本的class, 下面是用到的一个Example class

1
2
3
4
5
6
7
8
9
10
11
12
public class Example implements IExample {
private int counter;
public String message() {
return "Version 1";
}
public int plusPlus() {
return counter++;
}
public int counter() {
return counter;
}
}

We'll use a main() method that will loop infinitely and print out the information from the Exampleclass. We'll also need two instances of the Example class: example1 that is created once in the beginning and example2 that is recreated on every roll of the loop:

我们使用main()无限循环打印Example类的信息.同时我们还需要两个Example的实例: example1 从一开始就被创建, example2 每次循环都会重新创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
private static IExample example1;
private static IExample example2;
 
public static void main(String[] args) {
example1 = ExampleFactory.newInstance();
 
while (true) {
example2 = ExampleFactory.newInstance();
 
System.out.println("1) " +
example1.message() + " = " + example1.plusPlus());
System.out.println("2) " +
example2.message() + " = " + example2.plusPlus());
System.out.println();
 
Thread.currentThread().sleep(3000);
}
}
}

IExample is an interface with all the methods from Example. This is necessary because we'll be loading Example in an isolated classloader, so Main cannot use it directly (otherwise we'd get aClassCastException).

IExample是包含Example所有方法的接口.这是很有必要的,因为我们会用完全隔离的classloader来载入Example, 所以Main不能直接使用它(否则我们会得到一个ClassCastException)

1
2
3
4
public interface IExample {
String message();
int plusPlus();
}

From this example, you might be surprised to see how easy it is to create a dynamic class loader. If we remove the exception handling it boils down to this:

在这个例子里,你可能会很惊讶的看到如此简单就创建了一个动态的class loader. 如果我们移除了异常处理,简单来看就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ExampleFactory {
public static IExample newInstance() {
URLClassLoader tmp =
new URLClassLoader(new URL[] {getClassPath()}) {
public Class loadClass(String name) {
if ("example.Example".equals(name))
return findClass(name);
return super.loadClass(name);
}
};
 
return (IExample)
tmp.loadClass("example.Example").newInstance();
}
}

The method getClassPath() for the purposes of this example could return the hardcoded classpath. However, in the full source code (available in the Resources section below) you can see how we can use the ClassLoader.getResource() API to automate that.

方法getClassPath()是为了让这个example可以返回硬编码的classpath.然而,完整的源代码里你可以看到如何使用ClassLoader.getResource()API来自动获取.

Now let's run Main.main and see the output after waiting for a few loop rolls:

现在我们运行Main.main看看输出:

1) Version 1 = 3
2) Version 1 = 0

As expected, while the counter in the first instance is updated, the second stays at "0". If we change the Example.message() method to return "Version 2". The output will change as follows:

跟想象的一样, 当counter在第一个实例里更新以后, 第二个停在了0.加入我们改变Example.message()方法让他返回"Version 2". 输出会变成下面这样

1) Version 1 = 4
2) Version 2 = 0

As we can see, the first instance continues incrementing the counter, but uses the old version of the class to print out the version. The second instance class was updated, however all of the state is lost.

如你所见, 第一个实例的counter继续自增,但是使用的是旧版本的class来输出version.第二个实例的class被更新了.但是所有的状态都丢失了

To remedy this, let's try to reconstruct the state for the second instance. To do that we can just copy it from the previous iteration.

为了补救这一点,我们重构了第二个实例的state. 我们只需要将它从前一个迭代中复制过来就好

First we add a new copy() method to Example class (and corresponding interface method):

受限添加一个copy()方法到Example类中(要与接口方法保持一致)

1
2
3
4
5
public IExample copy(IExample example) {
if (example != null)
counter = example.counter();
return this;
}

Next we update the line in the Main.main() method that creates the second instance:

接下来我们更新Main.main()中创建example2的代码

9
example2 = ExampleFactory.newInstance().copy(example2);

Now waiting for a few iterations yields:

现在等待几次迭代:

1) Version 1 = 3
2) Version 1 = 3

And changing Example.message() method to return "Version 2" yields:

然后改变Example.message()方法让他返回"Version 2"

1) Version 1 = 4
2) Version 2 = 4

As you can see even though it's possible for the end user to see that the second instance is updated and all its state is preserved, it involves managing that state by hand. Unfortunately, there is no way in the Java API to just update the class of an existing object or even reliably copy its state, so we will always have to resort to complicated workarounds.

如你所见,就算最后用户看到了第二个实例被更新了且保留了所有状态, 但他是靠手动管理那个state的.不幸的是,Java API没有办法更新一个已存在的object或者可靠地复制他的状态, 所以我们必须采取复杂的变通方法来实现.

In subsequent articles we'll review how web containers, OSGi, Tapestry 5, Grails and others confront the problem of managing state when reloading classes, then we'll dig into how HotSwap, Dynamic Languages, and the Instrumentation API work, and go behind the scenes with JRebel as well.

在后续的文章里, 我们会回顾web容器, OSGi, Tapestry 5, Grails 和其他东西在是如何面对在重新载入class的时候管理状态的.然后我们会继续深挖 HotSwap, 动态语言, 还有Instrumentation API的工作方式,并且查看JRebel背后的景象.

Resources

Reloading Java Classes 101: Objects, Classes and ClassLoaders Translation的更多相关文章

  1. Reloading Java Classes 301: Classloaders in Web Development — Tomcat, GlassFish, OSGi, Tapestry 5 and so on Translation

    The Original link : http://zeroturnaround.com/rebellabs/rjc301/ Copyright reserved by Rebel Inc In t ...

  2. Classes as objects

    Before understanding metaclasses, you need to master classes in Python. And Python has a very peculi ...

  3. Java中的Nested Classes和Inner Classes

    Java中的Nested Classes和Inner Classes Java有嵌套类(Nested Classes)和内部类(Inner Classes)的概念. 嵌套类(Nested Classe ...

  4. [REPRINT] Java 101: Classes and objects in Java

    http://www.javaworld.com/article/2979739/learn-java/java-101-classes-and-objects-in-java.html?page=3 ...

  5. Reloading Java Classes 201: How do ClassLoader leaks happen? Translation

    The original link : http://zeroturnaround.com/rebellabs/rjc201/ From ClassLoaders to Classes 从ClassL ...

  6. java web项目中classes文件夹下的class和WEB-INF/lib中jar里的class文件加载顺序

    如果是发布到weblogic的话,可以在WebContent\WEB-INF\weblogic.xml里面配置.参考配置如下:<?xml version="1.0" enco ...

  7. Java用代码获取classes目录下的文件的方式

    假设我们有个Maven的Web项目,其中有src/main/java和src/main/resources两个输出目录(注意这两个目录下的内容运行后都会输出到classes目录下), 而在src/ma ...

  8. Effective Java 14 In public classes, use accessor methods, not public fields

    Principle To offer the benefits of encapsulation you should always expose private field with public ...

  9. Think Python - Chapter 15 - Classes and objects

    15.1 User-defined typesWe have used many of Python’s built-in types; now we are going to define a ne ...

随机推荐

  1. ClouderaManager中Event Server报No such file or directory

    错误日志如下: 2015-06-24 06:13:10,176 ERROR com.cloudera.cmf.eventcatcher.server.EventCatcherService: Erro ...

  2. CI框架与Thinkphp框架的一些区别

    初学CI框架遇到的一些问题,与Thinkphp框架对比的不同之处. system             是框架核心 application        是项目目录 index.php        ...

  3. java学习之租车系统

    ​背景:有三种类型的车供给用户来租用​ ​要求:控制台用户交互界面,根据用户需求输出租车价格,结果如下: 创建租车类主要设计过程: 创建租车类 创建Car父类,包含四种属性成员,重写构造方法 创建三种 ...

  4. bugku web题INSERT INTO注入

    0x01: 打开题目描述,已经将源码给了我们: <?php error_reporting(0); function getIp(){ $ip = ''; if(isset($_SERVER[' ...

  5. Linux设备驱动之USB

    Linux驱动框架分析(一)        事实上,Linux的设备驱动都遵循一个惯例——表征驱动程序(用driver更贴切一些,应该称为驱动器比较好吧)的结构体,结构体里面应该包含了驱动程序所需要的 ...

  6. listview重新计算高度

    将xml中的ListView改用下面的ListViewForScrollView //ScrollView中嵌入ListView,让ListView全显示出来 public class ListVie ...

  7. BZOJ.1018.[SHOI2008]堵塞的交通(线段树维护连通性)

    题目链接 只有两行,可能的路径数不多,考虑用线段树维护各种路径的连通性. 每个节点记录luru(left_up->right_up),lurd,ldru,ldrd,luld,rurd,表示这个区 ...

  8. Centos部署使用Jexus承载asp.net core2 web应用

    一,首先安装本地开发项目用的的 core对应版本运行时: https://www.microsoft.com/net/download/linux-package-manager/centos/run ...

  9. Hadoop系列之(三):使用Cloudera部署,管理Hadoop集群

    1. Cloudera介绍 Hadoop是一个开源项目,Cloudera对Hadoop进行了商业化,简化了安装过程,并对hadoop做了一些封装. 根据使用的需要,Hadoop集群要安装很多的组件,一 ...

  10. CentOS启动OpenVPN报错:Failed to start OpenVPN Robust And Highly Flexible Tunneling Application On server.

    tailf /var/log/openvpn.log 查看日志,里面有最详细的错误解说. 参考: https://forums.openvpn.net/viewtopic.php?t=21561