java 停顿的操作

Python014

java 停顿的操作,第1张

import javax.swing.*

import java.awt.*

import java.awt.event.*

import java.util.concurrent.*

/**

 * @author Hardneedl

 */

final class JPanelDemo extends JFrame {

    public String getTitle() {return "JPanelDemo"}

    static private final Dimension size = new Dimension(600,400)

    public Dimension getPreferredSize() {return size}

    public Dimension getMaximumSize() {return size}

    public Dimension getMinimumSize() {return size}

    public Dimension getSize(){return size}

    private class ChangeVisibilityAction implements ActionListener{

        private JComponent cmp

        private ChangeVisibilityAction(JComponent cmp) {this.cmp=cmp}

        public void actionPerformed(ActionEvent e) {

            cmp.setVisible(false)

            System.out.println("action performed")

        }

    }

    private class StartAction extends AbstractAction{

        private JComponent cmp

        private Timer timer

        public StartAction(JComponent cmp) {super("start")this.cmp=cmp}

        public void actionPerformed(ActionEvent e) {

            if(timer==null){

                timer=new Timer(5000,new ChangeVisibilityAction(cmp)){

                    public boolean isRepeats() {

                        return false

                    }

                }

                timer.start()

            }

        }

    }

    private JPanel panel

    JPanelDemo() throws HeadlessException {

        init()

        attachListeners()

        doLay()

    }

    private void init(){

        panel=new JPanel(){

            public boolean isOpaque() {return true}

            public Color getBackground() {return Color.MAGENTA}

        }

    }

    private void attachListeners(){

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)

    }

    private void doLay(){

        Container container = getContentPane()

        container.add(new JButton(new StartAction(panel)),BorderLayout.NORTH)

        container.add(panel,BorderLayout.CENTER)

        pack()

        setVisible(true)

    }

    public static void main(String[] args) {

        System.setProperty("swing.defaultlaf","com.sun.java.swing.plaf.windows.WindowsLookAndFeel")

        SwingUtilities.invokeLater(

            new Runnable(){

                public void run() {

                    new JPanelDemo()

                }

            }

        )

    }

}

1、jvm中,在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起。此时,系统只能允许GC线程进行运行,其他线程则会全部暂停,等待GC线程执行完毕后才能再次运行。这些工作都是由虚拟机在后台自动发起和自动完成的,是在用户不可见的情况下把用户正常工作的线程全部停下来,这对于很多的应用程序,尤其是那些对于实时性要求很高的程序来说是难以接受的。但不是说GC必须STW(Stop-The-World,全局暂停), 你也可以选择降低运行速度但是可以并发执行的收集算法,这取决于你的业务。

较短的时间停顿是很多企业应用程序运行时最主要的目标,尤其对于一些过长的响应延迟可能会导致事务超时的事务系统。对于运行在JVM上面的一些系统,垃圾回收(GC)有时会造成较长时间的停顿。

在本文中,我会介绍几种我们可能会遇到的GC导致长时间停顿的场景,并分析我们应该如何诊断和排查这些问题。

下面是应用程序运行时可能会导致GC长时间停顿的几种场景。

Java 堆中的碎片可能会引起频繁的GC活动,并且有可能引起GC长时间停顿。这种情况在CMS垃圾收集器并发收集时老年代空间没有启用碎片压缩时出现的可能性更大一点。

CMS垃圾收集器工作时,由于老年代中存在碎片,年轻代收集时会遇到 “promotion failures” 问题,进而引起 “Concurrent Mode Failure” 失败,最终会触发比并发收集耗时更长的 Full GC stop-the-world 行为。

这些碎片可能会导致直接在老年代分配对象失败,因而引起 Full GC ,尽管老年代还有很多剩余空间。频繁的碎片导致对象分配失败会造成频繁的 Full GC ,从而从总体上增加了应用的停顿时间。

下面这段CMS收集器的日志显示出在老年代有大量的碎片,导致新生代回收时出现 “promotion failures” 异常,并且引起CMS回收时 “Concurrent Mode Failure” 。进而引起一次耗时达到17.1365396s的 Full GC

应用程序停顿的时间总共有17.5730653s。

有些时候垃圾回收期间发生的一些系统操作也会引起GC停顿时间变长,比如swap空间交换或者网络活动。这些都有可能导致几秒到几分钟的停顿。

如果你的系统配置了使用swap空间,那么操作系统就会把JVM进程一些不活跃的空间转移到虚拟内存,以便可以释放内存空间给当前进程的活跃线程或系统的其他进程。由于需要操作速度比物理内存慢很多的硬盘,因此swap空间交换是代价非常昂贵的操作。所以,如果在GC期间系统需要swap空间交换,那么GC就会持续更长的时间。

下面是一段新生代GC持续了29.47s的日志:

相应的系统 ‘vmstat’ 命令在03:58输出的日志如下:

这次 Minor GC 持续了约29s。相对应的在此期间系统 ‘vmstat’ 命令输出信息显示系统可用的swap空间减少了差不多600M。这意味着在GC期间一些当前进程非必须的内存页从物理内存中被移到了swap空间。

从上面的信息可以看出,系统的物理内存不够所有运行在系统上面的进程使用。解决的方式就是运行尽量少的进程,同时增加更多的物理内存。上面的日志展示永久代配置的最大使用空间是9G,但是仅有1.8G使用了物理内存。因此有效的解决方式是减少堆空间的大小,减少物理内存的压力,尽量避免或者减少swap空间的交换活动。

除了swap空间交换,我们也要监控在GC期间是否有IO操作或者网络活动。这两项可以使用 ‘iostat’ ‘netstat’ 工具监控。同样使用 ‘mpstat’ 工具输出CPU统计信息观察GC停顿期间是否有可用的CPU资源也非常有用。

如果应用程序占用的空间超过我们为JVM设置的最大堆空间,那么就会导致频繁的GC操作。因为堆空间过小,为对象分配空间的请求失败,JVM就会调用GC操作,释放空间。但是由于每次GC并不能够释放足够的空间,因此越来越多的对象分配失败的请求会引起更多的GC调用。

对应用程序而言, Full GC 会引起更长的应用停顿。如下面的日志所示,因为永久代空间基本已经满了,所以在永久代分配对象的操作失败,触发了一次 Full GC

类似的因老年代剩余空间过小导致的对象在老年代分配失败或者 “promotion failures” 也会触发频繁的 Full GC

这种问题的解决方案就是根据应用平均使用空间的大小合理配置JVM堆的大小。

JVM的一些bug也会引起GC长时间停顿,比如下面列出的一些JVM bug就有可能造成Java程序长时间的GC停顿:

如果你正在运行的JVM版本包含以上bug,请升级到其他修复版本。

请检查一下是否有显示调用系统GC的操作。应用程序或者第三方模块的某些类对 System.gc() 方法的调用会引起 stop-the-world Full GC 操作。这些显示的系统GC调用也会造成长时间停顿。

如果你正在使用RMI框架,并观察到有固定频率的 Full GC 发生,那么就是这些RMI框架的实现类在定时触发 System.gc() 方法调用。触发间隔可以通过下面两个系统配置项配置:

这两个配置项的默认值在JDK 1.4.2版本和5.0版本是60s,在JDK 6及以后的版本中,都是1小时。如果你想禁止因 System.gc() 方法调用引起的 Full GC 操作,可以在应用运行时增加这个 -XX:+DisableExplicitGC JVM参数。

1、收集GC日志时配置上这些参数: -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime 。如果是CMS收集器,再增加 -XX:PrintFLSStatistics=2 这个配置。

这些GC日志可以给我们有关GC停顿的频率和性质的详细信息,即他们可以告诉我们GC停顿是发生在新生代还是老年代,以及停顿发生的频率等相关信息。

2、用Solaris和其他Linux平台上的 ‘iostat’ ‘netstat’ ‘mpstat’ ‘vmstat’ 等系统工具或者Windows操作系统上的 进程监视器 和任务管理器从全局监控系统的运行情况。

3、使用 GCHisto 工具使GC日志可视化,发现哪些GC操作耗时较长,并发现这些GC的一些共性。

4、通过GC日志发现是否在Java堆中存在碎片。

5、监控配置的Java 堆空间是否够应用程序使用。

6、检查你的应用程序是否运行在包含已知的会导致GC长时间停顿的bug的JVM版本。如果是,就升级到新的修复版本。