Разрешить перезапуск приложения Java с включенным мониторингом JMX

У меня есть приложение Java с мониторингом JMX, например:

-Dcom.sun.management.jmxremote.port=9999 \
// some other properties omitted

Но когда я пытаюсь перезапустить приложение, когда-то я получил сообщение об ошибке, говорит, что номер порта JMX уже используется. Это неприемлемо.

Итак, я хочу установить SO_REUSEADDR в true для базового сокета, чтобы избежать этой ошибки, но не обнаружил никаких связанных свойств JMX.

Любая идея?

Ответ 1

Я боюсь, что вы не можете сделать это из командной строки.

Вам нужно будет создать RMIServerSocketFactory, который создает ServerSockets с нужным параметром (SO_REUSEADDR).

Документы здесь: http://docs.oracle.com/javase/8/docs/technotes/guides/rmi/socketfactory/

Кто-то другой, решающий ту же проблему: https://svn.apache.org/viewvc?view=revision&revision=r1596579

Ответ 2

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

Вот фрагмент кода, который я использую для открытия JConsole-совместимого JMX-коннектора. В Scala, извините, но вы должны быть в состоянии легко адаптировать его

def startJmx(port: Int): Unit = {
if (port < 1) {
  return
}

val log = LoggerFactory.getLogger(getClass)

log.info("Starting JMX server connector on port {}", port)

val registry = LocateRegistry.createRegistry(port)

val server = ManagementFactory.getPlatformMBeanServer()

val url = new JMXServiceURL(s"service:jmx:rmi:///jndi/rmi://localhost:$port/jmxrmi")

val connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, Collections.emptyMap(), server)

 val thread = new Thread {
   override def run = try {
     connectorServer.start()
   } catch { 
     case e: Exception => log.error("Unable to start JMX connector", e)
   }
 }
 thread.setDaemon(true)
 thread.setName("JMX connector Thread")
 thread.start()
}

Ответ 3

У меня была такая же проблема. Это был первый экземпляр моего приложения (которое я остановил), которое все еще подписывалось на этот порт, поэтому новый экземпляр не мог запускаться. В моем случае это не было связано с механизмом TIME_WAIT сокета, а скорее с тем фактом, что после вызова stop() потребовалось некоторое время, пока все запущенные потоки не закончили изящно. То, что работало в моем случае, было отменой регистрации bean прямо перед тем, как остановить приложение, чтобы сокет был бесплатным.

private void unregisterBeanForName(String name) {
        try {

            JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://127.0.0.1:9999/jmxrmi");
            JMXConnector cc = JMXConnectorFactory.connect(jmxServiceURL);
            MBeanServerConnection mbsc = cc.getMBeanServerConnection();
//This information is available in jconsole
            ObjectName serviceConfigName = new ObjectName("YourObjectName");
            mbsc.unregisterMBean(serviceConfigName);
// Close JMX connector
            cc.close();
        } catch (Exception e) {
            System.out.println("Exception occurred: " + e.toString());
            e.printStackTrace();
        }
    }

Ответ 4

Это может быть одно решение: На удаленном сервере вы можете иметь два порта: 9999 и 9998, перенаправленные на 9999.

При повторном запуске приложения каждый раз изменяйте логическое значение, чтобы решить подключиться к 9999 или 9998.

Ответ 5

Добавьте к вашему приложению заглушку завершения, которая убьет jmx.

// kill process with port 9999    
fuser -k 9999/tcp