Задать вопрос
Ответы пользователя по тегу Java
  • Как хранить подключения(Socket) клиентов и надо-ли это делать?

    Vamp
    @Vamp
    Хранить объект конечно нужно, иначе как клиенту отправлять данные?

    Начните с Map, а дальше видно будет. Ещё нужно обязательно предусмотреть регулярную очистку Map от закрывшихся сокетов, иначе получите утечку памяти.
    Ответ написан
    Комментировать
  • Объекты, имеющие поля сложных типов (Immutable или нет)?

    Vamp
    @Vamp
    В состояние объекта входит состояние объектов сложных типов. С формальной точки зрения объекты обоих ваших классов являются изменяемыми. Однако в JVM существует понятие effectively final - это мутабельный объект, состояние которого не изменяется, хоть и не форсируется модификаторами private и final. Если JVM поймёт, что конкретный объект вашего класса не меняется (другими словами, является effectively final), то к этому объекту могут применяться оптимизации, характерные для неизменяемых объектов.
    Ответ написан
    Комментировать
  • При инициализации класса, загружаются ли в память его instace-члены?

    Vamp
    @Vamp
    Могут загружаться, а могут и нет. JVMS не определяет должны ли загружаться все, часть или вообще никакие типы, на которые ссылается загружаемый класс. Так что данное поведение остаётся на усмотрение разработчика JVM.

    HotSpot, например, реализует ленивую загрузку классов. То есть не загружает классы, если в этом нет строгой необходимости. Он может даже не все static члены загрузить во время статической инициализации класса.

    Пример:
    import java.time.LocalDateTime;
    import java.util.concurrent.CountDownLatch;
    
    public class A {
    
        private static LocalDateTime ldt;
    
        private static CountDownLatch cdl;
    
        static {
            ldt = LocalDateTime.now();
            cdl = null;
        }
    
        public static void main(String[] args) {
            System.out.println("123");
        }
    
    }

    При запуске данного кода с аргументом JVM "-verbose:class" выведется список всех загруженных классов. Пример запуска на openjdk 12:

    ...
    [0,100s][info][class,load] java.time.temporal.TemporalAccessor source: shared objects file
    [0,100s][info][class,load] java.time.temporal.Temporal source: shared objects file
    [0,100s][info][class,load] java.time.temporal.TemporalAdjuster source: shared objects file
    [0,101s][info][class,load] java.time.chrono.ChronoLocalDateTime source: shared objects file
    [0,101s][info][class,load] java.time.LocalDateTime source: shared objects file
    ...

    Видно, что загрузился LocalDateTime и все имплементируемые им интерфейсы, но CountDownLatch в списке отсутствует, несмотря на то, что существует статическая переменная с этим типом.

    Аналогично происходит и с instance членами - классы, на которые они ссылаются, будут загружаться только по мере необходимости (использования). Вполне нормальна ситуация, когда объекты одного класса активно используются в программе, но при этом не все используемые им типы загружены. Более того, это относится не только к членам класса или объекта, но и к локальным переменным и даже просто коду:

    import java.math.BigDecimal;
    import java.time.LocalDateTime;
    
    public class A {
    
        public static void main(String[] args) {
            if ("world".equals(System.getenv("HELLO"))) {
                System.out.println(LocalDateTime.now());
            } else {
                System.out.println(BigDecimal.TEN);
            }
        }
    
    }

    В этом примере будет загружен класс BigDecimal, но не LocalDateTime. Если инвертировать условие или запустить код с выставленной переменной окружения HELLO=world, то в списке классов появится LocalDateTime, но BigDecimal будет отсутствовать.
    Ответ написан
    2 комментария
  • Почему рекурсия в java уходит в бесконечность?

    Vamp
    @Vamp
    Идеальная задачка для пошаговой отладки. Она есть в любой приличной IDE. Покажу на примере IDEA:

    1. Ставьте на строчке, которую хотите отладить, так называемую точку прерывания (breakpoint).

    5de7eb1ceef0b714535201.png

    2. Запускайте программу в отладочном режиме.

    5de7eb4300731205266204.png

    3. Программа начнёт выполняться как обычно, но когда исполнение дойдёт до строки, помеченной breakpoint'ом, выполнение остановится и в дебаг окне отобразится текущий стек трейс, значения локальных переменных и самое важное для вашей проблемы - кнопки пошагового продолжения.

    5de7ebd9dd5e7287922482.png(на скриншоте я уже проехал несколько шагов вперёд)

    Вот эти 5de7ecb0c3d98348194703.png
    Первая кнопка продвинет выполнение программы на одну строчку. Вторая сделает то же самое, но если в выполняемой строке вызывается какой-то метод, то отладка шагнёт внутрь метода и шаги продолжатся уже в теле вызываемого метода.

    Выполняя отладку по шагам, вы будете точно представлять себе что происходит внутри программы и появится понимание откуда растут ноги у бага. Чем раньше вы освоите отладку в пошаговом режиме, тем проще будет в будущем.
    Ответ написан
  • Почему разные выходные значения Java и Python?

    Vamp
    @Vamp
    Причина в том, что второй вызов метода вычисляется в значение, не влезающее в тип данных long. То есть возникает классический integer overflow. В java и php старшие биты, не поместившиеся в long, просто отбрасываются. В python и ruby на уровне языка поддерживается bignum арифметика, поэтому результат другой. То есть числа могут быть сколь угодно велики без опасности возникновения переполнения. В php такое тоже возможно при помощи модуля bcmath или gmp. В java аналогичную арифметику предоставляет класс BigInteger:

    import java.math.BigInteger;
    
    public class A {
        public static void main(String[] args) {
            BigInteger output_1 = generate(400732734464L, -74, 12);
            BigInteger output_2 = generate(1641401281110016L, 100, 14);
    
            System.out.println(output_1);
            System.out.println(output_2);
        }
    
        public static BigInteger generate(long val1, int val2, int val3) {
            return BigInteger.valueOf(val2 & 255)
                .add(BigInteger.valueOf(val1))
                .shiftLeft(val3);
        }
    }
    Ответ написан
    Комментировать
  • Как ограничить область действия зависимостей?

    Vamp
    @Vamp
    Лучше всего форкнуть проект библиотеки и переписать код на spring 5. Возможно это будет не слишком трудоёмко, учитывая, что зависимость от spring у библиотеки транзитивная. То есть в коде библиотеки может и не быть кода, зависящего от spring, поэтому может быть достаточно обновить только версию непосредственной зависимости, которая уже тащит spring.

    Альтернативным вариантом является использование maven-shade-plugin. Этот плагин упаковывает зависимости проекта в единый jar файл и, опционально, может перемещать классы в другой пакет. Фича называется class relocation. То есть вы можете собрать shaded версию библиотеки soap-ws, у которой классы spring будут релоцированы из пакета org.springframework в, например, vildaraj.shaded.org.springframework. В этом случае вы можете в основном проекте использовать какую угодно версию spring без конфликтов.
    Ответ написан
    Комментировать
  • Как создать связку Forge+Bukkit?

    Vamp
    @Vamp
    Для этого необходимо очень хорошо разбираться как в forge, так и в bukkit. Лучшим вариантом будет поучаствовать в разработке обоих проектов. То есть не в создании модов и плагинов с использованием данных платформ, а именно в развитии их самих. А там уже будет видно как скрестить ежа с ужом. Так же можете посмотреть как это сделано в термосе и попробовать повторить.

    В любом случае, сделать быстро и просто точно не получится. Я однажды попробовал взяться, но когда понял, что это займёт неадекватное количество времени, то бросил и переключился на SpongeForge.
    Ответ написан
    Комментировать
  • Неправильно приходят байты?

    Vamp
    @Vamp
    Раз не склеивается само, значит нужно склеивать самому. Алгоритм довольно прост - читаем из буфера не более readableBytes() байт. Если не хватило данных до полного пакета, то сохраняем прочитанное и ждём следующего пакета, откуда дочитываем недостающее.

    Обычно в сетевых протоколах предусматривают заголовок с общей длиной пакета или маркеры конца, чтобы можно было понять сколько нужно вычитывать данных из сети.

    Вот эти квадратики в логе почти наверняка и есть маркер окончания пакета или заголовок следующего:
    5b220c3b09f5b774481408.png

    Но я думаю, что движок предоставляет абстракции более высокого уровня, которые сами собирают сырые пакеты в какой-нибудь Packet250CustomPayload, а вам останется только обработать их.
    Ответ написан
    6 комментариев
  • Можно ли заменить объект на новый, вместе с заменой ссылок?

    Vamp
    @Vamp
    Такое возможно если использовать прокси объект. То есть все ссылки в программе будут смотреть на прокси-объект, который инкапсулирует в себе ссылку непосредственно на тот объект, который вам хочется заменить.

    // MyClass - тип объекта, который хотим подменить
    // унаследоваться необходимо, чтобы не сломать совместимость типов
    class MyProxy extends MyClass {
    
      // наш спрятанный объект, который будем заменять в будущем
      private MyClass hidden;
    
      @Override
      public int hashCode() {
        return hidden.hashCode();
      }
    
      @Override
      public boolean equals(Object o) {
        return hidden.equals(o);
      }
      // и так далее заоверрайдить все-все публичные
      // методы наследуемого класса
    
      // правда, с публичными свойствами будет проблема - для
      // их замены необходима более сложная логика:
      @Override
      public int calculate() {
        hidden.var = var; // на случай если поле было изменено извне
        int result = hidden.calculate();
        var = hidden.var; // а это если поле было изменено изнутри
        return result;
      }
    
      public int var;
    
      // а вот и главный заменщик
      void setObject(MyClass new_o) {
        hidden = new_o;
        var = new_o.var;
      }
    }

    Потом останется только найти все места, где создаётся объект и заменить его на прокси объект. Далее настоящий объект подменяется вызовом setObject().

    Если у проксируемого объекта есть final поля или методы, то придётся отказаться от наследования MyClass и заменить во всём проекте тип ссылок MyClass на MyProxy.
    Ответ написан
    Комментировать
  • Почему параметры, выставленные в MySQL, не всегда работают в JAVA?

    Vamp
    @Vamp
    Проблема не в java, а в самом запросе. Если вы не перечисляете колонки, данные для которых вы хотите указать, то mysql считает, что вы собираетесь указать данные для всех существующих в таблице колонок. Независимо от того, есть там default значение или нет.

    Для решения вашей проблемы необходимо явным образом перечислить колонки:
    INSERT = "INSERT INTO users (login, password, email, group, ip, country, city) VALUES (?, ?, ?, ?, ?, ?, ?)";

    Все колонки, явным образом не перечисленные в запросе, получат значение по умолчанию, если оно было определено при создании таблицы для них.
    Ответ написан
    1 комментарий
  • Как правильно передать такой запрос через jdbc?

    Vamp
    @Vamp
    У вас в переменной sql сразу 4 разных запроса. Вам нужно выполнить каждый запрос отдельным вызовом executeUpdate(). Workbench же сам определяет количество переданных ему запросов, разделяет их и выполняет последовательно, один за другим. А в консоли вы ввели каждую команду отдельно, поэтому никаких проблем с workbench и консольным клиентом у вас нет.
    Ответ написан
    1 комментарий
  • Как правильно работать с MySQL в Java?

    Vamp
    @Vamp
    Первый вопрос: скажите пожалуйста правильно ли я построил схему работу с базой?

    Такой подход имеет право на жизнь и часто встречается у новичков. Но он не очень удобен в использовании и поддержке.

    Второй вопрос: у меня после каждого запроса в терминале висит куча Sleep соединений

    Сложно сказать в чём дело по тем данным что вы предоставили. Возможно con.close() выдал исключение и не закрыл соединение, а у вас это исключение тупо игнорируется.

    В однопоточных приложениях распространена практика открыть одно соединение и работать с ним всё время, пока приложение работает. То есть примерно так:

    package hello.world;
    
    import java.sql.*;
    
    public class Main {
    
        // один коннект на всё приложение
        private static Connection conn;
    
        // в данном случае так же можно использовать и единственный Statement
        private static Statement stmt;
    
        public static void main(String[] args) throws SQLException {
            conn = DriverManager.getConnection("jdbc:mysql://localhost/hello", "user", "password");
            stmt = conn.createStatement();
            doTheJob();
        }
    
        private static void doTheJob() throws SQLException {
    
            // здесь используется фишка Java7 под названием try-with-resources,
            // которая освобождает нас от необходимости закрывать ResultSet руками
            // он будет закрыт автоматически в конце try блока
            try ( ResultSet rs = stmt.executeQuery("SHOW TABLES") ) {
                while ( rs.next() ) {
                    System.out.println(rs.getString(1));
                }
            }
    
            // переиспользуем Statement для другого запроса - это допустимо
            try ( ResultSet rs = stmt.executeQuery("SELECT column FROM table") ) {
                while ( rs.next() ) {
                    System.out.println(rs.getString(1));
                }
            }
    
            // вариант без try-with-resources для Java6 и ниже
            ResultSet rs2 = null;
            try {
                rs2 = stmt.executeQuery("SELECT column2 FROM table2");
                // делаем что-то с rs2
            } finally {
                close(rs2);
            }
    
        }
    
        private static void close(ResultSet rs) {
            if ( rs != null ) {
                try {
                    rs.close();
                } catch ( SQLException e ) {
                    e.printStackTrace();
                }
            }
        }
    
    }


    То есть закрывать коннект к базе после каждого запроса совершенно необязательно. В некоторых случаях это даже вредно, потому что открытие нового коннекта занимает некоторое время и при большом количестве запросов этот оверхед становится заметным.

    Для многопоточных программ часто используется схожая техника, где у каждого потока есть свой объект Connection, с которым он работает (например, хранит свой экземпляр коннекта в ThreadLocal поле).

    Но в многопоточных приложениях удобнее пользоваться пулом коннектов. Пул берёт на себя ответственность по раздаче соединений всем страждущим. Он на старте создаёт сразу несколько соединений к базе и сохраняет в свою внутреннюю коллекцию. Далее, по мере необходимости (вызова метода getConnection на пуле), пул удаляет из внутренней коллекции первый по очереди коннект и возвращает его запросившему. Если колекция пуста, то создаётся новый коннект и сразу возвращается. Основная фишка в том, что возвращаемые этим пулом соединения при закрытии на самом деле на закрываются, а возвращаются обратно во внутреннюю коллекцию пула и могут быть переиспользованы другими потоками. Таким образом достигается более высокая производительность путём уменьшения количества открытых коненктов к базе и увеличения интенсивности их использования. Код приложения при этом выглядит так, будто на каждый запрос строится новое соединение к базе и закрывается сразу после завершения работы с результатом этого запроса.

    Разумеется, описание в предыдущем абзаце довольно поверхностное и неполное, но обрисовывает общую для всех пулов концепцию.

    Если будете заниматься многопоточным программированием, а это маст хэв для backend (в особенности, highload), то без пула коннектов не обойтись. Хотя конечно можно и обойтись, но по мере развития проекта вы будете пилить собственную обёртку и в итоге реализуете самый простейший пул коннектов. Оно того не стоит - проще сразу взять готовый.

    Я не рекомендую сразу браться за изучение каких-либо пулов. Сначала попрактикуйтесь с голым JDBC в однопоточных приложениях. Пулы коннектов - это уже вторая ступень. К ним следует приступать только когда будете уверенно себя чувствовать в JDBC. На начальном этапе они будут только мешать.

    Когда будете готовы освоить какой-нибудь пул, то советую обратить внимание на HikariCP. Он давно уже работает у меня в хайлоад проектах и ни разу не подводил. Ниже пример кода с его использованием:

    package hello.world;
    
    import com.zaxxer.hikari.HikariConfig;
    import com.zaxxer.hikari.HikariDataSource;
    
    import java.sql.*;
    import java.util.Properties;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Main {
    
        // пул, в котором содержатся все соединения
        private static HikariDataSource dbPool;
    
        // в этом сервисе будем параллельно выполнять запросы
        private static ExecutorService executor = Executors.newFixedThreadPool(5);
    
        public static void main(String[] args) throws SQLException {
    
            // конфигурируем пул
            Properties props = new Properties();
            props.setProperty("dataSourceClassName", "com.mysql.jdbc.jdbc2.optional.MysqlDataSource");
            props.setProperty("dataSource.url", "jdbc:mysql://localhost/hello");
            props.setProperty("dataSource.user", "user");
            props.setProperty("dataSource.password", "password");
            props.setProperty("poolName", "MyFirstPool");
            props.setProperty("maximumPoolSize", "5"); // в этом пуле будет максимум 5 соединений
            props.setProperty("minimumIdle", "1"); // как минимум одно активное соединение там будет жить постоянно
            dbPool = new HikariDataSource(new HikariConfig(props));
            doTheJob();
        }
    
        private static void doTheJob() throws SQLException {
    
            // сколько запросов будем делать параллельно
            int selects = 5;
    
            // этот объект позволит нам дождаться выполнения всех запросов,
            // выполняющихся в параллельных потоках чтобы подсчитать общее время
            // выполнения всех запросов
            CountDownLatch waitLatch = new CountDownLatch(selects);
    
            long startTime = System.nanoTime();
            for ( int i = 0; i < selects; ++i ) {
                executor.submit(new ThreadPoolJob(dbPool, waitLatch));
            }
    
            try {
                // ждём когда все воркеры закончат
                waitLatch.await();
            } catch ( InterruptedException e ) {
                System.out.println("latch was broken by interruption request");
            }
            long timeElapsed = System.nanoTime() - startTime;
            System.out.println("All queries was executed in: " + (timeElapsed / 1000000000) + " sec");
    
        }
    
        // класс-воркер, который будет выполнять запрос
        private static class ThreadPoolJob implements Runnable {
    
            private final HikariDataSource dbPool;
    
            private final CountDownLatch waitLatch;
    
            ThreadPoolJob(HikariDataSource dbPool, CountDownLatch waitLatch) {
                this.dbPool = dbPool;
                this.waitLatch = waitLatch;
            }
    
            @Override
            public void run() {
                String tName = Thread.currentThread().getName();
                System.out.println(tName + ": Start query");
                try (
                    Connection conn = dbPool.getConnection();
                    Statement stmt = conn.createStatement();
                 ) {
                    // здесь мы не получаем и не работаем с ResultSet, поэтому не
                    // описываем его в try-with-resources
                    stmt.execute("SELECT SLEEP(5)");
                    System.out.println(tName + ": Query completed");
                } catch ( SQLException e ) {
                    e.printStackTrace();
                } finally {
                    waitLatch.countDown();
                }
            }
        }
    
    }
    Ответ написан
    Комментировать
  • Как настроить Maven на копирование во время сборки?

    Vamp
    @Vamp
    Это делается при помощи плагинов. Вот на выбор antrun и maven-resources-plugin:
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-antrun-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <configuration>
                            <tasks>
                                <copy todir="${project.basedir}/target/jfx/speedmap">
                                    <fileset dir="/home/fluent/mapGr"/>
                                </copy>
                            </tasks>
                        </configuration>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
    
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.0.1</version>
                <executions>
                    <execution>
                        <id>copy-resources-foreign</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.basedir}/target/jfx/speedmap</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>/home/fluent/mapGr</directory>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>


    Есть директории, лежащие вне Java -проекта. Это не ресурсы (их надо копировать в директорию, отличную от target\classes).

    В вашем случае это всё-таки ресурсы. Просто по умолчанию maven считает ресурсами только контент из директорий src/main/resources и src/test/resources. Вам просто нужно сказать где ещё располагаются ресурсы, необходимые для сборки проекта.

    Вообще говоря, не очень хорошо завязываться на внешние по отношению к проекту ресурсы. Это усложняет вхождение новых участников, так как теперь уже недостаточно просто запустить mvn install и получить готовый артефакт. Да и сами потом забудете/запутаетесь, будете тратить время на вспоминание как правильно собирать проект. Хотя, конечно, хозяин - барин.
    Ответ написан
    1 комментарий
  • Можно ли в Java 8 устанавливать лимит памяти исходя из системной памяти, а не памяти java процесса?

    Vamp
    @Vamp
    В вашем случае решения чисто на java нет. Здесь необходимо зарядить регулярную задачу (cron), которая будет смотреть свободную физическую память в системе и убивать самое толстое java приложение при достижении 95% лимита памяти. Например, так:
    #!/usr/bin/env bash
    
    # pid'ы отслеживаемых java процессов
    # задачку определения актуальных pid'ов оставлю на вас
    PID1=1234
    PID2=5678
    
    # минимальное допустимое количество свободной памяти в процентах
    MIN_MEM_SIZE=5
    
    function get_java_mem {
      jstat -gc $1 | awk 'NR==1 {print ($3 + $4 + $6 + $8 + $10)}'
    }
    
    function get_free_mem {
      free | awk 'NR==2 {print ($7 * 100) / $2}'
    }
    
    if [ $(echo "`get_free_mem` < $MIN_MEM_SIZE" | bc) -eq "1" ]; then
      if [ $(echo "`get_java_mem $PID1` > `get_java_mem $PID2`" | bc) -eq "1" ]; then
        PID_TO_KILL=$PID1;
      else
        PID_TO_KILL=$PID2
      fi
      echo "Memory shortage detected. Taking heap dump and killing process $PID_TO_KILL"
      jmap  -dump:format=b,file=${PID_TO_KILL}.heapdump.bin $PID_TO_KILL
      kill $PID_TO_KILL
    fi


    если я пропишу -Xmx2g то оба влезут, но если один процесс использует все 2 гб, то второй не может эту память использовать.

    Не очень ясно зачем нужны такие качели. Если приложение нормально работает с -Xmx2g, то с -Xmx4g сильно лучше не станет. Разве что full gc будет происходить немного чаще, только и всего.
    Ответ написан
    Комментировать
  • Как красиво завершить работу с NIO при прерывании потока?

    Vamp
    @Vamp Автор вопроса
    Проблема оказалась в изначально неверном предположении.

    Во всех книгах написано, что jvm завершает свою работу только после завершения всех не daemon потоков. Но нигде не уточняется при каких условиях поток получает interrupted status. Мне всегда казалось, что при закрытии приложения внешним образом (Crtl+C, SIGTERM, рестарт системы), jvm сначала установит interrupted статус всем потокам, дождётся завершения последнего не daemon потока, после чего закроется. И достаточно просто продумать адекватную реакцию на прерывание потока.

    Как оказалось, единственный способ установить interrupted status - вызвать Thread.interrupt() из пользовательского кода. А все остальные способы завершения тупо останавливают потоки jvm без возможности как-то отреагировать на ситуацию. И единственный способ отследить это - установить shutdown hook.
    В принципе, это решение меня вполне устраивает.
    Ответ написан
    Комментировать
  • В какой ситуации я получаю writable SelectorKey из nio Selektor'a в Java?

    Vamp
    @Vamp
    Writable получается всегда, когда есть свободное место в output буфере у какого-либо сокета.

    Технически, метод write() всего лишь записывает данные в специальный буфер в памяти, привязанный к сетевому сокету. Размер этого буфера регулируется опцией SO_SNDBUF. Это in-memory операция. Реальная работа с сетью на этом этапе не происходит.

    Данные из буфера в сеть отправляются уже непосредственно операционной системой. Скорость опустошения буфера зависит от разных факторов, в частности, пропускной способности сети и производительности машины на другом конце провода. Если в буфере есть хотя бы один свободный байт, то флаг OP_WRITE будет установлен.

    На OP_WRITE нужно подписываться только когда есть данные, которые можно отправить удаленной стороне и отписываться сразу, как только вся порция данных будет записана в сокет. В противном случае, в результате каждого вызова Selector.select() вы будете получать список всех сокетов, у которых есть свободное место в буфере. А так как данных для записи у вас нет - только впустую потратите ресурс процессора.

    Образно говоря, на OP_READ вы подписываетесь, когда хотите послушать что говорят другие, а на OP_WRITE, когда вам самим есть что сказать.
    Ответ написан
    Комментировать