9.25.2015

Java HotSpot VM



В этой статье, я постараюсь рассказать вам о JVM, и так приступим что же за зверь такой JVM.  Он состоит из трех основных  частей: Java HotSpot Virtual Machine Runtime, JIT compiler, Garbage Collector.  JVMR – это основная рабочая лошадка которая отвечает за многие вещи: когда вы запускаете, JVM именно он обрабатывает параметры командной строчки, именно он подгружает классы стандартным загрузчиком классов (class loader), он отвечает за life cycle виртуальной машины, работает с jit компилятором, отвечает за обработку ошибок, обрабатывает потоки (thread) и отвечает за их синхронизацию, и ещё выполняет не заметно для нас много полезных вещей.  Для начала нам нужно знать о параметрах командной строки. Давайте вспомним, какие параметры могут иметь место в командной строчке виртуальной машины: стандартные опции которые будут присутствовать во всех версиях вириальной машины, формат параметра –Опция, например вывести версию вириальной машины java -version. Не стандартные которые могут не поддерживаться в дальнейшем при реализации в следующих поколениях виртуальных машин, начинаются такие параметры с XОпция, например java -XshowSettings:all . Продвинутые опции которыми нужно пользоваться с осторожностью и если понимаете что делаете, опять могут не поддерживаться во всех реализациях системы, параметры начинаются XX,  делятся на два подтипа  можете задать логический параметр, формат такой XX:[-,+]Опция, где  “минус” выключить опцию, “плюс”  включить опцию, например java -XX:+TraceClassLoading включить трассировку загрузки классов в виртуальной машине. Второй подтип вы задаете конкретно значение параметра XX:Параметр=значениеПараметра, например, установить параметр MaxHeapSize в 256 Мегабайт: java -XX:MaxHeapSize=256m. Ну, вот и все мы рассмотрели кратко, как задавать параметры виртуальной машины. А теперь, напишем простейшую программу, и на примере рассмотрим, что происходит с ней и как все работает в виртуальной машине:

package org.vit.ch1;

public class Test {

    public Test() {
        System.out.printf("%s","Hello JVM!");
    }

    public static void main(String args[]) {
        new Test();
    }

} 

И так первым делом мы должны преобразовать наш   java код в байт код, т. е. преобразовать файл Test.java в  Test.class, делается это все просто: в командной строке набираем javac Test.java  получаем Test.class, переходим в каталог,  где у вас начинается пакет и запускаем оттуда команду на запуск виртуальной машины:  

C:\jboss-4.2.0.GA\vit\poi_excel\c1\target\classes>java -cp "." org.vit.ch1.Test
Hello JVM!

Сразу встает вопрос, зачем все так сложно делать, получать байт код, потом его запускать, он будет интерпретироваться JVM, точнее  jit компилятором  в машинные коды, потом только  выполнится программа и получаем результат,  на ум приходит переносимость программ, кроссплатформенность.
Но за счет кроссплатформенности и переносимости  мы теряем в производительности т. к. любой интерпретированный код всегда будет работать медленней машинного кода, это факт.  И так не спешите расстраиваться, любая программа скомпилированная под одну конкретную архитектуру и версию операционной системы будет, себя хуже вести в более новой или старой версии ОС, а если поменялась и разрядность “аппаратной  архитектуры” с x32  на x64, то падение производительности программы трудно не заметить.  И так что же делать, нужно, если есть возможность  достать исходники и перекомпилировать программу в родной машинный код, это возможно только в Linux подобных системах с открытым исходными кодами. Для примера допустим вы установили ядро linux, и у вас много не нужных модулей этого ядра которые “мешаются”, вы просто пере собираете ядро, отключив при этом ненужные модули и компилите его под вашу “аппаратную архитектуру”, при этом у вас получается более шустрое ядро с родными машинными инструкциями (машинным кодом).   Примерно так же поступает JVM HotSpot, преобразовывает байт код в машинный и не просто преобразовывает, а пытается анализировать одни и те же куски кода, которые часто выполняются и оптимально преобразовывать их в машинные инструкции,  так же он собирает статистику выполнения и на основе этой статистику опять перестраивает код, перекомпилирует его уже с учетом этой информации. На практике встречаются случаи, когда оптимизированная программа на виртуальной машине становится более производительной с течением времени чем, подобная программа в машинных кодах. Все эти манипуляции JVM не проходят даром, и во-первых нужно запустить саму виртуальную машину, во-вторых на jit  компиляцию затрачиваются ресурсы системы. По этой причине вам поставляются  две виртуальные машины,  с двумя различными jit  компиляторами которые вы можете запустить, выбрав параметры при запуске JVM: -client или       server.  И так вернемся немного назад, с начало были jvm  которые только интерпретировали код, их признали медленными и отказались от них. После этого появился jit   компилятор который  преобразовывал байт код в машинный, при этом он делал это не спеша, компилировал только те куски кода, которые будут выполняться сейчас, остальные не трогал от это он получил название Just-It-Time (jit). Этот принцип работы дает возможность программе стартовать раньше, нежели,  если бы весь код был бы откомпилирован виртуальной машиной.   И так мы получили замедление при старте программы, но решили проблему медлительности интерпретаторов предыдущего поколения виртуальных машин.  И так JVM HotSpot вобрал в себя функции интерпретатора – часть кода по-прежнему интерпретируется, не часто используемые куски байт кода; компилятора – части кода, которые выполняются наиболее часто, преобразовываются в скомпилированный машинный код, и оптимизатора профилировщика который отслеживает и непрерывно старается оптимизировать “горячие куски” кода (от англ. “Hot Spots” – и пошло слово HotSpot). И так подведем итог: вместо преобразования всех байт кодов в машинный JVM начинает работу как интерпретатор и компилирует только "горячие куски" кода, то есть код, выполняющийся наиболее часто. Во время выполнения он собирает данные анализа. Эти данные используются для определения фрагментов кода, выполняющихся достаточно часто и заслуживающих компиляции. Компиляция только часто исполняемого кода имеет несколько преимуществ: не тратится время на компиляцию кода, выполняющегося редко, таким образом, компилятор может тратить больше времени на оптимизацию "горячего" кода, поскольку он знает, что время будет потрачено не зря. Кроме того, откладывая компиляцию, компилятор имеет доступ к данным анализа, которые могут быть использованы для улучшения оптимизации.  


 Виртуальная машина, запушенная с параметром  server имеет компилятор, оптимизированный для повышения скорости работы и предназначен для протяженных по времени серверных приложений, плюс не стесняется потреблять ресурсы системы. Виртуальная машина, запушенная с параметром  client имеет компилятор,  который оптимизирован для уменьшения времени начального запуска приложения и занимаемого объема памяти, реализует менее сложную оптимизацию, чем серверный компилятор, и, следовательно, требует меньше времени для компиляции, используется в основном для клиентских программ.
Надо сказать ещё об одном параметре виртуальной машины -XX:+TieredCompilation, это гибридный параметр, который вобрал в себя все лучшее из двух различных виртуальных машин, быстроту запуска, от клиентской и оптимизацию от серверной.  Этот параметр запускается парой  с  параметром  server -XX:+TieredCompilation, он по умолчанию включен у 64-битных серверных виртуальных машин, подходит для запуска, как клиентских приложений, так и серверных.  И так попробуем запустить виртуальные машины с параметром version которая показывает версию виртуальной машины:

Microsoft Windows XP [Версия 5.1.2600]
(С) Корпорация Майкрософт, 1985-2001.
C:\Documents and Settings\lopanov>java -version
java version "1.7.0_17"
Java(TM) SE Runtime Environment (build 1.7.0_17-b02)
Java HotSpot(TM) Client VM (build 23.7-b01, mixed mode, sharing)
C:\Documents and Settings\lopanov>

Microsoft Windows [Version 6.1.7601]
(c) Корпорация Майкрософт (Microsoft Corp.), 2009. Все права защищены.
C:\Users\kamaz1>java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
C:\Users\kamaz1>

Мы запустили две виртуальные машины на разных компьютерах, с разными операционными системами проанализируем  полученную информацию.  По умолчанию, в зависимости от операционной системы и её разрядности, виртуальные машины настроены на определенный тип: на серверный или на клиентский, и у них есть настройки по умолчанию, все основные  дефолтные параметры виртуальной машины,  можно посмотреть, включив опцию  -XX:+PrintCommandLineFlags для примера на win7:

C:\jboss-4.2.0.GA\vit\poi_excel\c1\target\classes>java -cp "."  -XX:+PrintCommandLineFlags org.vit.ch1.Test

-XX:InitialHeapSize=133309504 -XX:MaxHeapSize=2132952064 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Hello JVM!

Т.е. запуская просто java -cp "."  org.vit.ch1.Test вы по умолчанию запускаете виртуальную машину с дефолтными параметрами.  Все это называется енергономикой системы,  которые можно посмотреть и кратко почитать на английском. И так вернемся к нашим примерам. Для win XP – я смог только запустить клиентскую VM, для win7 только серверный вариант VM, вот такие ограничения есть у windows, у других ОС я слышал более широкие возможности … .  И так что же такое mixed mode -  эта опция jit компилятора, про которую я вам рассказывал, она установлена по умолчанию     -Xmixed, выполнять весь байт код в режиме интерпретатора, кроме “горячих кусков кода” которые компилируются в машинный код. Есть режим Xint запускается виртуальная машина только в режиме интерпретатора, нет компиляции в нативный (родной) машинный код методов, все интерпретируется.  Есть режим Xcomp который, заставляет jit компилятор компилировать байт код при первом вызове метода.  По умолчанию, байт код компилируется в машинный код после интерпретации его определенное количество раз.   Этот параметр равен 10 000 для серверной VM и 1 000 для клиентской VM, его можно выставить параметром -XX:CompileThreshold=5 000, метод откомпилируется после прогона его интерпретатором на пяти тысячный раз. Но на этом оптимизация не заканчивается,  JVM продолжает анализ и может перекомпилировать код заново с более высоким уровнем оптимизации, если решит, что метод является особенно "горячим" или последующий анализ данных показал возможность дополнительной оптимизации.  И так, какой можно сделать вывод из этого, что бы выполнять тесты производительности и замеры вам нужно предварительно “прогреть программу”, её “горячие куски” и только после этого снимать замеры производительности. Давайте познакомимся с еще одним интересным параметром -XX:+PrintFlagsFinal  он показывает все опции, которые включены у виртуальной машины, найдем значение CompileThreshold по умолчанию:

java -cp "."  -XX:+PrintFlagsFinal org.vit.ch1.Test
….
intx CompileThreshold                          = 10000                               {pd product}
….

У меня вышло более 700 параметров, которые вы в основном, можете менять у виртуальной машины, но обычно работают с  парой десятков центровых параметров, после производят замеры производительности, анализ и разбор полетов и так по кругу пока не устроит результат оптимизации.  У winXP  есть параметр sharing - в java 5 появилась такая вещь как Class Data Sharing. Это файл на диске, в моем случае C:\Program Files\Java\jre1.8.0_60\bin\server\classes.jsa, являющийся частью инсталляции JVM, который содержит дапм JVM памяти, содержащий загруженные классы образующие ядро JVM, т.е. те которые наверняка JVM пришлось бы загрузить сразу при старте. Имея такую область, она сразу мапится при инициализации java машины, тем самым ускоряя время старта приложения. Если файл classes.jsa существует, его всегда можно удалить а после перегенирировать командой -Xshare:dump

C:\Users\kamaz1>java -Xshare:dump
Allocated shared space: 37879808 bytes at 0x0000000800000000
Loading classes to share ...
Loading classes to share: done.
Rewriting and linking classes ...
Rewriting and linking classes: done
Number of classes 2482
    instance classes   =  2468
    obj array classes  =     6
    type array classes =     8
Calculating fingerprints ... done.
Removing unshareable information ... done.
Shared Lookup Cache Table Buckets = 8216 bytes
Shared Lookup Cache Table Body = 64792 bytes
ro space:   6505640 [ 35.3% of total] out of  16777216 bytes [38.8% used] at 0x0
000000800000000
rw space:  10391224 [ 56.3% of total] out of  16777216 bytes [61.9% used] at 0x0
000000801000000
md space:   1524696 [  8.3% of total] out of   4194304 bytes [36.4% used] at 0x0
000000802000000
mc space:     34053 [  0.2% of total] out of    131072 bytes [26.0% used] at 0x0
000000802400000
total   :  18455613 [100.0% of total] out of  37879808 bytes [48.7% used]

Рассмотрим параметр -Xshare:mode:  on -  включает CDS, offотключает, по умолчанию эта опция выбрана у  виртуальных машин Java HotSpot 32-Bit Server VM, Java HotSpot 64-Bit Client VM, and Java HotSpot 64-Bit Server VM,  autoвключается режим CDS если нужно, по умолчанию включено у Java HotSpot 32-Bit Client VM. CDS советуют включать, на клиентских машинах с ограниченной памятью, где запускается много виртуальных машин, и одна область памяти CDS будет использоваться для различных процессов совместно, несколькими jvm.  Включим параметр -Xshare:on  и посмотрим, какие же флаги добавились:

C:\jboss-4.2.0.GA\vit\poi_excel\c1\target\classes>java -cp "."  -Xshare:on -XX:+PrintCommandLineFlags org.vit.ch1.Test
-XX:InitialHeapSize=133309504 -XX:MaxHeapSize=2132952064 -XX:+PrintCommandLineFlags -XX:+RequireSharedSpaces -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC -XX:+UseSharedSpaces
Hello JVM!

Ну вот и все для начала, о виртуальной машине HotSpot.

9.01.2015

EJB remote & Wildfly



До этого все примеры, которые мы рассматривали,  были локальными EJB бинами, которые запускались на одной виртуальной  java  машине (jvm),  и из под сервера wildfly.  Часто так бывает, что клиентский код находиться на другом компьютере и нам нужно  порой работать с ejb  бинами удаленно, т. е. вызвать методы ejb удаленно и получить результат. Что бы позволить такую роскошь, мы должны описать интерфейс с аннотаций @Remote и реализовать его в нашем ejb бине.  Давайте посмотрим на код интерфейса:

package com.vit.fly.ejb;

import javax.ejb.Remote;
import java.util.concurrent.Future;

@Remote
public interface Calculator {
    Future<Integer> add(int x, int y);
    void longRun(int x, int y);
}

Он описывает два метода, первый метод будет складывать 2 целых числа и возвращать нам сумму, второй будет выполнять “долгую” по времени операцию.  Опишем сам ejb  и реализуем интерфейс Calculator:

package com.vit.fly.remoteejb;

import org.jboss.logging.Logger;
import javax.annotation.Resource;
import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

@Stateless(name = "calculator")
public class CalculatorBean implements Calculator {
    private static Logger log = Logger.getLogger(CalculatorBean.class.getName());

    @Resource
    private SessionContext context;

    @Override
    @Asynchronous
    public Future<Integer> add(int x, int y) {
        try {
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new AsyncResult<Integer>(x + y); //складываем 2 числа
    }



    @Override
    //@Asynchronous
    public void longRun(int x, int y) {
        try {
            TimeUnit.SECONDS.sleep(10L);//имитируем долгую работу, приостанавливаем поток на 10 секунд
           log.info("longRun method complete!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Собираем все в jar файл и помещаем его в каталог JBOSS_HOME/Standalone/deployments, после деплоя,  в логах сервера wildfly  появятся строчки примерно такого вида:

2015-08-27 21:41:36,160 INFO  [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-2) JNDI bindings for session bean named calculator in deployment unit deployment "ejb-remote-example-server.jar" are as follows:

                java:global/ejb-remote-example-server/calculator!com.vit.fly.remoteejb.Calculator
                java:app/ejb-remote-example-server/calculator!com.vit.fly.remoteejb.Calculator
                java:module/calculator!com.vit.fly.remoteejb.Calculator
                java:jboss/exported/ejb-remote-example-server/calculator!com.vit.fly.remoteejb.Calculator
                java:global/ejb-remote-example-server/calculator
                java:app/ejb-remote-example-server/calculator
                java:module/calculator

2015-08-27 21:41:36,236 INFO  [org.jboss.weld.deployer] (MSC service thread 1-4) JBAS016005: Starting Services for CDI deployment: ejb-remote-example-server.jar

Перейдем к написанию клиента, который будет вызывать наш ejb  бин удаленно:

package com.vit.fly;

import com.vit.fly.remoteejb.Calculator;
import com.vit.fly.remoteejb.CalculatorBean;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Properties;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class Main {

    private static Context context = null;

    public static void main(String[] args) throws Exception {

        try {
            Properties prop = new Properties();            prop.put(Context.INITIAL_CONTEXT_FACTORY,"org.jboss.naming.remote.client.InitialContextFactory");
            prop.put(Context.PROVIDER_URL, "http-remoting://127.0.0.1:8080");
            context = new InitialContext(prop);
            Calculator calculator = (Calculator) context.lookup("/ejb-remote-example-server/calculator!com.vit.fly.remoteejb.Calculator");
            System.out.println(calculator);
            Future<Integer> result = calculator.add(1, 4);
                        calculator.longRun(1, 2);
                       System.out.println(result.get(2, TimeUnit.SECONDS));              
        } finally {
            context.close();
        }
    }

}

Вам понадобится еще файл с настройками jboss-ejb-client.properties, положите его в classpath:

endpoint.name=client-endpoint
remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
remote.connections=default
remote.connection.default.username=my-username-here
remote.connection.default.password=my-password-here
remote.connection.default.host=localhost
remote.connection.default.port=8080
remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

Запустим на выполнение наш пример, и получим результат работы метода:

авг 28, 2015 7:59:38 PM org.jboss.ejb.client.remoting.RemotingConnectionEJBReceiver associate
INFO: EJBCLIENT000013: Successful version handshake completed for receiver context EJBReceiverContext{clientContext=org.jboss.ejb.client.EJBClientContext@9df6e3, receiver=Remoting connection EJB receiver [connection=org.jboss.ejb.client.remoting.ConnectionPool$PooledConnection@bbe4b0,channel=jboss.ejb,nodename=kamaz1-pc]} on channel Channel ID b371683b (outbound) of Remoting connection 00eebce6 to localhost/127.0.0.1:8080
5

Как вы можете заметить, нам пришлось ждать результата окончания работы программы 11 секунд. Метод  longRun  отработал с задержкой в 10 секунд,  если вы сделаете  его асинхронным, вам будет не нужно ждать, пока метод завершит свою работу, то задержка у основной программы будет приблизительно около 1 секунды. И после запуска из программы метода, 10 секунд  метод  longRun  будет автономно работать вне зависимости от нашей основной программы, автономно.  Для этого нужно, просто раскоментировать аннотацию @Asynchronous у метода longRun и он станет асинхронным. Здесь я вам показываю работу еще одного  асинхронного  метода add, который возвращает результат посредством интерфейса Future.  У этого интерфейса есть метод  .get(2, TimeUnit.SECONDS) который принимает два параметра время, в течение которого ожидается результат, и в чем первый параметр измеряется, в моем случае в секундах. И на последок клиентской программе нужна библиотека jboss-client.jar, которая находиться в дистрибутиве wildfly, вообще-то  не хватающие библиотеки я устанавливаю из установленного мной дистрибутива. Просто организовываю поиск  нужного мне класса, нахожу библиотеку jar инсталирую её в .m2 репозитарий и подсовываю её maveny как зависимость. Вот мои pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.vit.fly.remoteejb</groupId>
    <artifactId>jboss_ejb1</artifactId>
    <version>1.0</version>
    <name>ejb remote server</name>
    <packaging>ejb</packaging>

    <repositories>
        <repository>
            <id>jboss-public-repository-group</id>
            <name>JBoss Public Maven Repository Group</name>
            <url>http://repository.jboss.org/nexus/content/groups/public/</url>
            <layout>default</layout>
            <releases>
                <enabled>true</enabled>
                <updatePolicy>never</updatePolicy>
            </releases>
            <snapshots>
                <enabled>true</enabled>
                <updatePolicy>never</updatePolicy>
            </snapshots>
        </repository>
    </repositories>

    <dependencies>
       <dependency>
            <groupId>org.jboss.spec.javax.ejb</groupId>
            <artifactId>jboss-ejb-api_3.2_spec</artifactId>
            <version>1.0.0.Final</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.annotation</artifactId>
            <version>3.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.1.9.Final</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.1.9.Final</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>jboss-annotations-api_1.2_spec</artifactId>
            <version>1.0.0.Final</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.3</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>1.7</source>
                        <target>1.7</target>
                    </configuration>
                </plugin>
                <plugin>
                    <artifactId>maven-ejb-plugin</artifactId>
                    <version>2.4</version>
                    <configuration>
                        <ejbVersion>3.2</ejbVersion>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
        <finalName>ejb-remote-example-server</finalName>
    </build>
</project>