任何足够先进的科技,皆与魔法无异。 ——阿瑟·克拉克
游客 登录

JVM的DirectMemory设置

几台服务器的JVM占用内存总是持续增长,大大超过-Xmx设定的值,服务器物理内存几乎被耗尽。

使用jmap查看JVM的内存使用,发现jvm的堆大小完全在-Xmx参数设定的范围之内,那问题只能处在别的地方了。

JVM除了堆内存之外,就只有栈内存和DirectMemory了。栈空间每个线程是固定的,线程数也没可能多到可以占用这么多内存的程序,所以怀疑的目标就在DirectMemory上了。

DirectMemory是java nio引入的,直接以native的方式分配内存,不受jvm管理。这种方式是为了提高网络和文件IO的效率,避免多余的内存拷贝而出现的。DirectMemory占用的大小没有直接的工具或者API可以查看,不过这个在Bits类中是有两个字段存储了最大大小和已分配大小的,使用反射可以拿到这个数据:

Class<?> c = Class.forName("java.nio.Bits");
Field maxMemory = c.getDeclaredField("maxMemory");
maxMemory.setAccessible(true);
Field reservedMemory = c.getDeclaredField("reservedMemory");
reservedMemory.setAccessible(true);
Long maxMemoryValue = (Long)maxMemory.get(null);
Long reservedMemoryValue = (Long)reservedMemory.get(null);

结果证实了猜测,DirectMemory增长失控了。

原来,DirectMemory 的默认大小是64M,而JDK6之前和JDK6的某些版本的SUN JVM,存在一个BUG,在用-Xmx设定堆空间大小的时候,也设置了DirectMemory的大小。加入设置了-Xmx2048m,那么jvm最终可分配的内存大小为4G多一些,是预期的两倍。

解决方式是设置jvm参数-XX:MaxDirectMemorySize=128m,指定DirectMemory的大小。

为Process.waitFor设置超时

Java中在使用Runtime.getRuntime().exec(command)调用系统命令后,一般会调用Process.waitFor()来等待命令执行结束,获取执行结果。今天一个悲剧的事实证明了,即使只是调用了很简单的脚本命令,在调用Process.waitFor()后同样可能发生无休止或者接近于无休止的阻塞。处理完故障之后痛定思痛,决定在代码中加入超时控制,但是Process.waitFor()本身并不支持超时时间设置。一个方法是改用非阻塞的Process.exitValue()方法,然后轮询检查进程状态,这种方式比较消耗CPU,以至于轮询间隔也不能设置得太小,总归不是很完美。另外就是多起一个线程,借助于其他的超时机制来控制。最后使用的代码如下:

public class ProcessUtils {
   
    /**
     * 运行一个外部命令,返回状态.若超过指定的超时时间,抛出TimeoutException
     * @param command
     * @param timeout
     * @return
     * @throws IOException
     * @throws InterruptedException
     * @throws TimeoutException
     */
    public static int executeCommand(final String command, final long timeout) throws IOException, InterruptedException, TimeoutException {
        Process process = Runtime.getRuntime().exec(command);
        Worker worker = new Worker(process);
        worker.start();
        try {
            worker.join(timeout);
            if (worker.exit != null){
                return worker.exit;
            } else{
                throw new TimeoutException();
            }
        } catch (InterruptedException ex) {
            worker.interrupt();
            Thread.currentThread().interrupt();
            throw ex;
        } finally {
            process.destroy();
        }
    }
   

    private static class Worker extends Thread {
        private final Process process;
        private Integer exit;

        private Worker(Process process) {
            this.process = process;
        }

        public void run() {
            try {
                exit = process.waitFor();
            } catch (InterruptedException ignore) {
                return;
            }
        }
    }

}

Eclipse3.7的中文字体问题

Eclipse3.7版本的默认中文字体非常的小,如我这般视力良好的人都得把趴到显示器上来看才能看清,原因是其将的默认字体从Courier New改为了Consolas。

Consolas是微软最早随Vista系统发布的等宽字体,专为程序员设计,字体美观而且字形上对0和O,1和l等字符做了很好的区分,用于代码的显示确实非常漂亮。可惜的是Consolas只包含英文字体,中文字体显示仍然由宋体等显示。

解决办法是换一个字体来显示。在此推荐YaHei-Consolas字体,这个是国内Robert Lee制作的一个混合字体,使用雅黑字体显示中文,Consolas显示英文字符。雅黑作为矢量字体,在小像素、粗体、斜体上的显示都非常的好,相当的适合代码显示。

到此下载

下载的字体复制到C:\Windows\Fonts就可以使用了。在eclipse选项中,展开General->Appearance->Colors and Fonts->basic,双击Test Font,然后选择字体:YaHei Consolas Hybrid即可。

附效果图一张:

JDK7新特性之AIO

JDK7中新加入的文件和网络io特性称为nio2(new io 2, 因为jdk1.4中已经有过一个nio了),包含了众多性能和功能上的改进,其中最重要的部分,就是对异步io的支持,称为java aio(asynchronous IO)。

异步io包括文件io和socket io,其中文件异步io各主流OS都提供了原生的支持,异步socket io则并未获得所有操作系统的支持。windows的IOCP,solaris等部分unix系统,已经实现了异步IO模型;而linux的epoll,bsd的kqueue等,仍属于非阻塞多路复用io模型,读写操作是同步的。

jdk在1.4版本的nio中提供了对非阻塞多路复用同步io模型的支持,在linux上基于epoll实现,在bsd上基于kqueue实现,在Windows上则是基于select/poll实现,主要因为io模型的不同,nio无法使用IOCP来实现。jdk1.7中提供对aio的支持后,带来了两方面的好处:

1. Windows上可以使用iocp了。aio在Windows上基于iocp实现,解决了Windows上java nio由于使用较原始的select/poll导致的并发能力低下问题。

windows IOCP是在94年随着NT3.5系统发布的,在17年后java终于提供了支持,这也算是个尴尬的事情吧。如果异步io是由操作系统实现(如IOCP),可以利用硬件和驱动的异步功能,在内核态中调度线程完成数据读写,并且避免了多次内核态数据到用户态数据的拷贝操作,其性能会表现得相当优异。

在linux系统上由于aio是使用epoll来实现,其性能并不会出现提升。

2. 简化了网络变成模型。异步io相比较非阻塞多路复用模型更易理解,开发更为简单。

和多路复用的java nio相比较,可以发现,异步io是在数据读取或者写入调用已经完成的时候,再通知调用者,而非阻塞多路复用io则是在有数据就绪,可以读写的时候通知调用者,读写仍然是由调用者执行并且是阻塞的(这意味着如果要同时进行其他工作,要控制读写操作不能阻塞太长时间或者需要将其放去单独的io线程执行)。

 

JDK7中的java aio新增的类和接口主要有:

AsynchronousServerSocketChannel ,对应于bio中的ServerSocket和nio中的ServerSocketChannel,用于server端的网络程序。

AsynchronousSocketChannel,对云关于bio中的Socket和nio中的SocketChannel,用于client端的网络程序。

CompletionHandler,回调接口,在socket进行accept/connect/read/write等操作时,可以传入一个CompletionHandler的实现,操作执行完毕后,会调用注册的CompletionHandler。

除了CompletionHandler这种回调方式,aio中还支持返回Future对象,使用Future来设定回调操作。

JWebpane for swing在哪里

JavaFx是个悲剧的产品,但是最近发布的2.0beta却有两个算是英明的决定,一个取消对javafx script这种新创造的语言的支持,代之以java语言直接开发JavaFx,一个就是加入了对web渲染组件的支持,即以webkit为基础的JWebPane。

JWebPane最初透露出来只在09年JavaOne,起初传出的消息是JWebPane会先提供对Swing的支持,继而会支持JavaFx。如今对于JavaFx部分的承诺算是兑现了,对Swing的支持却还遥遥无期,恐怕要等到JDK7发布吧——如果不需要等得更久的话。在如今这个时代,还有其他任何一种主流GUI Toolkit不支持WebView控件的吗……甚至连靠谱的第三方方案都不存在。Java在桌面软件领域的失败,这帮搞Swing的人绝对是难辞其咎。

JDK7新特性之G1 GC

Garbage-first garbage collector,简称G1 GC,是最终将用于代替Concurrent Mark-Sweep garbage collector(CMS GC)的新一代垃圾回收器。原本的计划是作为JDK7新特性的一部分发布,但其后JDK7一直在坚持不懈的跳票,G1也无法再等下去了。目前JDK1.6update14及以后版本的jvm中已经继承了G1 GC,可以使用参数-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC来启用。

G1是一个适用于服务器端、大内存、多CPU情景的垃圾收集器,主要目标是在维持高效率回收(high thoughput)的同时,提供软实时中断特性。用户可以指定一个时间上限,如果垃圾回收导致的程序暂停超过了用户设定的时间上限,会打断垃圾回收,恢复程序的执行。

G1的原理在于将堆划分成等一系列大小的区域,每一个区域都有一个对应的remembered set结构,用来记录指向这个区域中的地址的其他区域的指针。 在垃圾回收时,选择记录最少的一个区域进行,按找这种方式选择出来的区域,通常是有用数据最少、垃圾最多的区域,这也就是“Garbage-first ”名称的由来。假如没有外部的指针指向这个区域,就可以直接回收整块区域而不用进行内存Copy,这种情况真是太好了。

以区域为单位的回收,时间粒度更小,这也是实现垃圾回收软实时中断的基础。

G1付出的代价之一是额外的空间占用(用于remembered set)。

附送SUN公司的一篇论文,供想了解更多细节的人观览。

Oracle Coherence

Coherence原名Tangosol,在被Oracle收购之后,改称Oracle Coherence,是一个“企业级”的内存分布式集群缓存框架, 具有无需中央控制、自管理、易扩展的、高可用行的特点。

其官方网站称:Coherence 基于可靠的(reliable)、高度可伸缩(highly scalable)的点对点(peer-to-peer )集群协议,提供了可复制的(replicated )、分布式的(distributed )(分区的(partitioned))数据管理和缓存服务。Coherence 不存在单点故障,当某台服务器无法操作或从网络断开时,它可以自动且透明地进行故障切换并重新分布它的集群化数据管理服务。当新服务器加入或故障服务器重 启时,它会自动加入集群,Coherence 会将服务切回到该服务器,透明地重新分布集群负载。Coherence 包含网络级的容错特性和透明的软重启功能,以支持服务器自我修复。

Coherence同时提供了java、.net、c++版本的实现。使用Coherence java版本的话,仅需要简单的使用get和set方法来访问;一台机器上的jvm把数据交给Coherence缓存之后,集群中的其他机器就可以自动共享访问该数据,并且即使这台机器死掉,缓存中的数据也不会丢失。

Timeout in java network IO

在稳定可靠的网络环境下编写程序的时候,很容易就忘记真实世界有多么险恶。响应缓慢的对方应用程序、恶劣拥堵的网络将使网络连接变得变得速度低下甚至死掉。很少有人愿意花费时间来处理这些网络超时问题,但是忽略这个问题,就会造成应用程序无限期的阻塞。如果是单线程或者使用线程池的程序,阻塞的网络连接最终会卡死运行中的程序;如果为每个链接启动新的线程或者进程,最终会产生大量僵死的并发线程/进程,耗尽系统资源。最近已经见过无数存在这种问题的程序,出现问题的时候可以阻塞在某个网络IO上面有几天甚至几个月时间——直到有人发现然后去重启他们。这些程序通常需要访问一些很慢的站点(比如yahoo us),或者从一些高负载的机器上进行hessian调用。

在Java程序中,处理这种问题还是比较容易的。JDK中的传统Socket接口,在JDK1.1之后提供了用于设置超时的参数,一个在Socket构造函数中指定,用于设置连接超时(connect timeout),用于设定socket connect操作的超时时间;一个用于读取超时(sotimeout),用于设定socket read操作的超时时间。如果在指定的时间内这些方法调用没能完成,就会抛出一个SocketTimeoutException.

JDK或者第三方提供http协议访问类库,作为基于Socket的程序,也都提供了设置设置超时的接口。这些接口通常直接调用了Socket提供的接口。但是JDK中的HttpURLConnection,在1.5版本之后才提供了setConnectTimeout和setReadTimeout用于设置链接超时和读取超时,之前的版本只能自己去修改实现了。

在未指定超时时间时,Socket操作是会无限期阻塞的——这真是个悲剧。而在1.4及之前的jdk中,URLConnection没有提供超时设置的接口,又放大了这个悲剧。JDK1.5之后,为了保持向前兼容,默认情况下URLConnection仍然是无超时设置的,这就需要我们每次涉及到这些操作时,都要有意识的设置超时参数。基于URLConnection的一些其他实现(Webservice, Hessian等)也有同样的问题。

另外,java socket虽然有connect和read(recv)的超时设置,却没有write(send)的超时设置。可以轻松的构造一个阻塞在write上面的client:
Server端代码:
        ServerSocket ss = new ServerSocket(8889);
        Socket s = ss.accept();
        Thread.sleep(100 * 1000);
Client端代码:
        Socket s = new Socket("127.0.0.1", 8889);
        s.setSoTimeout(100);
        s.getOutputStream().write(new byte[1000000]);

如果真有必要处理这样的情况,就需要启动一个定时器线程,在超时之后,把socket close掉了。

论Object.wait()要放到while循环里

wait()放while里面算是一个常识性的准则。为什么要这样呢,如果放到if里面会有什么后果?今天水木有人贴出了一段出错的代码,对这个问题现身说法:

public class A {
        private Object[] queue = new Object[1024];
        private int cMsg;

        public synchronized boolean accept(Object msg, Object token)  {
                if (cMsg >= queue.length) {
                        try {
                                wait();
                        }
                        catch (InterruptedException e) {
                                return false;
                        }
                }

                queue[cMsg++] = token;
                queue[cMsg++] = msg;
                return true;
        }

        public synchronized Object[] getMessages()  {
                if (cMsg == 0) {
                        return null;
                }

                Object[] tmp = (Object[]) Arrays.copyOf(queue, cMsg);
                Arrays.fill(queue, 0, cMsg, null);
                cMsg = 0;
                notify();
                return tmp;
        }
}

这个代码在大并发下测试,抛出了java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 1025异常。

要分析原因的话,就是wait()被唤醒后,队列已经满了,cMsg >= queue.length这个条件已经不满足了,再往后移下标的话就数组越界了。

问题是为什么wait()唤醒后队列会满。在代码里,将队列清空后,才执行notify(),这个时候它应该只唤醒了一个线程,那么谁把队列填满的呢

答案是一个阻塞在accept上面的线程。首先要知道一点:其他线程收到信号并不是在notify调用的那一刻!notify的信号是在退出同步函数后才发出的,从退出同步函数,到信号发出,这中间有个时间差,因而就有可能出现以下执行序列:

1. getMessages方法中的 notify()调用
2. getMessages退出,此线程A释放类实例上的monitor
3. 一个阻塞在accept上的线程B,得以进入accept方法,因为此时数组被清空,线程B填入数据,下表+2;accept不断的调用,直到数组被填满,而阻塞在wait()调用上
4. notify信号发出,一个线程C被唤醒。这时没有再判断数组下标位置,直接想数组中塞数据,数组越界。

解决这个问题的方法当然就是把if改为while:

                while (cMsg >= queue.length) {
                        try {
                                wait();
                        }
                        catch (InterruptedException e) {
                                return false;
                        }
                }

在被唤醒后,重新判断条件是否满足。

如何停止一个超时的线程

经常,我们启动了一组线程,让他们去工作,并等待他们完成,获取他们的返回结果。为了保证程序不会卡死在这些线程的执行上,我们为线程设定了一个超时时间,希望线程如果超过这个时间没有完成,就终止执行。

在线程启动的时候,为其建立一个定时器就可以计算超时时间了。那么,问题就是,如何在一个线程已经超时的时候,停止这个线程的执行。

这是个从线程出现以后就在纠结的问题。

Java最初提供了一个Thread.stop方法用于终止线程,但是这个方法随后就因难以解决的线程安全问题被标记为deprecated,不建议继续使用。Thread.stop的原理是让线程抛出ThreadDeath异常(确切的说,它是一个Error),由于程序都不会捕获这个Error,所以这个线程依次退出调用栈,最终退到栈底而终止。在退栈的过程中,会执行所有的finally代码块,并释放线程持有的所有的锁!看起来这是一个设计相当完美的方案,当初那帮设计者应该会被自己的聪明智慧感动得流泪了吧。然而在实际使用的时候,由于被终止的线程释放了所有的锁,被这些锁保护的对象都变得可以被其他线程所访问,从而引发了意想不到的问题——问题可大可小,可能根本察觉不到,也可能造成莫名其妙的错误。

在剥夺了Thread.stop方法后,JDK转而建议使用共享条件变量来控制线程退出,就好似这样:

Class MyThread extents Thread{

    public volatile boolean stop = false;

    public void run(){
        dosomething1();
        if(stop){
            return;
        }

        dosomething2();
        if(stop){
            return;
        }

        dosomething3();

    }

}

需要停止时只要此线程的stop设为true就行了。这需要小心翼翼的编码,如果在最耗时的操作中间没有对退出标识进行判断,那所有其他的工作也就是白费了。

设置条件变量仍然没有办法处理线程被阻塞的情况(如调用Object.wait()、ServerSocket.accept()和DatagramSocket.receive()等方法时)。一个阻塞中的线程是没法检查条件变量的,它只有等到条件满足,解除阻塞时,才能对条件变量进行判断。这时候可以调用Thread.interrupt方法打破阻塞。Thread.interrupt会使目标线程抛出InterruptedException异常,这个异常通常在代码中被捕获,线程因而跳过阻塞的请求,继续执行。

所以应如下停止一个线程:

MyThread thread = new MyThread();
//wait for some time.
thread.stop = true;
thread.interrupt();

事情还没有完。有些阻塞的线程是不响应Thread.interrupt方法的(例如阻塞在socket.accept() 等旧式IO请求上),Thread.interrupt可以打断的阻塞调用只有Object.wait, Thread.join和Thread.sleep三种。对于这些情况没有通用的处理方法,只能却别对待。例如对于阻塞在socket.accept()的线程,我们可以调用socket.close()来接触阻塞。需要说的是,这种不响应Thread.interrupt的阻塞线程,也不会响应Thread.stop。

综上:多线程是强大的工具,但是也面临很多难以解决的问题,停止正在执行的线程就是其中之一。

共35篇,第1/4页 首页 1 2 3 4 下一页 尾页