Pergunta

Às vezes eu vejo muitas aplicações como o MSN, Windows media player, etc, que são aplicações de instância única (quando executa usuário enquanto a aplicação está em execução uma nova instância do aplicativo não será criado).

Em C #, eu uso classe Mutex para isso, mas eu não sei como fazer isso em Java.

Foi útil?

Solução

Se Eu acredito que este artigo , por:

tendo a primeira tentativa exemplo, para abrir um socket de escuta na interface localhost. Se ele é capaz de abrir o socket, presume-se que esta é a primeira instância do aplicativo para ser lançado. Se não, o pressuposto é que uma instância deste aplicativo já está em execução. A nova instância deverá notificar a instância existente que o lançamento foi tentada, em seguida, sair. A instância existente assume depois de receber a notificação e dispara um evento para o ouvinte que lida com a ação.

Nota: Ahe menciona no comentário que o uso InetAddress.getLocalHost() pode ser complicado:

  • ele não funciona como esperado no DHCP-ambiente, porque endereço retornado depende se o computador tem acesso à rede.
    Solução foi a conexão aberta com InetAddress.getByAddress(new byte[] {127, 0, 0, 1}) ;
    Provavelmente relacionado com bug 4.435.662 .
  • Eu também achei bug 4.665.037 que os relatórios que os resultados esperados de getLocalHost: endereço IP retorno da máquina, vs. Os resultados reais: retorno 127.0.0.1.

é surpreendente ter getLocalHost retorno 127.0.0.1 em Linux, mas não no Windows.


Ou você pode usar ManagementFactory objeto. Como explicado aqui :

O método getMonitoredVMs(int processPid) recebe como parâmetro o aplicativo atual PID, e pegar o nome do aplicativo que é chamado de linha de comando, por exemplo, o aplicativo foi iniciado a partir de caminho c:\java\app\test.jar, então a variável valor é "c:\\java\\app\\test.jar". Desta forma, vamos pegar o nome do aplicativo apenas na linha 17 do código abaixo.
Depois disso, buscamos JVM para outro processo com o mesmo nome, se encontrou e a aplicação PID é diferente, isso significa que é a segunda instância do aplicativo.

ofertas JNLP também um SingleInstanceListener

Outras dicas

Eu uso o seguinte método no método principal. Este é o mais simples, mais robusto e menos intrusiva método que eu vi, então eu pensei que eu iria partilhá-la.

private static boolean lockInstance(final String lockFile) {
    try {
        final File file = new File(lockFile);
        final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
        final FileLock fileLock = randomAccessFile.getChannel().tryLock();
        if (fileLock != null) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    try {
                        fileLock.release();
                        randomAccessFile.close();
                        file.delete();
                    } catch (Exception e) {
                        log.error("Unable to remove lock file: " + lockFile, e);
                    }
                }
            });
            return true;
        }
    } catch (Exception e) {
        log.error("Unable to create and/or lock file: " + lockFile, e);
    }
    return false;
}

Se o aplicativo. tem um GUI, lançá-lo com JWS e usar o SingleInstanceService. Veja a demonstração. do SingleInstanceService para (demo. e) exemplo de código.

Sim, esta é uma resposta realmente decente para eclipse RCP eclipse aplicativo de instância única Abaixo está o meu código

em application.java

if(!isFileshipAlreadyRunning()){
        MessageDialog.openError(display.getActiveShell(), "Fileship already running", "Another instance of this application is already running.  Exiting.");
        return IApplication.EXIT_OK;
    } 


private static boolean isFileshipAlreadyRunning() {
    // socket concept is shown at http://www.rbgrn.net/content/43-java-single-application-instance
    // but this one is really great
    try {
        final File file = new File("FileshipReserved.txt");
        final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
        final FileLock fileLock = randomAccessFile.getChannel().tryLock();
        if (fileLock != null) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    try {
                        fileLock.release();
                        randomAccessFile.close();
                        file.delete();
                    } catch (Exception e) {
                        //log.error("Unable to remove lock file: " + lockFile, e);
                    }
                }
            });
            return true;
        }
    } catch (Exception e) {
       // log.error("Unable to create and/or lock file: " + lockFile, e);
    }
    return false;
}

Nós usamos o bloqueio de arquivos para isso (pegar um bloqueio exclusivo em um arquivo de magia no diretório de dados do aplicativo do usuário), mas estamos interessados ??principalmente na prevenção de várias instâncias de sempre em execução.

Se você está tentando ter o segundo args linha de comando exemplo de passe, etc ... para a primeira instância, em seguida, usando uma conexão de soquete em localhost será matar dois pássaros com uma pedra. algoritmo geral:

  • No lançamento, tentar ouvinte aberto no XXXX porta em localhost
  • Se falhar, abra um escritor a essa porta em localhost e enviar os argumentos de linha de comando, então desligamento
  • Caso contrário, escutar em XXXXX porta em localhost. Quando receber argumentos de linha de comando, processá-los como se o aplicativo foi lançado com essa linha de comando.

Eu encontrei uma solução, uma explicação desenho animado pouco, mas ainda funciona na maioria dos casos. Ele usa o arquivo de bloqueio velho liso criando coisas, mas em uma visão bem diferente:

http://javalandscape.blogspot.com/ 2008/07 / single-instance-de-seu-application.html

Eu acho que vai ser uma ajuda para aqueles com uma configuração de firewall estrito.

Você pode usar a biblioteca JUnique. Ele fornece suporte para a execução de aplicativos Java de instância única e é open-source.

http://www.sauronsoftware.it/projects/junique/

A biblioteca JUnique pode ser usado para impedir que um usuário para executar, ao mesmo tempo mais instâncias do mesmo aplicativo Java.

JUnique implementos fechaduras e canais de comunicação compartilhada entre todos as instâncias JVM lançado pelo mesmo usuário.

public static void main(String[] args) {
    String appId = "myapplicationid";
    boolean alreadyRunning;
    try {
        JUnique.acquireLock(appId, new MessageHandler() {
            public String handle(String message) {
                // A brand new argument received! Handle it!
                return null;
            }
        });
        alreadyRunning = false;
    } catch (AlreadyLockedException e) {
        alreadyRunning = true;
    }
    if (!alreadyRunning) {
        // Start sequence here
    } else {
        for (int i = 0; i < args.length; i++) {
            JUnique.sendMessage(appId, args[0]));
        }
    }
}

Sob o capô, ele cria arquivo de fechaduras pasta junique em% USER_DATA% /. E cria um soquete de servidor na porta aleatória para cada appId única que permite o envio / recebimento de mensagens entre aplicações Java.

No Windows, você pode usar launch4j .

classe ManagementFactory suportado em J2SE 5.0 ou posterior detalhe

mas agora eu uso J2SE 1.4 e eu achei este http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/ mas Eu nunca testar. O que você acha sobre isso?

Você pode tentar usar a API de Preferências. É independente de plataforma.

A forma mais genérica de limitar o número de instância de em uma única máquina, ou até mesmo uma rede inteira, é usar uma tomada de multicast.

Usando um soquete multicast, permite-lhe transmitir uma mensagem para qualquer quantidade de instâncias do seu aplicativo, alguns dos quais podem ser em máquinas fisicamente remotas através de uma rede corporativa.

Desta forma, você pode permitir que muitos tipos de configurações, para controlar as coisas como

  • uma ou várias instâncias por máquina
  • uma ou várias instâncias por rede (por exemplo, controladores instala em um site do cliente)

suporte a multicast de Java é através de java.net pacote com MulticastSocket & DatagramSocket sendo as principais ferramentas.

Nota : de MulticastSocket não garantem a entrega de pacotes de dados, então você deve usar uma ferramenta construída em cima de soquetes multicast como JGroups . JGroups não entrega garantia de todos os dados. É um único arquivo jar, com uma API muito simples.

JGroups tem sido em torno de um tempo, e tem alguns usos impressionantes na indústria, por exemplo, sustenta mecanismo de agrupamento do JBoss que os dados transmitidos para todas as instâncias de um cluster.

Para usar JGroups, para limitar o número de instâncias de um aplicativo (em uma máquina ou uma rede, vamos dizer: para o número de licenças que um cliente tenha comprado) é conceitualmente muito simples:

  • Na inicialização de sua aplicação, cada instância tenta se juntar a um grupo chamado por exemplo, "Meu Grande App Grupo". Você vai ter configurado este grupo para permitir a 0, 1 ou membros n
  • Quando a contagem de membro do grupo é maior do que o que você configurou para ele .. seu aplicativo deve recusar-se a iniciar-se.

Você pode abrir uma memória mapeada arquivo e depois ver se esse arquivo está aberto já. se ele já está aberto, você pode retornar a partir principal.

Outras formas é usar arquivos de bloqueio (prática padrão UNIX). mais é uma maneira de colocar algo na área de transferência quando principais começa depois de verificar se algo já está na área de transferência.

Caso contrário, você pode abrir um socket em uma escuta modo (ServerSocket). Primeiro tentar se conectar a hte tomada; se você não consegue se conectar, em seguida, abrir um ServerSocket. se você se conectar, então você sabe que outra instância já está em execução.

Assim, praticamente qualquer recurso do sistema pode ser usado para saber que um aplicativo está em execução.

BR, ~ A

Eu usei soquetes para que e dependendo se o aplicativo está no lado do lado do cliente ou servidor o comportamento é um pouco diferente:

    lado do cliente
  • : se uma instância já existe (não posso escutar uma porta específica) Vou passar os parâmetros de aplicação e de saída (você pode querer executar algumas ações na instância anterior) se não vou iniciar o aplicativo <. / li>
  • lado do servidor:. Se uma instância já existe vou imprimir uma mensagem e sair, se não vou começar a aplicação
public class SingleInstance {
    public static final String LOCK = System.getProperty("user.home") + File.separator + "test.lock";
    public static final String PIPE = System.getProperty("user.home") + File.separator + "test.pipe";
    private static JFrame frame = null;

    public static void main(String[] args) {
        try {
            FileChannel lockChannel = new RandomAccessFile(LOCK, "rw").getChannel();
            FileLock flk = null; 
            try {
                flk = lockChannel.tryLock();
            } catch(Throwable t) {
                t.printStackTrace();
            }
            if (flk == null || !flk.isValid()) {
                System.out.println("alread running, leaving a message to pipe and quitting...");
                FileChannel pipeChannel = null;
                try {
                    pipeChannel = new RandomAccessFile(PIPE, "rw").getChannel();
                    MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1);
                    bb.put(0, (byte)1);
                    bb.force();
                } catch (Throwable t) {
                    t.printStackTrace();
                } finally {
                    if (pipeChannel != null) {
                        try {
                            pipeChannel.close();
                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                    } 
                }
                System.exit(0);
            }
            //We do not release the lock and close the channel here, 
            //  which will be done after the application crashes or closes normally. 
            SwingUtilities.invokeLater(
                new Runnable() {
                    public void run() {
                        createAndShowGUI();
                    }
                }
            );

            FileChannel pipeChannel = null;
            try {
                pipeChannel = new RandomAccessFile(PIPE, "rw").getChannel();
                MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1);
                while (true) {
                    byte b = bb.get(0);
                    if (b > 0) {
                        bb.put(0, (byte)0);
                        bb.force();
                        SwingUtilities.invokeLater(
                            new Runnable() {
                                public void run() {
                                    frame.setExtendedState(JFrame.NORMAL);
                                    frame.setAlwaysOnTop(true);
                                    frame.toFront();
                                    frame.setAlwaysOnTop(false);
                                }
                            }
                        );
                    }
                    Thread.sleep(1000);
                }
            } catch (Throwable t) {
                t.printStackTrace();
            } finally {
                if (pipeChannel != null) {
                    try {
                        pipeChannel.close();
                    } catch (Throwable t) {
                        t.printStackTrace();
                    } 
                } 
            }
        } catch(Throwable t) {
            t.printStackTrace();
        } 
    }

    public static void createAndShowGUI() {

        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800, 650);
        frame.getContentPane().add(new JLabel("MAIN WINDOW", 
                    SwingConstants.CENTER), BorderLayout.CENTER);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

Editar : Em vez de usar essa abordagem WatchService, um simples 1 segundo segmento temporizador pode ser usado para verificar se os indicatorFile.exists (). Excluí-lo, em seguida, trazer o toFront aplicativo ().

Editar : Eu gostaria de saber por que isso foi downvoted. É a melhor solução que tenho visto até agora. Por exemplo. a abordagem tomada servidor falhar se outro aplicativo acontece já estar ouvindo a porta.

Basta fazer o download do Microsoft Windows Sysinternals TCPView (ou uso netstat) , iniciá-lo, ordenar por "Estado", olhar para o bloco de linha que diz "ouvir", escolher aquele cujo endereço remoto diz o nome do seu computador, colocar essa porta em seu novo socket () - solução. Em minha implementação do mesmo, eu posso produzir fracasso de cada vez. E é lógica , porque é o próprio fundamento da abordagem. Ou o que não estou recebendo a respeito de como implementar isso?

Por favor me informar se e como eu estou errado sobre isso!

A minha opinião - o que eu estou pedindo para você refutar se possível - é que os desenvolvedores estão sendo aconselhados a usar uma abordagem no código de produção que irá falhar em pelo menos 1 de cerca de 60000 casos. E se essa visão passa a ser correto, então ele pode absolutamente não ser que a solução apresentada que não tem este problema é downvoted e criticado por sua quantidade de código.

As desvantagens da abordagem tomada em comparação:

  • falhará se o bilhete de loteria errado (número da porta) é escolhido.
  • Falha em ambiente multi usuário: Somente um usuário pode executar o aplicativo ao mesmo tempo. (Minha abordagem teria de ser ligeiramente alterado para criar o arquivo (s) na árvore usuário, mas isso é trivial.)
  • falha se as regras de firewall são muito rigorosos.
  • Faz usuários suspeitos (que eu fiz se encontram em estado selvagem) é de admirar que travessuras que você é até quando o seu editor de texto está reivindicando um socket servidor.

Eu só tinha uma boa idéia de como resolver a nova instância-to-instância existente problema de comunicação Java de uma forma que deve funcionar em todos os sistemas. Então, eu chicoteado até esta classe em cerca de duas horas. Funciona como um encanto: D

É baseado em Robert 's abordagem bloqueio de arquivo (também nesta página), que eu tenho usado desde então. Para dizer a instância já em execução que outra instância tentou iniciar (mas não o fez) ... um arquivo é criado e imediatamente eliminada, ea primeira instância usa o WatchService para detectar esta mudança do conteúdo da pasta. Eu não posso acreditar que, aparentemente, esta é uma idéia nova, dado quão fundamental é o problema.

Isso pode facilmente ser alterado para apenas criar e não excluir o arquivo e, em seguida, as informações podem ser colocados em que a instância adequada pode avaliar, por exemplo, os argumentos de linha de comando - e a instância adequada pode, em seguida, executar a exclusão. Pessoalmente, eu só precisava de saber quando para restaurar a janela do meu aplicativo e enviá-lo para a frente.

Exemplo de uso:

public static void main(final String[] args) {

    // ENSURE SINGLE INSTANCE
    if (!SingleInstanceChecker.INSTANCE.isOnlyInstance(Main::otherInstanceTriedToLaunch, false)) {
        System.exit(0);
    }

    // launch rest of application here
    System.out.println("Application starts properly because it's the only instance.");
}

private static void otherInstanceTriedToLaunch() {
    // Restore your application window and bring it to front.
    // But make sure your situation is apt: This method could be called at *any* time.
    System.err.println("Deiconified because other instance tried to start.");
}

Aqui está a classe:

package yourpackagehere;

import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.nio.file.*;




/**
 * SingleInstanceChecker v[(2), 2016-04-22 08:00 UTC] by dreamspace-president.com
 * <p>
 * (file lock single instance solution by Robert https://stackoverflow.com/a/2002948/3500521)
 */
public enum SingleInstanceChecker {

    INSTANCE; // HAHA! The CONFUSION!


    final public static int POLLINTERVAL = 1000;
    final public static File LOCKFILE = new File("SINGLE_INSTANCE_LOCKFILE");
    final public static File DETECTFILE = new File("EXTRA_INSTANCE_DETECTFILE");


    private boolean hasBeenUsedAlready = false;


    private WatchService watchService = null;
    private RandomAccessFile randomAccessFileForLock = null;
    private FileLock fileLock = null;


    /**
     * CAN ONLY BE CALLED ONCE.
     * <p>
     * Assumes that the program will close if FALSE is returned: The other-instance-tries-to-launch listener is not
     * installed in that case.
     * <p>
     * Checks if another instance is already running (temp file lock / shutdownhook). Depending on the accessibility of
     * the temp file the return value will be true or false. This approach even works even if the virtual machine
     * process gets killed. On the next run, the program can even detect if it has shut down irregularly, because then
     * the file will still exist. (Thanks to Robert https://stackoverflow.com/a/2002948/3500521 for that solution!)
     * <p>
     * Additionally, the method checks if another instance tries to start. In a crappy way, because as awesome as Java
     * is, it lacks some fundamental features. Don't worry, it has only been 25 years, it'll sure come eventually.
     *
     * @param codeToRunIfOtherInstanceTriesToStart Can be null. If not null and another instance tries to start (which
     *                                             changes the detect-file), the code will be executed. Could be used to
     *                                             bring the current (=old=only) instance to front. If null, then the
     *                                             watcher will not be installed at all, nor will the trigger file be
     *                                             created. (Null means that you just don't want to make use of this
     *                                             half of the class' purpose, but then you would be better advised to
     *                                             just use the 24 line method by Robert.)
     *                                             <p>
     *                                             BE CAREFUL with the code: It will potentially be called until the
     *                                             very last moment of the program's existence, so if you e.g. have a
     *                                             shutdown procedure or a window that would be brought to front, check
     *                                             if the procedure has not been triggered yet or if the window still
     *                                             exists / hasn't been disposed of yet. Or edit this class to be more
     *                                             comfortable. This would e.g. allow you to remove some crappy
     *                                             comments. Attribution would be nice, though.
     * @param executeOnAWTEventDispatchThread      Convenience function. If false, the code will just be executed. If
     *                                             true, it will be detected if we're currently on that thread. If so,
     *                                             the code will just be executed. If not so, the code will be run via
     *                                             SwingUtilities.invokeLater().
     * @return if this is the only instance
     */
    public boolean isOnlyInstance(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {

        if (hasBeenUsedAlready) {
            throw new IllegalStateException("This class/method can only be used once, which kinda makes sense if you think about it.");
        }
        hasBeenUsedAlready = true;

        final boolean ret = canLockFileBeCreatedAndLocked();

        if (codeToRunIfOtherInstanceTriesToStart != null) {
            if (ret) {
                // Only if this is the only instance, it makes sense to install a watcher for additional instances.
                installOtherInstanceLaunchAttemptWatcher(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread);
            } else {
                // Only if this is NOT the only instance, it makes sense to create&delete the trigger file that will effect notification of the other instance.
                //
                // Regarding "codeToRunIfOtherInstanceTriesToStart != null":
                // While creation/deletion of the file concerns THE OTHER instance of the program,
                // making it dependent on the call made in THIS instance makes sense
                // because the code executed is probably the same.
                createAndDeleteOtherInstanceWatcherTriggerFile();
            }
        }

        optionallyInstallShutdownHookThatCleansEverythingUp();

        return ret;
    }


    private void createAndDeleteOtherInstanceWatcherTriggerFile() {

        try {
            final RandomAccessFile randomAccessFileForDetection = new RandomAccessFile(DETECTFILE, "rw");
            randomAccessFileForDetection.close();
            Files.deleteIfExists(DETECTFILE.toPath()); // File is created and then instantly deleted. Not a problem for the WatchService :)
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private boolean canLockFileBeCreatedAndLocked() {

        try {
            randomAccessFileForLock = new RandomAccessFile(LOCKFILE, "rw");
            fileLock = randomAccessFileForLock.getChannel().tryLock();
            return fileLock != null;
        } catch (Exception e) {
            return false;
        }
    }


    private void installOtherInstanceLaunchAttemptWatcher(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {

        // PREPARE WATCHSERVICE AND STUFF
        try {
            watchService = FileSystems.getDefault().newWatchService();
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        final File appFolder = new File("").getAbsoluteFile(); // points to current folder
        final Path appFolderWatchable = appFolder.toPath();


        // REGISTER CURRENT FOLDER FOR WATCHING FOR FILE DELETIONS
        try {
            appFolderWatchable.register(watchService, StandardWatchEventKinds.ENTRY_DELETE);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }


        // INSTALL WATCHER THAT LOOKS IF OUR detectFile SHOWS UP IN THE DIRECTORY CHANGES. IF THERE'S A CHANGE, ANOTHER INSTANCE TRIED TO START, SO NOTIFY THE CURRENT ONE OF THAT.
        final Thread t = new Thread(() -> watchForDirectoryChangesOnExtraThread(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread));
        t.setDaemon(true);
        t.setName("directory content change watcher");
        t.start();
    }


    private void optionallyInstallShutdownHookThatCleansEverythingUp() {

        if (fileLock == null && randomAccessFileForLock == null && watchService == null) {
            return;
        }

        final Thread shutdownHookThread = new Thread(() -> {
            try {
                if (fileLock != null) {
                    fileLock.release();
                }
                if (randomAccessFileForLock != null) {
                    randomAccessFileForLock.close();
                }
                Files.deleteIfExists(LOCKFILE.toPath());
            } catch (Exception ignore) {
            }
            if (watchService != null) {
                try {
                    watchService.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        Runtime.getRuntime().addShutdownHook(shutdownHookThread);
    }


    private void watchForDirectoryChangesOnExtraThread(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {

        while (true) { // To eternity and beyond! Until the universe shuts down. (Should be a volatile boolean, but this class only has absolutely required features.)

            try {
                Thread.sleep(POLLINTERVAL);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            final WatchKey wk;
            try {
                wk = watchService.poll();
            } catch (ClosedWatchServiceException e) {
                // This situation would be normal if the watcher has been closed, but our application never does that.
                e.printStackTrace();
                return;
            }

            if (wk == null || !wk.isValid()) {
                continue;
            }


            for (WatchEvent<?> we : wk.pollEvents()) {

                final WatchEvent.Kind<?> kind = we.kind();
                if (kind == StandardWatchEventKinds.OVERFLOW) {
                    System.err.println("OVERFLOW of directory change events!");
                    continue;
                }


                final WatchEvent<Path> watchEvent = (WatchEvent<Path>) we;
                final File file = watchEvent.context().toFile();


                if (file.equals(DETECTFILE)) {

                    if (!executeOnAWTEventDispatchThread || SwingUtilities.isEventDispatchThread()) {
                        codeToRunIfOtherInstanceTriesToStart.run();
                    } else {
                        SwingUtilities.invokeLater(codeToRunIfOtherInstanceTriesToStart);
                    }

                    break;

                } else {
                    System.err.println("THIS IS THE FILE THAT WAS DELETED: " + file);
                }

            }

            wk.reset();
        }
    }

}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top