Java.lang.OutOfMemoryError: impossibile creare un nuovo thread nativo-fs - > in exec clone() e execve () clash?


Sto sperimentando occasionalmente: -

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:691)

Ho letto molto su questo, e non credo di raggiungere un vero limite, in termini di memoria fisica, memoria virtuale (JVM a 64 bit), limite di thread, limite di processo o descrittori di file.

In realtà, non sono nemmeno sicuro che questo sia in alcun modo correlato al fatto che i processi che noto essere influenzati sono processi Java.

Il mio sospetto è che questo sia un problema nel kernel Linux, o forse a seconda di come si sostiene, un problema nella JVM Java o pthread_create ().

La macchina che vedo su questo è un mostro: 64 processori, 512GB RAM (70GB gratis). La sua corsa RHES 5.11 (Tikanga) 2.6.18-402.el5 x86_64. I limiti sono impostati in questo modo

bash-3.2$ cat /proc/sys/kernel/pid_max
65536
bash-3.2$ cat /proc/sys/kernel/threads-max
8257699

bash-3.2$ /sbin/sysctl fs.file-nr
fs.file-nr = 62220      0       1048576

bash-3.2$ /sbin/sysctl fs.file-max
fs.file-max = 1048576

bash-3.2$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 4128849
max locked memory   (kbytes, -l) 32
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4128849
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

Molti utenti utilizzano questo computer, e finora, solo due di loro (io e un altro) hanno segnalato il problema. La cosa comune di questi utenti è che i processi interessati creeranno periodicamente thread Java che corrono per un po ' e poi si fermano, e inoltre, ci saranno altri processi periodicamente e regolarmente programmati sotto lo stesso userid. Il processo Java della vittima stesso non sta eseguendo altri processi.

Nel mio caso, il processo Java stabilisce regolarmente connessioni JMX, le usa, quindi le chiude e ciò comporta la creazione di thread in background (che non è sotto il mio controllo, quindi non posso raggrupparlo). L'altra attività è uno script di shell in loop che esegue alcuni processi, quindi dorme, quindi si ripete. Numeri di processi, numero di thread per processo, uso della memoria, uso del descrittore di file, ecc..., sono tutti bene all'interno di limiti.

Nell'implementazione di fork.c, ci sono vari motivi per cui EAGAIN può essere restituito, per lo più risorse o motivi di sicurezza (che trovo difficile credere in questo caso), e anche uno relativo a copy_fs e fs->in_exec. in_exec può apparentemente essere impostato quando c'è un execve non sicuro, in check_unsafe_execve (), a seconda dell'uso della struttura condivisa fs_struct (che Non capisco, vedi i commenti alla fine).

Https://github.com/torvalds/linux/blob/e66eded8309ebf679d3d3c1f5820d1f2ca332c71/kernel/fork.c

In sostanza, sembra che ci sia qualche condizione di razza o uno sfortunato allineamento temporale tra l'elaborazione clone() e l'elaborazione execve() che può causare il ritorno di clone() EAGAIN. Per me, questo sembra sbagliato, in quanto in realtà non è stato raggiunto un limite di risorse e non sono "troppi processi" per pagina man per clone().

C'è una discussione online sul fatto che questo comportamento sia necessario o corretto. http://lkml.iu.edu/hypermail/linux/kernel/0905.0/02309.html

Se l'argomento è che, se ben educati programma dovrebbe riprovare più tardi se riceve EAGAIN da clone(), beh, questo è problematico, così come è un programma per distinguere tra una EAGAIN causato da in_exec (che vorrebbe riprovare) e una vera carenza di risorse (che forse non si vuole mantenere ripetizione per un tempo molto lungo per).

Ho visto l'eccezione su Java 7 e Java 8, e non penso che la versione sia importante, quindi guardando qualsiasi vecchia versione sorgente OpenJDK, non riesco a vedere alcun tentativo (os/linux/vm/os_linux.cpp).

Inoltre, da strace, posso vedere la JVM che emette il clone (), riceve l'EAGAIN e poi muore ...

1454948661.318678 clone(
    child_stack=0x2b6297dc5250,
    flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_
    parent_tidptr=0x2b6297dc59d0,
    tls=0x2b6297dc5940, child_tidptr=0x2b6297dc59d0
    ) = -1 EAGAIN (Resource temporarily unavaiable)

A questo punto non sono sicuro di guardare il giusto sorgente del kernel Linux per il kernel in uso. Anche io non capisco cosa fs_struct è per e come potrebbe essere condiviso tra thread e processi. Quindi, dopo aver esplorato i limiti, sto indagando su quest'altra possibilità perché è ciò che rimane.

Se ho ragione, cosa ci vorrebbe per causare deliberatamente il problema. Ho provato a scrivere programmi che avviano molti pthreads e fork ed execve processi di breve durata in modo da aumentare la possibilità che clone() si verifichi in Java contemporaneamente a execve(). Ho provato alcune varianti, ma non sono sicuro di esserci riuscito per ottenere la giusta combinazione di eventi, o fortunato. Questo è probabilmente perché non apprezzo appieno cosa significhi ottenere un execve () "non sicuro".

Cos'è fs_struct, quando potrebbe essere condiviso, come posso influenzarlo?

Ciò che sto suggerendo ha senso?

Qualche consiglio su come dimostrare/confutare il mio sospetto?

Author: Andy Key, 2016-02-12