`
deepinmind
  • 浏览: 444984 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:40840
社区版块
存档分类
最新评论

Java的对象驻留

阅读更多
Java会将源代码中的字符串常量存储到常量池中。也就是说,当你这么写的时候:

String a = "I am a string";
String b = "I am a string";
 


变量a和变量b是同一个值。这不只是说它俩的值是一样的,而是说就是同一个字符串对象。用Java的话来说就是a==b的结果是true。然而这个只对字符串以及小的整型或者长整型有效。其它的对象是不会被驻留的,也就是说如果你创建了两个对象而他们的值是相等的,但他们并不是同一个对象。这个问题有时候很讨厌,尤其是当你从某个持久化存储中取出一个对象时。如果同一个对象你取了两次,你当然希望最终取出的是同一个对象,不过实际上你取出的是两份拷贝。换句话说你其实希望的是取出的是存储中那个对象在内存里面的同一个拷贝。有些存储层是会做这样的处理的。比如说JPA的实现就是遵循这个模式的,而别的情况可能你就得自己去做缓存了。

这篇文章中我会介绍一个简单的内部池的实现,这在stackoverflow上也有一个[url=http://it.deepinmind.com]相关的主题[/url]。我还会把为什么会选用文中的这种实现方式的具体原因介绍给大家。跟stackoverflow上面的讨论相比,本文中会有附上详细的教程信息。

[b]对象池[/b]

对象驻留需要一个对象池。当你需要驻留一个对象时,你会先去看一下对象池里面是不是已经有一个同样的对象了。如果已经有了,就直接使用那个对象。如果还没有,就把这个对象放进池里然后使用它。

实现过程中我们主要面临的有两大问题:


垃圾回收
多线程



当不再需要一个对象时,应该把它从池里移除掉。这个移除操作可以由应用程序负责,不过这样的做法太老套了。Java和C++相比,最大的好处就是垃圾回收(译注:C++的同学看到后求别喷)。我们可以让GC来回收这些对象。想要这样做的话,池里的对象就不能再用强引用了。

[b]引用[/b]

如果你已经知道什么是软引用,弱引用和虚引用,就直接跳到下节。

你可能已经注意到了,我没有直接说引用,而是说的强引用。如果你认为GC是回收那些没有引用的对象的话,这个是不太准确的。实际上GC判断对象是否可达看的是强引用。更准确的描述是,一个对象存在强引用,必定是从其它强引用对象的本地变量,静态变量或者其它类似的地方直接引用过来的。换句话说,如果一堆对象通过某个不存活的对象“强引用”过来的话,它们会被一起回收掉。

如果这是强引用的话,那当然你会想肯定有些引用不是强引用了。对的,有一个java.lang.Reference类,它有几个子类,分别是:


PhantomReference
WeakReference and
SoftReference


它们都在同一个包下面。如果你看一下文档,你会觉得,这里我们应该需要用到的是弱引用。虚引用肯定是不能用到这个池里了,因为通过它可能无法获取到最终的对象。软引用的话,又显得有些浪费了。如果已经没有强引用了,也没必要把对象留在池里了。下次还有请求过来的话,我们会重新再驻留它。当然这肯定就是另外一个实例了,不过你也察觉不到这个,因为原先的那个早就没人引用了。

弱引用可以用来获取对象,但又不会影响GC的判断。

[b]WeakHashMap[/b]

当然我们不会直接使用弱引用。有一个叫WeakHashMap的类,它使用弱引用来引用key。这就是我们想要的。当我们驻留一个对象时候,我们会看池里是否已经有一个一样的了,Map正好可以用来查找对象。用弱引用来存储key的话,如果这个key不再需要了,GC会负责回收掉它。

现在我们已经可以查找对象了,很好。用map是为了获取对象,如果我们想获取到同一个对象的话,如果它还没在池里,我们当然得先把它存到map里。不过如果直接放对象存到map里就破坏了我们使用弱引用存储key的初衷了。我们得把这个目标对象也存成弱引用的。

[b]WeakPool[/b]

解释了这么多,该上代码了。如果池里有一个对象和要驻留的对象一样的话,就返回那个对象。如果没有,直接返回null,同时put方法用来存储新对象到池里,它用会一个新的对象来覆盖旧的那个。

public class WeakPool<T> {
  private final WeakHashMap<T, WeakReference<T>> pool = new WeakHashMap<T, WeakReference<T>>();
  public T get(T object){
      final T res;
      WeakReference<T> ref = pool.get(object);
      if (ref != null) {
          res = ref.get();
      }else{
          res = null;
      }
      return res;
  }
  public void put(T object){
      pool.put(object, new WeakReference<T>(object));
  }
}



驻留池

问题最终的解决方案是一个驻留池,用上面的WeakPool就可以轻松实现它了。这个InternPool里面有一个WeakPool的实现,这个类只有一个synchronized的intern方法。

{% highlight java%}
public class InternPool<T> {
  private final WeakPool<T> pool = new WeakPool<T>();
  public synchronized T intern(T object) {
    T res = pool.get(object);
    if (res == null) {
        pool.put(object);
        res = object;
    }
    return res;
  }
}




这个方法会先尝试从池里获取对象,如果获取不到的话,再把这个对象存储进去,并返回它。如果已经有一个匹配的了,就返回池里那个匹配的对象。

多线程

这个方法标记成synchronized是为了确保判断和插入操作是一个原子操作。没有同步的话,如果两个线程同时检查两个相同的对象,都发现池里没有匹配的,它们会都执行插入操作。最后插入的那个会覆盖先插入的那个对象,而先完成的线程会认为自己拿到的对象是唯一的。同步解决了这个问题。

和GC的竞争冲突

尽管使用这个池的不同线程不会面临并发操作的问题,但是我们还是得看一下会不会和GC线程产生冲突。

当调用弱引用的get方法时有可能会返回null。这是有可能的,如果key对象已经被垃圾回收掉了,而WeakPool里面的WeakHashMap还没有删除这条记录的话。尽管每次WeakHashMap查询的时候都会检查这个key是否仍存在,但GC在这个get方法调用的过程中可能会介入。即便在map返回了引用的时候这个对象仍然存在,但由于这是个弱引用,在下次调用引用的get方法时,这个对象可能已经被GC给回收掉了。

这个情况下,WeakPool是会返回null的。不过没事,这对InternPool也不会产生影响。

如果你看一下我前面提到的stackoverflow里面一个讨论话题,你会发现有人是这么实现的:

public class InternPool<T> {
 
    private WeakHashMap<T, WeakReference<T>> pool =
        new WeakHashMap<T, WeakReference<T>>();
 
    public synchronized T intern(T object) {
        T res = null;
        // (The loop is needed to deal with race
        // conditions where the GC runs while we are
        // accessing the 'pool' map or the 'ref' object.)
        do {
            WeakReference<T> ref = pool.get(object);
            if (ref == null) {
                ref = new WeakReference<T>(object);
                pool.put(object, ref);
                res = object;
            } else {
                res = ref.get();
            }
        } while (res == null);
        return res;
    }
}



这段代码里作者用了个无限的循环来解决这个问题。虽然这样做并不是很好,但也能解决问题。这个循环不太可能无限执行下去,但不太可能也是有可能的。同时这个结构让人很难读懂,太复杂了。记住一点,单一职责原则。关注最简单的事情,把你的程序拆分成各个简单的组件。

总结

尽管Java只对字符串和一些基础类型做了驻留,但有时候你还是需要自己驻留一些别的对象。这种情况下,应用程序就得自己想办法解决了。这个时候,上面这两个类就派上用场了,你可以直接复制粘贴到你的代码里,也可以:

<dependency>
  <groupId>com.javax0</groupId>
  [url=http://it.deepinmind.com]intern</artifactId>
  <version>1.0.0</version>
</dependency>



导入上面的maven库。这个库非常小,只有上面那两个类而已,并且它是apache license的,放心用吧。库的源码在GitHub上可以下载到。


原创文章转载请注明出处:http://it.deepinmind.com

英文原文链接















4
1
分享到:
评论
3 楼 andrii 2014-05-26  
andrii 写道
String str1 = "str1";
String str2 = "str2";
System.out.println(str1 == str2)//false;

自己写错了!
2 楼 andrii 2014-05-26  
String str1 = "str1";
String str2 = "str2";
System.out.println(str1 == str2)//false;
1 楼 fair_jm 2014-04-16  
文章格式有点问题吧...挺不错的~

相关推荐

    Java中的字符串驻留

     顾名思义,驻留是在内存中保留(在Java中,我们通常称驻留对象的地方为驻留池,不过它也是内存的一部分),它不仅存在于Java中,在C#中同样存在。那么我写几个例子来讲解什么叫Java中字符串的驻留: public ...

    Android仿String的对象驻留示例分析

    本文实例分析了Android仿String的对象驻留。分享给大家供大家参考,具体如下: String a = “abc”; String b = “abc”; a == b true; 变量a和变量b是同一个值。这不只是说它俩的值是一样的,而是说就是同一个...

    Py4J-开源

    Py4J使Python程序能够动态访问任意Java对象。 就像Java对象驻留在Python虚拟机中一样调用方法。 双方都没有代码可生成,也没有接口可用于实现共享对象。

    java-builders:检查Java对象的构建变体

    检查Java对象的构建变体。 伸缩构造器 伸缩构造函数示例。 优点: 简单的。 缺点: 可扩展性差。 JavaBeans JavaBeans 示例。 优点: 简单的; 伸缩性好。 缺点: 使用的样板过多; 允许不一致和易变。 ...

    超市收银管理系统java源码-updated_gof:更新_gof

    对象驻留在堆内存中,我们可以在堆内存中的物理空间允许的情况下实例化尽可能多的对象。 但是,在某些情况下,我们可能会遇到只能实例化类的一个实例的情况。 因此,假设我们正在开发一个播放音频文件的程序。 在该...

    Java编程语言中EJB技术的详细说明

    Enterprise JavaBean (EJB) 1.1 规范定义了开发和部署基于事务性、分布式对象...这些服务器端组件称作 Enterprise Bean,它们是 Enterprise JavaBean 容器中驻留的分布式对象,为分布在网络中的客户机提供远程服务。

    数据库查询系统.doc

    2.4 DatabaseClient.java(客户端主类) DatabaseClient是Applet类的子类,尽管DatabaseClient类需要驻留在服务器端,但它 需要下载到客户端的浏览器来运行,因此Java Applet是客户端程序。DatabaseClient类的成员...

    Python中的 is 和 == 以及字符串驻留机制详解

    这点和Java有点类似,只不过Java中是用 == 来比较两个对象在内存中的地址,用 equals() 来检查两者之间的值是否相等。 is == 概念 对象标示符 相等 作用 比较对象在内存中的地址 检查

    100-words-design-patterns-java:GoF设计模式,每个模式都描述了真实生活中的故事

    对象驻留在堆内存中,并且我们可以实例化堆内存中允许的尽可能多的对象。但是,在某些情况下,我们可能会遇到只能实例化一个类的一个实例的情况。因此,想象一下我们正在开发一个正在播放音频文件的程序。在该程序...

    java简易版开心农场源码-GOF23:一起来学习设计模式吧~

    java简易版开心农场源码 1. 创建型模式 创建型模式关注的是对象创建的过程, 有单例模式, 工厂模式, 生成器模式, 原型模式 1. 单例模式 单例模式 Singleton 的核心思想: 保证一个类只有一个实例, 并且提供一个访问该...

    Java in memory JNDI context-开源

    Java JNDI 1.1命名上下文。 JNDI不仅仅适用于J2EE,借助此软件包,您可以将其与任何Java应用程序一起使用。 该上下文完全驻留在内存中,可以通过xml进行配置,并且能够存储任何Java对象。 需要Java 1.4或更高版本。

    WebSphere参数调优.txt

    Java 虚拟机(JVM)堆大小设置将影响 Java 对象的无用数据收集。堆设置过大,会占用过多的内存,使内存资源耗尽,从而会频繁的进行I/O操作来使用虚拟内存。堆设置过小,会使得对象可分配空间变小,从而会频繁的使用...

    Java字符串的10大热点问题盘点

    下面我为大家总结了10条Java开发者经常会提的关于Java字符串的问题,如果你也是Java初学者,仔细看看吧:  1、如何比较字符串,应该用”==”还是equals()?  总的来说,”==”是用来比较字符串的引用地址,而...

    flink-spillable-statebackend:Apache Flink的可溢出状态后端的预览版

    HeapKeyedStateBackend是Flink中两个KeyedStateBackend之一,因为状态作为HeapKeyedStateBackend堆上的Java对象HeapKeyedStateBackend并且反序列化仅在状态快照和还原期间发生,所以当所有数据都可以驻留在内存中时...

    Java String 十大常见问题

     如果你了解字符串的驻留(String Interning)则会更好地理解这个问题。  2. 对于敏感信息,为何使用char[]要比String更好?  String是不可变对象,意思是一旦创建,那么整个对象不可以改变,即使新手觉得String...

    javabean介绍

    Javabean的介绍,不懂的看看 ...它们都是用一组特性创建,以执行其特定任务的对象或组件。它们还有从当前所驻留服务器上的容器获得其它特性的能力。这使得 bean 的行为根据特定任务和所在环境的不同而有所不同。

    GOF23之单例模式

    1、单例模式只能产生一个实例,减少了系统性能的开销,当一个对象的产生需要比较多的资源时,如“读取配置,产生其他依赖对象”可以通过在应用启动时产生一个单例对象,然后永久驻留内存 2、单例模式可以在系统设置...

    深入浅出Struts 2 .pdf(原书扫描版) part 1

    这是因为某个servlet(服务器端Java程序)在应用户的请求而首次调入内存执行之后将一直驻留在内存里,对同一个servlet的后续请求不用再对这个servlet的类进行实例化,因此响应速度更快。 可是,servlet也存在一个...

Global site tag (gtag.js) - Google Analytics