2009-08-27 8 views
2

J'ai un problème avec mon programme Java qui se ferme soudainement, sans aucune exception levée ou le programme se terminant normalement. J'écris un programme pour résoudre Project Euler14th problem. Voilà ce que je suis:Java VM se déconnectant soudainement sans raison apparente

private static final int INITIAL_CACHE_SIZE = 30000; 
private static Map<Long, Integer> cache = new HashMap<Long, Integer>(INITIAL_CACHE_SIZE); 

public void main(String... args) { 
    long number = 0; 
    int maxSize = 0; 

    for (long i = 1; i <= TARGET; i++) { 
     int size = size(i); 
     if (size > maxSize) { 
      maxSize = size; 
      number = i; 
     } 
    } 
} 
private static int size(long i) { 
    if (i == 1L) { 
     return 1; 
    } 
    final int size = size(process(i)) + 1; 
    return size; 
} 

private static long process(long n) { 
    return n % 2 == 0 ? n/2 : 3*n + 1; 
} 

Cela fonctionne très bien, et se termine correctement en 5 secondes avec un objectif de 1 000 000.

Je voulais optimiser en ajoutant un cache, donc je changé la méthode de taille à ceci:

private static int size(long i) { 
    if (i == 1L) { 
     return 1; 
    } 
    if (cache.containsKey(i)) { 
     return cache.get(i); 
    } 
    final int size = size(process(i)) + 1; 
    cache.put(i, size); 
    return size; 
} 

maintenant, quand je le lance, il arrête simplement (sorties de processus) quand je reçois à 555144. même nombre chaque fois. Aucune exception, erreur, plantage Java VM ou tout est levé.

Modification de la taille du cache ne semble pas avoir d'effet non plus, alors comment le cache introduction peut provoquer cette erreur?

Si j'appliquer la taille du cache pour être non seulement initial, mais permanent comme ceci:

if (i < CACHE_SIZE) { 
     cache.put(i, size); 
    } 

le bug ne se produit plus. Modifier: Lorsque je définis la taille du cache sur 2M, le bogue commence à s'afficher à nouveau.

Quelqu'un peut-il reproduire cela, et peut-être même donner une suggestion pour expliquer pourquoi cela se produit?

+0

Sur quel système d'exploitation travaillez-vous? –

+0

Je cours sous Windows Vista Professionnel et JDK 1.6.0_03 – Jorn

+0

Vous pouvez essayer de mettre à jour le JDK et voir si vous obtenez le même comportement. Ils sont maintenant sur la mise à jour 16 à 1.6. – digitaljoel

Répondre

8

Ceci est simplement une OutOfMemoryError qui ne sont pas en cours d'impression. Le programme s'exécute correctement si je définis une taille de tas élevée, sinon il se ferme avec un OutOfMemoryError non ouvert (facile à voir dans un débogueur, cependant).

Vous pouvez vérifier et obtenir une décharge de tas (ainsi que l'impression qu'un OutOfMemoryError a eu lieu) en passant cette arg machine virtuelle Java et de refaire votre programme:

-XX:+HeapDumpOnOutOfMemoryError

Avec cela, il sera alors imprimer quelque chose à cet effet:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid4192.hprof ...
Heap dump file created [91901809 bytes in 4.464 secs]

Bump votre taille de tas avec, disons, -Xmx200m et vous n'aurez pas un problème - au moins pour TARGET = 1000000.

+0

Oui, vous avez raison. Cet argument de ligne de commande affichait OutOfMemoryError. – Jorn

0

Si votre processus Java se bloque soudainement, il se peut qu'une ressource soit dépassée. Comme la mémoire. Vous pouvez essayer de définir un tas max plus élevé.

+0

Je ne pense pas que ce soit ça. Il ne lance pas OutOfMemoryExceptions. En outre, Gestionnaire des tâches Windows me dit qu'il n'utilise pas plus de 100 Mo de mémoire. – Jorn

+0

il n'utilise peut-être pas plus de 100 Mo, mais il se met à souffler quand il tente d'allouer un morceau de mémoire vraiment énorme (qui n'apparaîtrait pas dans le gestionnaire de tâches car il n'est jamais alloué). Votre point sur le manque d'exceptions OutOfMemory ne correspond pas tout à fait à ce scénario, cependant ... – rmeador

+1

J'ai essayé ceci. Je peux le reproduire. Le processus Java exe renvoie le code d'erreur 1 au système d'exploitation. Je soupçonne qu'il y a un bug dans le rehashing de hashmap. Je suis sur le JDK de Sun 1.6.0_16 –

0

Voyez-vous un Heap Dump généré après le crash? Ce fichier devrait être dans le répertoire courant pour votre JVM, c'est là que je chercherais plus d'informations.

+0

Non, rien de tel. – Jorn

+0

Les vidages de tas ne sont pas générés automatiquement à la sortie sans passer un argument. Il peut y avoir d'autres façons, mais dans ce cas particulier: -XX: + HeapDumpOnOutOfMemoryError –

+0

Les hachages de tas sont générés automatiquement (bien que le comportement peut très bien entre les implémentations de VM), mais pas pour OutOfMemoryErrors, mais uniquement pour les vrais crash jvm. – Yishai

0

Je reçois une erreur OutOfMemory sur le cache.put (i, size);

Pour obtenir l'erreur exécutez votre programme en éclipse en utilisant le mode de débogage il apparaîtra dans la fenêtre de débogage. Il ne produit pas de trace de pile dans la console.

3

Il semble que la JVM elle-même se bloque (c'est le premier réflexe quand votre programme meurt sans aucune allusion d'exception). La première étape d'un tel problème consiste à passer à la dernière révision de votre plate-forme. La machine virtuelle Java doit vider le tas dans un fichier .log du répertoire dans lequel vous avez démarré la machine virtuelle Java, en supposant que votre niveau d'utilisateur dispose des droits d'accès à ce répertoire. Cela étant dit, certaines erreurs OutOfMemory ne sont pas signalées dans le thread principal, donc à moins que vous n'essayiez try/catch (Throwable t) et que vous en obteniez un, il est difficile de vous assurer que vous n'êtes pas réellement juste à court de mémoire. Le fait qu'il n'utilise que 100 Mo pourrait simplement signifier que la JVM n'est pas configurée pour utiliser plus. Cela peut être changé en changeant les options de démarrage à la JVM à -Xmx1024m pour obtenir un gig de mémoire, pour voir si le problème va n'importe où.

Le code pour faire les prises d'essai devrait être quelque chose comme ceci:

public static void main(String[] args) { 
    try { 
     MyObject o = new MyObject(); 
     o.process(); 
    } catch (Throwable t) { 
     t.printStackTrace(); 
    } 
} 

Et tout dans la méthode du processus et ne stocke pas votre cache statique, de cette façon si l'erreur se produit à l'instruction catch l'objet est hors de portée et peut être collecté, libérant suffisamment de mémoire pour permettre l'impression de la trace de la pile. Aucune garantie que cela fonctionne, mais cela donne un meilleur coup.

+0

Le problème est un OutOfMemoryError, probablement dans un thread qui re-hash hashmap une fois qu'il a dépassé une certaine taille –

+0

Cela ne devrait pas se produire dans un thread séparé. –

0

La méthode récursive size() n'est probablement pas un bon endroit pour effectuer la mise en cache. J'ai mis un appel à cache.put (i, size); à l'intérieur de la boucle for principale() et cela fonctionne beaucoup plus rapidement. Sinon, je reçois également une erreur MOO (plus d'espace de tas). Editer: Voici la source - la récupération du cache est en taille(), mais le stockage est effectué dans main().

public static void main(String[] args) { 
    long num = 0; 
    int maxSize = 0; 

    long start = new Date().getTime(); 
    for (long i = 1; i <= TARGET; i++) { 
     int size = size(i); 
     if (size >= maxSize) { 
      maxSize = size; 
      num = i; 
     } 
     cache.put(i, size); 
    } 

    long computeTime = new Date().getTime() - start; 
    System.out.println(String.format("maxSize: %4d on initial starting number %6d", maxSize, num)); 
    System.out.println("compute time in milliseconds: " + computeTime); 
} 

private static int size(long i) { 
    if (i == 1l) { 
     return 1; 
    } 

    if (cache.containsKey(i)) { 
     return cache.get(i); 
    } 

    return size(process(i)) + 1; 
} 

Notez que en supprimant la cache.put() appeler de la taille(), il ne met pas en cache toutes les tailles calculée, mais elle évite également re-mise en cache une taille calculée précédemment. Cela n'affecte pas les opérations de hashmap, mais comme le fait remarquer akf, il évite les opérations autoboxing/unboxing d'où provient votre tueur de tas. J'ai aussi essayé "if (! ContainsKey (i)) {cache.put() etc" dans size() mais malheureusement, il manque aussi de mémoire.

1

Une différence significative entre les deux implémen- tations de size(long i) est la quantité d'objets que vous créez.

Dans la première implémentation, aucun Objects n'est créé. Dans la seconde vous faites énormément d'autoboxing, en créant un nouveau Long pour chaque accès de votre cache, et en mettant de nouveaux Long s et de nouveaux Integer s sur chaque modification.

Cela expliquerait l'augmentation de l'utilisation de la mémoire, mais pas l'absence d'un OutOfMemoryError. Augmenter le tas permet de le compléter pour moi.

De this Sun aritcle:

The performance ... is likely to be poor, as it boxes or unboxes on every get or set operation. It is plenty fast enough for occasional use, but it would be folly to use it in a performance critical inner loop.

+0

L'augmentation de l'utilisation de la mémoire est probablement ce qui mène à l'OOM - Je suppose que la génération d'objets rapides mastiquent la mémoire avant que le garbage collector se déplace pour libérer les objets inutilisés. – weiji

Questions connexes