1

使用 java 代码访问 haddop 文件时出现堆栈溢出错误。

import java.io.InputStream;
import java.net.URL;
import org.apache.hadoop.fs.FsUrlStreamHandlerFactory;
import org.apache.hadoop.io.IOUtils;
public class URLCat 
{
    static 
    {
            URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
    }

    public static void main(String[] args) throws Exception 
    {
        InputStream in = null;
        try 
        {
            in = new URL(args[0]).openStream();
            IOUtils.copyBytes(in, System.out, 4096, false);
        }
        finally 
        {
            IOUtils.closeStream(in);
        }
    }
}

我用 eclipse 调试这段代码然后我就知道了

in = new URL(args[0]).openStream();

产生错误。

我通过传递hadoop文件路径来运行这段代码,即

 hdfs://localhost/user/jay/abc.txt

例外(从评论中提取):

Exception in thread "main" java.lang.StackOverflowError
  at java.nio.Buffer.<init>(Buffer.java:174) 
  at java.nio.ByteBuffer.<init>(ByteBuffer.java:259) 
  at java.nio.HeapByteBuffer.<init>(HeapByteBuffer.java:52) 
  at java.nio.ByteBuffer.wrap(ByteBuffer.java:350) 
  at java.nio.ByteBuffer.wrap(ByteBuffer.java:373) 
  at java.lang.StringCoding$StringEncoder.encode(StringCoding.java:237) 
  at java.lang.StringCoding.encode(StringCoding.java:272) 
  at java.lang.String.getBytes(String.java:946) 
  at java.io.UnixFileSystem.getBooleanAttributes0(Native Method) 
  .. stack trace truncated ..
4

2 回答 2

3

1)这是因为hadoop提供的FSURLStreamHandlerFactory类的bug。请注意,该错误已在包含此类的最新 jar 中修复。

2) 该文件位于 hadoop-common-2.0.0-cdh4.2.1.jar 中。要完全理解这个问题,我们必须了解 java.net.URL 类是如何工作的。

URL对象的工作

当我们使用其构造函数中的任何一个而不传递“URLStreamHandler”(通过为其值传递 null 或调用不以 URLStreamHandler 对象作为其参数的构造函数)创建一个新 URL 时,它会在内部调用一个名为 getURLStreamHandler() 的方法。该方法返回 URLStreamHandler 对象并设置一个成员

URL 类中的变量。

该对象知道如何构造特定方案的连接,例如“http”、“file”……等等。这个 URLStreamHandler 是由工厂构造的,叫做

URLStreamHandlerFactory。

3) 在上面给出的问题示例中,通过调用以下静态方法将 URLStreamHandlerFactory 设置为“FsUrlStreamHandlerFactory”。

    URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());

因此,当我们创建一个新 URL 时,这个“FSUrlStreamHandlerFactory”用于通过调用它的 createURLStreamHandler(protocol) 方法为这个新 URL 创建 URLStreamHandler 对象。

该方法又调用 FileSystem 类的 loadFileSystems() 方法。loadFileSystems() 方法调用 ServiceLoader.load("FileSystem.class") 因此它尝试通过搜索类路径中所有 jar 文件的所有 META-INF/services/*.FileSystem 文件来读取 FileSystem 实现类的二进制名称,并且阅读其条目。

4) 请记住,每个 jar 都作为 URL 对象处理,这意味着对于每个 jar,一个 URL 对象是由 ClassLoader 在内部创建的。类加载器提供 URLStreamHandler 对象

在为这些 jar 构建 URL 时,这些 URL 不会受到我们设置的“FSUrlStreamHandlerFactory”的影响,因为 URL 已经具有“URLStreamHandler”。既然我们是

处理 jar 文件时,类加载器将“URLStreamHandler”设置为“sun.net.www.protocol.jar.Handler”类型。

5) 现在为了读取文件系统实现类的 jar 文件中的条目,“sun.net.www.protocol.jar.Handler”需要为每个条目构造 URL 对象

在没有 URLStreamHandler 对象的情况下调用 URL 构造函数。由于我们已经将 URLStreamHandlerFactory 定义为“FSUrlStreamHandlerFactory”,因此它调用了 createURLStreamHandler

(协议)方法导致无限递归并导致“StackOverflowException”。

这个错误被 Hadoop 提交者称为“HADOOP-9041”。链接是https://issues.apache.org/jira/browse/HADOOP-9041

我知道这有点复杂。

所以简而言之,这个问题的解决方案如下。

1)使用最新的jar hadoop-common-2.0.0-cdh4.2.1.jar 修复了这个bug

或者

2) 在设置 URLStreamHandlerFactory 之前,将以下语句放入静态块中。

      static {
               FileSystem.getFileSystemClass("file",new Configuration()); 
               URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
             } 

请注意,静态块中的第一条语句现在不依赖于 FsUrlStreamHandlerFactory 并使用 file:// 的默认处理程序来读取 META-INF/services/*.FileSystem 文件中的文件全文。

于 2014-04-22T21:21:48.727 回答
1

我有一个解决方法。

如果有人更熟悉 Hadoop 世界的当前状态(2014 年 1 月)能启发我们和/或解释这种行为,那就太好了。

尝试从 Haddop The Definitive Guide 第三版 Tom White 运行 URLCat 时,我遇到了相同的 StackOverflowError

Cloudera QuickStart 4.4.0 和 4.3.0 有问题

同时使用 jdk1.6.0_32 和 jdk1.6.0_45

问题发生在 java.net.URL 下的 org.apache.hadoop.fs.FileSystem 的初始化/类加载期间有某种递归异常处理正在启动。我尽我所能追踪它。该路径导致 java.util.ServiceLoader 然后调用 sun.misc.CompoundEnumeration.nextElement() 不幸的是,sun.misc.CompoundEnumeration 的源包含在 jdk src.zip 中......也许是一个疏忽,因为它在java包sun.misc

为了尝试通过另一个执行路径触发错误,我想出了一个解决方法......

您可以通过在注册 StreamHandlerFactory 之前调用 org.apache.hadoop.fs.FileSystem.getFileSystemClass(String, Configuration) 来避免导致 StackOverflowError 的条件。

这可以通过修改静态初始化块来完成(参见上面的原始列表):

   static {
        Configuration conf = new Configuration();
        try {
            FileSystem.getFileSystemClass("file", conf);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        };
        URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
    }

这也可以通过将此静态块的内容移动到您的 main() 来完成。

我在 2011 年 8 月的stackoverflow 中使用 FsUrlStreamHandlerFactory找到了对这个错误的另一个引用

我很困惑更多的hadoop新手没有偶然发现这个问题......购买Hadoop书籍......下载Cloudera QuickStart......尝试一个非常简单的例子......失败!?

任何更有经验的人的见解将不胜感激。

于 2014-01-17T23:59:18.800 回答