代理背后的Java小程序在实例化新类时会遇到临时冻结

时间:2012-10-23 08:04:21

标签: java proxy applet classloader

简介

我们的客户办公室的Java小程序存在一些问题。小程序用于通过定期截取屏幕来记录屏幕。它已签名,应该以提升的权限运行。

我们遇到的问题是:

  1. Java applet有时可能根本无法启动。如果Java控制台出现,它可能很快就会消失。感觉好像整个JVM都崩溃了。
  2. 如果Java applet启动,它有时会在启动后10秒内崩溃。
  3. 如果Java小程序启动,它几乎总会遇到持续时间长达12秒的冻结。
  4. (小程序启动有点慢。)
  5. 典型的情况是,在启动Windows后,applet在第一次尝试时不会加载。刷新/重新登录/重新启动浏览器后(我不完全确定需要什么)applet启动,遇到冻结然后崩溃。在第三次尝试时它不再崩溃,但它仍然会冻结。

    背景

    客户有一个Blue Coat Systems HTTP代理可能与该问题有关。设置网络,以便所有到Internet的流量都必须通过代理。例如,ping www.google.com在超时后会失败,因为主机www.google.com无法解决。我找到了一些描述Java和Blue Coat代理问题的页面。我不认为这些提供了很多有用的信息,但他们更加坚信代理与问题有关。

    代理需要NTLM身份验证,这意味着浏览器第一次尝试通过时会弹出一个询问我的用户名和密码的弹出窗口。当我尝试使用Chrome访问Google时,以下是来自代理的HTTP响应的一部分:

    HTTP/1.1 407 Proxy Authentication Required
    Proxy-Authenticate: NTLM TlRMTVNTUAACAAAADAAMADgAAAAFgokC/+XiO/tpmQMAAAAAAAAAAKQApABEAAAABQLODgAAAA9KAEEATQBQAFQASQACAAwASgBBAE0AUABUAEkAAQASAFAASwBIAEsASQBEAEMAMAAxAAQAHgBwAHUAdQBrAGUAcwBrAHUAcwAuAGwAbwBjAGEAbAADADIAcABrAGgAawBpAGQAYwAwADEALgBwAHUAdQBrAGUAcwBrAHUAcwAuAGwAbwBjAGEAbAAFAB4AcAB1AHUAawBlAHMAawB1AHMALgBsAG8AYwBhAGwAAAAAAA==
    

    环境

    我在客户的场所自己在自己的笔记本电脑上体验这个问题。我的设置是我在虚拟机中运行Ubuntu Linux 12.04和Windows 7。我在大多数测试中使用了Windows和Java 1.6.35。我确实在Linux上运行了一次applet(我认为使用的是Oracle Java 1.7.0_07)并且它没有经历过冻结,但是applet所采取的屏幕截图可能是空白的或已损坏。

    我取消选中该框"将临时文件保存在我的计算机上"在Java设置中,因为缓存的Java applet有时会搞乱开发。我在客户办公室调试applet时禁用了临时文件,但我不相信这个设置会影响问题,因为我们的客户可能启用了临时文件,并且他们总是遇到这些问题。 / p>

    这些问题似乎并不依赖于浏览器。我尝试过IE9,Firefox,Chrome和Opera。

    观察

    在Windows中运行applet时,我将Eclipse调试器附加到applet。当applet被冻结时我暂停了applet的执行并检查了它在做什么。这是堆栈的样子。 " OurOwnClass.doStillMoreSomething"是我们自己的代码中正在执行的最后一行,它创建了一个新的类实例。

    Thread [AWT-EventQueue-2] (Suspended)
        Inet6AddressImpl.lookupAllHostAddr(String) line: not available [native method]
        InetAddress$1.lookupAllHostAddr(String) line: not available
        InetAddress.getAddressFromNameService(String, InetAddress) line: not available
        InetAddress.getAllByName0(String, InetAddress, boolean) line: not available
        InetAddress.getAllByName(String, InetAddress) line: not available
        InetAddress.getAllByName(String) line: not available
        InetAddress.getByName(String) line: not available
        Handler(URLStreamHandler).getHostAddress(URL) line: not available
        Handler(URLStreamHandler).hostsEqual(URL, URL) line: not available
        Handler(URLStreamHandler).sameFile(URL, URL) line: not available
        Handler(URLStreamHandler).equals(URL, URL) line: not available
        URL.equals(Object) line: not available
        JarVerifier$VerifierCodeSource(CodeSource).equals(Object) line: not available
        JarVerifier$VerifierCodeSource.equals(Object) line: not available
        HashMap<K,V>.getEntry(Object) line: not available
        HashMap<K,V>.containsKey(Object) line: not available
        HashSet<E>.contains(Object) line: not available
        CPCallbackHandler.isTrusted(CodeSource) line: not available
        CPCallbackHandler.access$1200(CPCallbackHandler, CodeSource) line: not available
        CPCallbackHandler$ChildElement.checkResource(String) line: not available
        DeployURLClassPath$JarLoader.checkResource(String, boolean, JarEntry, JarFile, DeployURLClassPath$PathIterator) line: not available
        DeployURLClassPath$JarLoader.getResource(String, boolean, DeployURLClassPath$PathIterator) line: not available
        DeployURLClassPath.getResource(String, boolean) line: not available
        Plugin2ClassLoader$2.run() line: not available
        AccessController.doPrivileged(PrivilegedExceptionAction<T>, AccessControlContext) line: not available [native method]
        Applet2ClassLoader(Plugin2ClassLoader).findClassHelper(String) line: not available
        Applet2ClassLoader.findClass(String, boolean) line: not available
        Applet2ClassLoader(Plugin2ClassLoader).loadClass0(String, boolean, boolean) line: not available
        Applet2ClassLoader(Plugin2ClassLoader).loadClass(String, boolean, boolean) line: not available
        Applet2ClassLoader(Plugin2ClassLoader).loadClass(String, boolean) line: not available
        Applet2ClassLoader(ClassLoader).loadClass(String) line: not available
        OurOwnClass.doStillMoreSomething(String) line: 701
        OurOwnClass.doMoreSomething() line: 671
        OurOwnClass.doSometihng() line: 655
        OurOwnClass.handleTimer() line: 510
        OurOwnClass.actionPerformed(ActionEvent) line: 391
        Timer.fireActionPerformed(ActionEvent) line: not available
        Timer$DoPostEvent.run() line: not available
        InvocationEvent.dispatch() line: not available
        EventQueue.dispatchEventImpl(AWTEvent, Object) line: not available
        EventQueue.access$400(EventQueue, AWTEvent, Object) line: not available
        EventQueue$2.run() line: not available
        EventQueue$2.run() line: not available
        AccessController.doPrivileged(PrivilegedAction<T>, AccessControlContext) line: not available [native method]
        AccessControlContext$1.doIntersectionPrivilege(PrivilegedAction<T>, AccessControlContext, AccessControlContext) line: not available
        EventQueue.dispatchEvent(AWTEvent) line: not available
        EventDispatchThread.pumpOneEventForFilters(int) line: not available
        EventDispatchThread.pumpEventsForFilter(int, Conditional, EventFilter) line: not available
        EventDispatchThread.pumpEventsForHierarchy(int, Conditional, Component) line: not available
        EventDispatchThread.pumpEvents(int, Conditional) line: not available
        EventDispatchThread.pumpEvents(Conditional) line: not available
        EventDispatchThread.run() line: not available
    

    applet会冻结几行代码。堆栈看起来与我们自己的代码中的第一行相同(上图:OurOwnClass.doStillMoreSomething),因此看起来所有的冻结都有相同的原因。而导致这个问题的所有线路都在创建新对象。我相信在所有这些情况下,它也是第一次加载特定的类,但我没有检查这一点。其中一个类是我们自己的公共类,但大多数是名为OurOwnClass $ 4的匿名类,代码如下:

    worker = new SwingWorker<Void,Void>() {
        @Override
        public Void doInBackground() {
            // Do stuff.
            return null;
        }
    };
    

    堆栈顶部的Java方法是Inet6AddressImpl.lookupAllHostAddr。调试器指示此方法正在尝试解析为applet提供服务的主机的主机名。我想我在某个地方看到了我们的applet JAR文件的完整URL,所以我得到的印象是JVM试图再次访问我们的applet JAR文件,即使它已经有了它 - 它还在执行什么呢?

    分析

    我最好的猜测是类加载器需要对要加载的类进行某种权限检查。也许JVM正在尝试检查JAR文件自最初加载以来是否已更新,但JVM在尝试使用本机方法解析服务器主机名时挂起。当地址解析失败时,类加载器的执行返回。所有这一切都有点奇怪,因为applet可以很好地与我们的服务器通信(它从浏览器获取代理信息),但我感觉JVM没有使用代理来做任何事情。在这里做这个解释听起来有道理吗?

    然后,我再也不知道如何解释崩溃以及让applet首先启动的问题。我没有花时间尝试调试这些问题,所以我不知道发生了什么。如果我以后仍然去我们客户的办公室,也许我应该尝试寻找hs_err _ * .log文件。

    如果我有类似客户的设置,这将有助于调试。我可能会尝试建立一个只能使用HTTP代理访问Internet的环境,但我不知道它是否必须是Blue Coat代理。我也不确定是否可以使用NTLM协议使代理需要身份验证,而不是其他方法(基本,摘要)。

    在任何情况下,我都很感激帮助摆脱冻结(如果可能的话,崩溃和启动问题)。我们可以以某种方式更改代码或JAR包,以避免这些问题吗?是否有一些JVM启动参数可以提供帮助?我还有什么办法可以调查这个问题吗?

    修改

    我更改了我的Windows虚拟机的网络设置,以便它只能与主机Linux通信,而不是整个互联网。然后我在主机Linux上设置了Squid HTTP代理,我告诉Windows将其用作代理。现在我遇到了类似于我们客户办公室的问题,尽管不是那么可靠。似乎我不需要让代理需要身份验证。

    编辑2

    我使用模拟环境并启动Wireshark以查看Windows guest虚拟机正在尝试执行的操作。显然,它正在与主机进行一种乒乓球运行:它向ip询问我们的服务器并获得响应&#34; Destination unreachable(Port unreachable)&#34; (类型3,代码3)。这发生了五次,它们之间的间隔是1,1,2和4秒。然后在4秒后,applet继续愉快地做它正在做的事情。间隔总计达12秒,恰好是最常见的延迟长度。我不知道客户办公室的网络是否响应主机无法访问或任何事情。 Ping会在那里以及我的模拟设置中超时。

    我想知道这是JVM中的错误还是设计错误。我通常不希望实例化一个类冻结线程一段时间。我还希望classloader / JVM使用代理。通常情况下,我认为尝试主机名解析几次是有意义的,但我不确定在这种情况下做对是否正确。

    编辑3

    评论者是对的:它是打乒乓球的操作系统。我通过捕获ping www.google.com生成的网络流量并尝试相同的行为来尝试此操作。

2 个答案:

答案 0 :(得分:2)

查看URLStreamHandler中有问题的JRE代码:

protected synchronized InetAddress getHostAddress(URL u) {
    if (u.hostAddress != null)
        return u.hostAddress;

    String host = u.getHost(); 
    if (host == null || host.equals("")) {
        return null;
    } else {
        try {
            u.hostAddress = InetAddress.getByName(host); // Hanging here
        } catch (UnknownHostException ex) {
            return null;
        } catch (SecurityException se) {
            return null;
        }
    }
    return u.hostAddress;
}

如果此代码失败并返回null,则父hostsEqual方法将返回String比较,这很好。我们要UnknownHostExceptionSecurityException

看起来防火墙正在丢弃DNS请求,导致超时,而不是返回NXDOMAIN(或者更好的是,ICMP在管理上被禁止)。

您能说服客户端正确配置防火墙吗? (您可以通过阻止传出的DNS来测试,除了代理,在您的ubuntu框上)

另一个替代方法是强制SecurityException InetAddress.getByName被{{1}}抛出,这可能会发生:

  

如果存在安全管理器且其checkConnect方法不允许   操作

我不知道该怎么做或者是否有可能 - 你能改变小程序的权限吗?

答案 1 :(得分:0)

我们遇到了同样的冻结问题。我们的applet应用程序曾经间歇性冻结的地方。 根本原因是“虚假重新传输”,在TCP级别缺少某些数据包序列号。它与第4层有关。

此问题的解决方法是,在apache config条目下覆盖下一个。 原始值为:

SetEnvIf User-Agent“。 MSIE。” \ nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0

我们通过在openshift config参数的apache_config中写入此值来覆盖这些值。

 SetEnvIf用户代理“。 MSIE。” \  !nokeepalive!ssl-unclean-shutdown \  !downgrade-1.0!force-response-1.0

HTTP 1.1使用特定的标头在客户端完成数据传输时向客户端发送信号,而HTTP 1.0中不存在