【问题标题】:Caching java instance for peformance缓存 java 实例以提高性能
【发布时间】:2022-02-01 08:31:04
【问题描述】:

请帮忙,例如在java中缓存这一行/新实例是什么意思:

XPath xpath = XPathFactory.newInstance().newXPath();

我知道我必须存储某种记忆...谁能给我举个例子。

谢谢。

【问题讨论】:

  • 这取决于您究竟需要做什么以及需要缓存多长时间。最简单的意思是“缓存”只是将对该实例的引用存储在某个地方,使其可用于需要它的代码 - 还取决于需要它的范围,即可以是方法、类(例如该类中的方法)或更全局可用的上下文。
  • 另一个相关注意事项:使用缓存需要考虑线程安全。如果缓存的实例有可能被多个线程同时使用——直接或间接——你需要确保这样做是安全的(检查类文档,它通常应该说明它是否是线程安全的——如果它没有'不声明任何假设该类不是线程安全的)。您可能还没有达到处理线程的水平,在这种情况下,此评论可能没有直接帮助,但请记住以供日后参考。
  • 我同意 Matteo 的回答,因为它只是意味着将一个实例变量全局声明为成员实例,并且该变量只能在加载类时加载一次,因为它是多个调用的公共变量。在课堂上...谢谢大家

标签: java xml caching


【解决方案1】:

缓存意味着不要让垃圾收集器在使用变量后丢弃它,如果您已经知道稍后需要使用相同的变量(但 GC 不理解这一点)。

这实际上取决于Xpath 状态持续多长时间(可能是函数作用域、实例作用域或类作用域 - 甚至是更缩小的作用域,如 for 循环或 if 块,但这只是你知道)。

以下内容应该有助于理解:

案例 1 - 函数内部

如果你这样做:

public Object doSomething() {
    //code...
    XPath xpath = XPathFactory.newInstance().newXPath();
    //code...
}

..那么垃圾收集器会认为一旦你退出了你不再需要它的功能,所以它很快就会把它扔掉。下次您再次调用该函数时,您将不得不从头开始重新构建它。

案例 2 - 作为类字段

如果您改为这样做:

public class YourClass {
    
    private final XPath xpath = XPathFactory.newInstance().newXPath();

    public Object doSomething() {
        //code...
        this.xpath.use(...);
        //code...
    }

.. 那么每个创建的实例你只做一次工作。如果您创建 10 个类的实例,您将执行 10 次。如果你只创建一个,你只会做一次。 只要该实例存在,垃圾收集器就会保留每个实例的值。

案例 3 - 静态字段

但如果这真的从不依赖任何东西,那么它应该是静态的:

public class YourClass {
    private static final XPath XPATH = XPathFactory.newInstance().newXPath();
    
    public Object doSomething() {
        //code...
        XPATH.use(...);
        //code...
    }        
}

...在最后一种情况下,无论您构建了多少个类的实例,您将始终只有一个且只有一个 Xpath 实例,而垃圾收集器将让变量平静地存在 as只要您的类被使用/位于包含已使用类的类加载器中

(小记:ClassClassLoader 加载后立即初始化静态字段,ClassLoader 加载该类和许多其他类。该类符合 GC 条件的唯一情况是该类并且该类加载器的所有其他类都变得无法访问。这是一个非常难以访问的状态,这意味着通常,一旦初始化静态字段,您可以非常安全,直到您关闭您的应用程序)。

【讨论】:

  • 我不太确定最后一部分:“只要你的类的一个实例存在于某处”。即使没有实例,JVM 通常也不应该卸载该类。我还将添加一个选项(或至少提及它),以使缓存的实例在包含它的类之外可用,例如可以作为单例访问的某种形式的上下文。
  • @Thomas 这是一个极端情况,但它是可能的。当没有人再引用它并且它的类加载器以及同一类加载器中的所有其他类都无法访问时,一个类就有资格进行垃圾收集。在标准设置中,这是一个很难达到的状态,但理论上是可能的。我将在我的最后一句话中更加明确。
【解决方案2】:

假设上面一行的代码是从循环中调用的:

void bar() {
 for (int i = 0; i < 10; i++) {
   XPath xpath = XPathFactory.newInstance().newXPath();
   // use xpath variable
  }
}

这里创建了 10 个 XPath 实例。或者,您可以将 xpath 变量声明提升到循环之外,这样只会创建 1 个实例:

void bar() {
 XPath xpath = XPathFactory.newInstance().newXPath();
 for (int i = 0; i < 10; i++) {
   // use xpath variable
  }
}

这是最简单的缓存案例,即重用某些资源而不是重新创建它。

【讨论】:

    【解决方案3】:

    要缓存三个重要的对象:

    (a) 源文件。如果您对同一个文档运行多个查询,您不想为每个查询重复解析 XML 文件。解析一次,然后保存生成的树。大多数人似乎都使用默认的 DOM 树模型,但也有一些替代方案,例如 JDOM2 和 XOM,它们对用户更加友好。

    (b) XPath 引擎。初始化 XPath 引擎通常很昂贵。如果您要评估许多 XPath 表达式,则只需执行一次。

    (c) 单个 XPath 表达式。如果您需要重复执行同一个 XPath 表达式,请记住,表达式的初始编译可能需要 100 倍于表达式每次求值的时间。

    因此,您希望将这些对象保留在内存中并尽可能重用它们。

    缓存是实现这一目标的一种方式。术语“缓存”通常意味着您的应用程序在想要创建对象时总是会发出新请求,但是一些中间层会识别出这是对已经在内存中的对象的请求,因此不需要再次创建它从头开始。

    所以你可能有一个源文档的缓存,所以当你的应用程序调用Document doc = fetchDocument(filename)时,fetchDocument的实现会保留一个内存中的哈希表,如果文档已经存在,它会从哈希中获取它表,否则它从文件存储中读取并解析文件。更复杂的缓存将丢弃最近最少使用的文档,以避免内存需求不断增长。

    对于 XPath 引擎,它将是一个微不足道的单项缓存:可能类似于

    XPath getXPathEngine() {
      if (xpath == null) xpath = XPathFactory.newInstance().newXPath();
      return xpath;
    }
    

    用于缓存已编译的 XPath 表达式。如果您使用 Saxon API,则不必实现自己的缓存,它会在幕后自动完成。例如,如果你这样做:

    Processor proc = new Processor();
    XPathCompiler xpc = proc.newXPathCompiler();
    xpc.setCaching(true);
    XPathExpression exp = xpc.compile("//x");
    

    然后您可以通过直接引用它或通过再次调用来编译相同的表达式来重用已编译的表达式exp,在这种情况下,它将从缓存中检索。

    如果您重复使用相同的 XPath 表达式,则使用变量对其进行参数化:使用表达式 //x[name=$param] 并使用 $param 的不同值重复执行它,而不是构建表达式,例如 "//x[name=' + param + "']",其中每个表达式必须重新编译。 (构建这样的表达式也会使您受到注入攻击。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-16
      • 2018-09-04
      • 1970-01-01
      • 2010-12-27
      • 2012-08-13
      相关资源
      最近更新 更多