实例化新类时,代理背后的 Java 小程序会暂时冻结 [英] Java applet behind proxy experiences temporary freezes when instantiating new classes

查看:23
本文介绍了实例化新类时,代理背后的 Java 小程序会暂时冻结的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

We have some problems with our Java applet at our customer's office. The applet is intended for recording the screen by taking screenshots at regular intervals. It's signed and it should run with elevated privileges.

我们遇到的问题是:

  1. Java 小程序有时可能根本无法启动.如果 Java 控制台出现,它可能很快就会消失.感觉好像整个 JVM 都崩溃了.
  2. 如果 Java 小程序启动,它有时会在启动 10 秒后崩溃.
  3. 如果 Java 小程序启动,它几乎总是会遇到长达 12 秒的临时冻结.
  4. (小程序启动有点慢.)

一个典型的情况是,在启动 Windows 后,小程序不会在第一次尝试时加载.刷新/重新登录/重新启动浏览器(我不完全确定需要什么)后,小程序启动,体验冻结然后崩溃.在第三次尝试时,它不会再崩溃,但仍然会冻结.

A typical case is that after booting up Windows the applet won't load on the first attempt. After a refresh/re-login/restarting the browser (I'm not totally sure what's needed) the applet starts, experiences freezes and then crashes. On the third attempt it won't crash anymore but it still experiences freezes.

客户有一个 Blue Coat Systems HTTP 代理,可能与问题有关.网络的设置使所有到 Internet 的流量都必须通过代理.例如,ping www.google.com 会在超时后失败,因为无法解析主机 www.google.com.我找到了一些描述 Java 和 Blue Coat 代理问题的页面.我认为这些信息并没有提供多少有用的信息,但它们加强了我的信念,即代理与问题有关.

The customer has a Blue Coat Systems HTTP proxy that probably has something to do with the problem. The network is set up so that all traffic to the Internet has to go through the proxy. For example, ping www.google.com would fail after a timeout because the host www.google.com couldn't be resolved. I have found some pages that describe problems with Java and Blue Coat proxy. I don't think these provide much helpful information but they strengthen my belief that the proxy has something to do with the problems.

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

The proxy requires NTLM authentication, meaning that the first time the browser tries to go through I get a popup asking for my username and password. Here's part of the HTTP response from the proxy when I tried to access Google using Chrome:

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 中运行了一次小程序(我认为是使用 Oracle Java 1.7.0_07)并且它没有遇到死机,但是小程序截取的屏幕截图可能是空白的或损坏的.

Environment

I was at the customer's premises to experience the problem myself on my own laptop. My setup is that I run Ubuntu Linux 12.04 and Windows 7 in a virtual machine. I used Windows and Java 1.6.35 for most of the tests. I did run the applet once in Linux (using Oracle Java 1.7.0_07 I think) and it didn't experience freezes, but the screenshots that the applet took could be blank or corrupted.

我已取消选中 Java 设置中的在我的计算机上保留临时文件"框,因为缓存的 Java 小程序有时会干扰开发.我在客户办公室调试小程序时禁用了临时文件,但我认为此设置不会影响问题,因为我们的客户可能启用了临时文件并且他们总是遇到这些问题.

I have unchecked the box "Keep temporary files on my computer" in Java settings since cached Java applets sometimes mess up with development. I had the temporary files disabled when I was debugging the applet at the customer's office, but I don't believe this setting affects the problem since our customer probably has temporary files enabled and they experience these problems always.

问题似乎与浏览器无关.我尝试过 IE9、Firefox、Chrome 和 Opera.

The problems don't seem to depend on the browser. I've tried IE9, Firefox, Chrome and Opera.

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

When running the applet in Windows I attached the Eclipse debugger to the applet. I suspended the execution of the applet when it was frozen and checked what it was doing. Here's what the stack looked like. "OurOwnClass.doStillMoreSomething" is the last line in our own code that was executing, and it creating a new instance of a class.

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

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

There were several lines of code where the applet would freeze. The stack would look the same up to the first line that was in our own code (above: OurOwnClass.doStillMoreSomething), so it looked like all the freezes have the same cause. And all our lines causing this problem were creating new objects. I believe in all these cases it was also the first time that a particular class was being loaded, though I didn't check this. One of the classes was our own public class but most of them were anonymous classes with names like OurOwnClass$4 and code like:

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

堆栈顶部的 Java 方法是 Inet6AddressImpl.lookupAllHostAddr.调试器指出此方法正在尝试解析为小程序提供服务的主机的主机名.我想我在某处看到了我们的小程序 JAR 文件的完整 URL,所以我的印象是 JVM 试图再次访问我们的小程序 JAR 文件,即使它应该已经拥有它——它还在执行什么?

The Java method at the top of the stack was Inet6AddressImpl.lookupAllHostAddr. The debugger indicated that this method was trying to resolve the hostname for the host that serves the applet. I think I saw the full URL of our applet JAR file somewhere, so I got the impression that the JVM was trying to access our applet JAR file again even though it should already have had it – what else was it executing?

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

My best guess is that the classloader needs to do some kind of permission checking for the class about to be loaded. Perhaps the JVM is trying to check if the JAR file has been updated since it was initially loaded but the JVM hangs while it's trying to use native methods for resolving the hostname of our server. When the address resolution fails the execution of the classloader returns. All this is a bit odd since the applet can communicate with our server just fine (it gets the proxy information from the browser), but I get the feeling that the JVM doesn't use the proxy for whatever it's doing here. Does this explanation sound plausible?

再说一次,我不知道如何解释崩溃和让小程序首先启动的问题.我没有花时间尝试调试这些问题,所以我不知道发生了什么.如果我以后还去我们客户的办公室,也许我应该尝试查找 hs_err_*.log 文件.

Then again, I don't know how to explain the crashes and the problems getting the applet to start up in the first place. I haven't spent time trying to debug those problems so I don't know what's happening. Maybe I should try to look for hs_err_*.log files if I still go to our customer's office in the future.

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

It would help debugging if I had a similar setup like the customer has. I may try to set up an environment where I can only access the Internet using a HTTP proxy but I don't know if it has to be a Blue Coat proxy. I'm also unsure if I can make the proxy require authentication using the NTLM protocol and not some other method (basic, digest).

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

In any case, I'd appreciate help in getting rid of the freezes (and if possible, the crashes and startup problems). Could we change the code or the JAR package somehow that we could avoid these problems? Are there some JVM startup parameters that could help? Is there still something I should do to investigate the problem?

我更改了我的 Windows 虚拟机的网络设置,使其只能与主机 Linux 通信,而不能与整个 Internet 通信.然后我在主机 Linux 上设置了 Squid HTTP 代理,并告诉 Windows 使用它作为代理.现在我遇到了与在我们客户办公室发生的问题类似的问题,尽管没有那么可靠.好像不需要让代理需要认证.

I changed the network settings for my Windows virtual machine so that it can only communciate with the host Linux, not the whole Internet. Then I set up Squid HTTP proxy on the host Linux and I told Windows to use that as the proxy. Now I get similar problems like the ones that happened at our customer's office, though not as reliably. It seems that I don't need to make the proxy require authentication.

我使用了模拟环境并启动了 Wireshark 以查看 Windows 客户机尝试执行的操作.显然它正在与主机打一种乒乓:它向我们的服务器询问 ip 并得到一个响应目标无法访问(端口无法访问)"(类型 3,代码 3).这会发生五次,它们之间的间隔是 1、1、2 和 4 秒.然后在 4 秒后,applet 继续愉快地做它正在做的任何事情.间隔总和为 12 秒,这恰好是最常见的延迟长度.我不知道我们客户办公室的网络是否响应主机无法访问或其他任何问题.Ping 在那里和在我的这个模拟设置中都会超时.

I used the simulated environment and fired up Wireshark to see what the Windows guest is trying to do. Apparently it's playing a sort of ping-pong with the host: it asks the ip for our server and gets back a response "Destination unreachable (Port unreachable)" (type 3, code 3). This happens five times, and the intervals between them are 1, 1, 2 and 4 secs. Then after 4 seconds the applet goes on happily doing whatever it was doing. The intervals sum up to 12 seconds which happens to be the most common delay length. I don't know if the network at our customer's office responds that the host is unreachable or anything. Ping would timeout both over there and in this simulated setup of mine.

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

I'm wondering if this is a bug or a design error in the JVM. I wouldn't normally expect that instantiating a class freezes the thread for a while. I would also expect the classloader/JVM to use the proxy. Normally I would think that it's meaningful to try the hostname resolution a few times, but I'm not sure if it's the right thing to do in this context.

评论者是对的:这是打乒乓球的操作系统.我通过捕获 ping www.google.com 生成的网络流量进行了尝试,并得到了相同的行为.

A commenter is right: it's the OS playing the ping-pong. I tried this by capturing network traffic generated by ping www.google.com and got the same behavior.

推荐答案

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

Looking at the problematic JRE code in URLStreamHandler:

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.

If this code fails and returns null, the parent hostsEqual method will fall back on String comparison, which is fine. So we want either UnknownHostException or SecurityException.

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

Looks like the firewall is dropping the DNS requests, resulting in a timeout, instead of returning NXDOMAIN (or better still, ICMP administratively prohibited).

你能说服客户正确配置防火墙吗?(您可以通过在 ubuntu 机器上阻止传出 DNS(代理除外)来测试这一点)

Can you persuade the client to configure the firewall correctly? (You can test this by blocking outgoing DNS, except to the proxy, on your ubuntu box)

另一种选择是强制 InetAddress.getByName 抛出 SecurityException,这可能发生:

Another alternative would be to force the SecurityException to be thrown by InetAddress.getByName, which can happen:

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

if a security manager exists and its checkConnect method doesn't allow the operation

我不知道该怎么做,或者如果可能的话 - 你能改变小程序的权限吗?

I don't know how to do this or if it's possible - can you change the permissions of the applet?

这篇关于实例化新类时,代理背后的 Java 小程序会暂时冻结的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆