Index: doc/options.txt
===================================================================
--- doc/options.txt	(revision 4107)
+++ doc/options.txt	(working copy)
@@ -41,7 +41,7 @@
 '-' cannot be used, simply use the long name instead. 
 <p>
 ;--output-dir=directory
-:     The directory in which all output files are written. It defaults
+:	Specify the directory in which all output files are written. It defaults
 to the current working directory, i.e. the directory the command is
 executed from.
 <p>
@@ -53,7 +53,7 @@
 that it is unique and does not clash with others.
 <p>
 ;--description=text
-: 	Sets the descriptive text for the map. This may be displayed in
+: 	Set the descriptive text for the map. This may be displayed in
 QLandkarte, MapSource or on a GPS, where it is normally shown
 below the family name. Example: --description="Germany, Denmark"
 Please note: if you use splitter.jar to build a template.args file
@@ -63,20 +63,27 @@
 "-c template.args".
 <p>
 ;--country-name=name
-: 	Sets the map's country name. The default is "COUNTRY".
+: 	Set the map's country name. The default is "COUNTRY".
 <p>
 ;--country-abbr=abbreviation
-: 	Sets the map's abbreviated country name. The default is "ABC".
+: 	Set the map's abbreviated country name. The default is "ABC".
 <p>
 ;--region-name=name
-: 	Sets the map's region name. By default, the map has no region name.
+: 	Set the map's region name. By default, the map has no region name.
 <p>
 ;--region-abbr=abbreviation
-: 	Sets the map's abbreviated region name. By default, the map has
+: 	Set the map's abbreviated region name. By default, the map has
 no abbreviated region name.
 <p>
 === Label options ===
 
+;--code-page=number
+:     Specify which international character set is to be used. Only 8 bit
+character sets are supported so you have to specify which code page you
+want to use.
+It is entirely dependent on the device firmware which code pages are
+supported.
+<p>
 ;--latin1
 : 	This is equivalent to --code-page=1252.
 <p>
@@ -83,20 +90,10 @@
 ;--unicode
 : 	This is equivalent to --code-page=65001. Note that some devices don't support 
 Unicode maps produced by mkgmap.
-
 <p>
-;--code-page=number
-:     This option enables the use of international characters. Only 8 bit
-character sets are supported and so you have to specify which code page
-you want to use.
-<p>
-It is entirely dependent on the device firmware which code pages are
-supported.
-<p>
 ;--lower-case
-: 	Allow labels to contain lower case letters.  Note that most or all
-Garmin devices are not able to display lower case letters at an angle
-so this option is not generally useful.
+: 	Allow labels to contain lower case letters.  Note that many
+Garmin devices are not able to display lower case letters at an angle.
 <p>
 === Address search options ===
 ;--index
@@ -135,8 +132,8 @@
 be used as were used to compile the individual map tiles.
 
 ;--split-name-index
-:     An option to enable indexing each part of a street name separately.
-So for example if the street is "Aleksandra Gryglewskiego" then you will be able to
+:     Index each part of a street name separately.
+For example, if the street is "Aleksandra Gryglewskiego" then you will be able to
 search for it as both "Aleksandra" and "Gryglewskiego".  It will also increase the
 size of the index.  Useful in countries where searching for the first word in name
 is not the right thing to do. Words following an opening bracket '(' are ignored. 
@@ -143,7 +140,9 @@
 :	See also option road-name-config.  
 <p>
 ;--road-name-config=filename
-:	This option handles the problem that some countries have road names which 
+:	Provide the name of a file containing commonly used road name prefixes
+and suffixes.
+This option handles the problem that some countries have road names which 
 often start or end with very similar words, e.g. in France the first word
 is very often 'Rue', often followed by a preposition like 'de la' or 'des'.
 This leads to rather long road names like 'Rue de la Concorde' where only
@@ -154,21 +153,20 @@
 the important part. In combinarion with option split-name-index
 only the words in the important part are indexed.
 <p>
-:There are two different effects of this option:
+:There are two effects of this option:
 ::	- On the PC, when zooming out, the name 'Rue de la Concorde' is only
 rendered as 'Concorde'.
 ::	- The index for road names only contains the important part of the name.
 You can search for road name Conc to find road names like 'Rue de la Concorde'.
-One problem: Search for 'Rue' will not list 'Rue de la Concorde' 
-or 'Rue du Moulin'. It may list 'Rueben Brookins Road' if that is in the map.
+However, a search for 'Rue' will not list 'Rue de la Concorde' or
+'Rue du Moulin'. It may list 'Rueben Brookins Road' if that is in the map.
 Only MapSource shows a corresponding hint.
 <p>
 ::	Another effect is that the index is smaller. 
-:	The option specifies the path to a file which gives the details. See 
-comments in the sample roadNameConfig.txt for further details.
+:	See comments in the sample roadNameConfig.txt for further details.
 <p>
 ;--mdr7-excl=name[,name...]
-:	This option allows you to specify words which should not be in the road index.
+:	Specify words which should be omitted from the road index.
 It was added before option road-name-config and is probably no longer needed.
 :	Example usage: --x-mdr7-excl="Road, Street, Straße, Weg"
 <p>
@@ -180,7 +178,7 @@
 if the word after it appears in the given list. If so, the word is removed
 and the search is repeated. The remaining string is used to create the index.
 :	Example: Assume your style adds surface attributes like 'pav.' or 'unp.' to a road
-label. You can use --mdr7-del="pav.,unp." to remove these appendixes from the index.
+label. You can use --mdr7-del="pav.,unp." to remove these suffixes from the index.
 <p>
 ;--poi-excl-index=poi[-poi][,poi[-poi]...]
 :	By default, mkgmap indexes the following POI types with a non-empty label:
@@ -188,7 +186,7 @@
 ::	- 0x2axx..0x30xx (Food & Drink, Lodging, ...)
 ::	- 0x28xx (no category ?)
 ::	- 0x64xx .. 0x66xx (attractions)   
-:	This option allows to exclude POI types from the index. 
+:	This option allows the exclusion of POI types from the index. 
 The excluded types are not indexed, but may still be searchable on a device 
 as some devices seem to ignore most of the index, e.g. an Oregon 600 with 
 firmware 5.00 only seems to use it for city search.
@@ -209,7 +207,7 @@
 to reduce the index size.
 <p>
 ;--bounds=directory|zipfile
-:     A directory or a zip file containing the preprocessed bounds files. 
+:     Specify a directory or zip file containing the preprocessed bounds files. 
 Bounds files in a zip file must be located in the zip file's root directory.
 <p>
 The preprocessed boundaries are used to add special tags to all elements 
@@ -507,11 +505,15 @@
 === Miscellaneous options ===
 
 ;--max-jobs[=integer]
-: 	When number is specified, allow that number of maps to be
-processed concurrently. If number is not specified, the limit
-is set equal to the number of CPU cores. If this option is not
-given at all, the limit is 1 (i.e., the maps are processed
-sequentially).
+: 	Specify the number of threads to be used for concurrent processing.
+Increasing max-jobs will reduce the execution time, providing
+sufficient memory is available and the value is not greater than the
+number of cores in the CPU.  If no value is specified, or the option
+is not given at all, the limit is automatically set to a reasonable value
+based on the amount of memory allocated to the Java runtime and the amount
+used in processing the first tile. To optimise mkgmap to use all available
+CPU cores, you may need to use the Java -Xmx option to increase the amount
+of available heap storage.
 <p>
 ;--keep-going
 : 	Don't quit whole application if an exception occurs while
@@ -553,9 +555,8 @@
 and that no more than one connecting highway joins at each node.
 <p>
 ;--check-roundabout-flares
-: 	Sanity check roundabout flare roads - warn if they don't point
-in the correct direction or if they are not one-way or if they
-extend too far.
+: 	Check that roundabout flare roads point in the correct
+direction, are one-way and don't extend too far.
 <p>
 ;--max-flare-length-ratio=NUM
 : 	When checking flare roads, ignore roads whose length is
@@ -774,8 +775,7 @@
 compared with these patterns and those that match are deleted.
 <p>
 ;--ignore-fixme-values
-: 	Tells mkgmap to ignore all tags for which the value matches the pattern
-"(?i)fix[ _]?+me".	
+: 	Ignore all tags for which the value matches the pattern "(?i)fix[ _]?+me".	
 <p>
 ;--tdbfile
 : 	Write files that are essential to running with MapSource, a .tdb file and
@@ -792,7 +792,7 @@
 : 	The default is show-profiles=0.
 <p>
 ;--transparent
-: 	Makes the map transparent, so that if two maps covering the same area are
+: 	Make the map transparent, so that if two maps covering the same area are
 loaded, you can see through this map to see details from the other map too.
 Typically used for maps containing just contour lines. See --draw-priority
 as well.
Index: resources/help/en/options
===================================================================
--- resources/help/en/options	(revision 4107)
+++ resources/help/en/options	(working copy)
@@ -39,7 +39,7 @@
 	'-' cannot be used, simply use the long name instead. 
 
 --output-dir=directory
-	The directory in which all output files are written. It defaults
+	Specify the directory in which all output files are written. It defaults
 	to the current working directory, i.e. the directory the command is
 	executed from.
 
@@ -51,7 +51,7 @@
 	that it is unique and does not clash with others.
 
 --description=text
-	Sets the descriptive text for the map. This may be displayed in
+	Set the descriptive text for the map. This may be displayed in
 	QLandkarte, MapSource or on a GPS, where it is normally shown
 	below the family name. Example: --description="Germany, Denmark"
 	Please note: if you use splitter.jar to build a template.args file
@@ -61,20 +61,28 @@
 	"-c template.args".    
 
 --country-name=name
-	Sets the map's country name. The default is "COUNTRY".
+	Set the map's country name. The default is "COUNTRY".
 
 --country-abbr=abbreviation
-	Sets the map's abbreviated country name. The default is "ABC".
+	Set the map's abbreviated country name. The default is "ABC".
 
 --region-name=name
-	Sets the map's region name. By default, the map has no region name.
+	Set the map's region name. By default, the map has no region name.
 
 --region-abbr=abbreviation
-	Sets the map's abbreviated region name. By default, the map has
+	Set the map's abbreviated region name. By default, the map has
 	no abbreviated region name.
 	
 Label options:
 
+--code-page=number
+	Specify which international character set is to be used. Only 8 bit
+	character sets are supported so you have to specify which code page you
+	want to use.
+
+	It is entirely dependent on the device firmware which code pages are
+	supported.
+	
 --latin1
 	This is equivalent to --code-page=1252.
 
@@ -82,18 +90,9 @@
 	This is equivalent to --code-page=65001. Note that  
 	some devices don't support Unicode maps produced by mkgmap.
 
---code-page=number
-	This option enables the use of international characters. Only 8 bit
-	character sets are supported and so you have to specify which code page
-	you want to use.
-
-	It is entirely dependent on the device firmware which code pages are
-	supported.
-	
 --lower-case
-	Allow labels to contain lower case letters.  Note that most or all
-	Garmin devices are not able to display lower case letters at an angle
-	so this option is not generally useful.
+	Allow labels to contain lower case letters.  Note that many Garmin
+	devices are not able to display lower case letters at an angle.
 
 Address search options:
 --index
@@ -131,8 +130,8 @@
 	be used as were used to compile the individual map tiles.
 
 --split-name-index
-	An option to enable indexing each part of a street name separately.
-	So for example if the street is "Aleksandra Gryglewskiego" then you will be able to
+	Index each part of a street name separately.
+	For example, if the street is "Aleksandra Gryglewskiego" then you will be able to
 	search for it as both "Aleksandra" and "Gryglewskiego".  It will also increase the
 	size of the index.  Useful in countries where searching for the first word in name
 	is not the right thing to do. Words following an opening bracket '(' are ignored. 
@@ -139,6 +138,8 @@
 	See also option road-name-config.  
 
 --road-name-config=filename
+	Provide the name of a file containing commonly used road name prefixes
+	and suffixes.
 	This option handles the problem that some countries have road names which 
 	often start or end with very similar words, e.g. in France the first word
 	is very often 'Rue', often followed by a preposition like 'de la' or 'des'.
@@ -150,21 +151,20 @@
 	the important part. In combination with option split-name-index
 	only the words in the important part are indexed.
 
-	There are two different effects of this option:
+	There are two effects of this option:
 	- On the PC, when zooming out, the name 'Rue de la Concorde' is only
 		rendered as 'Concorde'.
 	- The index for road names only contains the important part of the name.
 		You can search for road name Conc to find road names like 'Rue de la Concorde'.
-		One problem: Search for 'Rue' will not list 'Rue de la Concorde' 
-		or 'Rue du Moulin'. It may list 'Rueben Brookins Road' if that is in the map.
+		However, a search for 'Rue' will not list 'Rue de la Concorde' or
+		'Rue du Moulin'. It may list 'Rueben Brookins Road' if that is in the map.
 		Only MapSource shows a corresponding hint.
 		
 	Another effect is that the index is smaller. 
-	The option specifies the path to a file which gives the details. See 
-	comments in the sample roadNameConfig.txt for further details.
+	See comments in the sample roadNameConfig.txt for further details.
  
 --mdr7-excl=name[,name...]
-	This option allows you to specify words which should not be in the road index.
+	Specify words which should omitted from the road index.
 	It was added before option road-name-config and is probably no longer needed.
 	Example usage: --x-mdr7-excl="Road, Street, Straße, Weg"
  
@@ -176,7 +176,7 @@
 	if the word after it appears in the given list. If so, the word is removed
 	and the search is repeated. The remaining string is used to create the index.
 	Example: Assume your style adds surface attributes like 'pav.' or 'unp.' to a road
-	label. You can use --mdr7-del="pav.,unp." to remove these appendixes from the index.
+	label. You can use --mdr7-del="pav.,unp." to remove these suffixes from the index.
 	  
 --poi-excl-index=poi[-poi][,poi[-poi]...]
 	By default, mkgmap indexes the following POI types with a non-empty label:
@@ -184,7 +184,7 @@
 	- 0x2axx..0x30xx (Food & Drink, Lodging, ...)
 	- 0x28xx (no category ?)
 	- 0x64xx .. 0x66xx (attractions)   
-	This option allows to exclude POI types from the index. 
+	This option allows the exclusion of POI types from the index. 
 	The excluded types are not indexed but may still be searchable on a device, 
 	as some devices seem to ignore most of the index, e.g. an Oregon 600 with 
 	firmware 5.00 only seems to use it for city search.
@@ -203,9 +203,9 @@
 		to exclude this.
 	- For the mentioned Oregon you may use --poi-excl-index=0x2a00-0x661f
 		to reduce the index size.
-		
+
 --bounds=directory|zipfile
-	A directory or a zip file containing the preprocessed bounds files. 
+	Specify a directory or zip file containing the preprocessed bounds files. 
 	Bounds files in a zip file must be located in the zip file's root directory.
 
 	The preprocessed boundaries are used to add special tags to all elements 
@@ -499,11 +499,15 @@
 Miscellaneous options:
 
 --max-jobs[=integer]
-	When number is specified, allow that number of maps to be
-	processed concurrently. If number is not specified, the limit
-	is set equal to the number of CPU cores. If this option is not
-	given at all, the limit is 1 (i.e., the maps are processed
-	sequentially).
+	Specify the number of threads to be used for concurrent processing.
+	Increasing max-jobs will reduce the execution time, providing
+	sufficient memory is available and the value is not greater than the
+	number of cores in the CPU.  If no value is specified, or the option
+	is not given at all, the limit is automatically set to a reasonable value
+	based on the amount of memory allocated to the Java runtime and the amount
+	used in processing the first tile. To optimise mkgmap to use all available
+	CPU cores, you may need to use the Java -Xmx option to increase the amount
+	of available heap storage.
 
 --keep-going
 	Don't quit whole application if an exception occurs while
@@ -544,9 +548,8 @@
 	and that no more than one connecting highway joins at each node.
 
 --check-roundabout-flares
-	Sanity check roundabout flare roads - warn if they don't point
-	in the correct direction or if they are not one-way or if they
-	extend too far.
+	Check that roundabout flare roads point in the correct direction,
+	are one-way and don't extend too far.
 
 --max-flare-length-ratio=NUM
 	When checking flare roads, ignore roads whose length is
@@ -785,8 +788,7 @@
 	compared with these patterns and those that match are deleted.
 	
 --ignore-fixme-values
-	Tells mkgmap to ignore all tags for which the value matches the pattern
-	"(?i)fix[ _]?+me".	
+	Ignore all tags for which the value matches the pattern "(?i)fix[ _]?+me".	
 
 --tdbfile
 	Write files that are essential to running with MapSource, a .tdb file and
@@ -803,7 +805,7 @@
 	The default is show-profiles=0.
 
 --transparent
-	Makes the map transparent, so that if two maps covering the same area are
+	Make the map transparent, so that if two maps covering the same area are
 	loaded, you can see through this map to see details from the other map too.
 	Typically used for maps containing just contour lines. See --draw-priority
 	as well.
Index: src/uk/me/parabola/mkgmap/main/Main.java
===================================================================
--- src/uk/me/parabola/mkgmap/main/Main.java	(revision 4107)
+++ src/uk/me/parabola/mkgmap/main/Main.java	(working copy)
@@ -19,6 +19,13 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.PrintStream;
+import java.lang.OutOfMemoryError;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryPoolMXBean;
+import java.lang.management.MemoryType;
+import java.lang.management.MemoryUsage;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -85,7 +92,7 @@
 	private final List<FilenameTask> futures = new LinkedList<>();
 	private ExecutorService threadPool;
 	// default number of threads
-	private int maxJobs = 1;
+	private static int maxJobs = 0;
 
 	private boolean createTdbFiles = false;
 	private boolean tdbBuilderAdded = false;
@@ -117,7 +124,7 @@
 	 * @param args The command line arguments.
 	 */
 	private static int mainStart(String... args) {
-		long start = System.currentTimeMillis();
+		Instant start = Instant.now();
 		System.out.println("Time started: " + new Date());
 		// We need at least one argument.
 		if (args.length < 1) {
@@ -134,6 +141,13 @@
 			CommandArgsReader commandArgs = new CommandArgsReader(mm);
 			commandArgs.setValidOptions(getValidOptions(System.err));
 			commandArgs.readArgs(args);
+		} catch (OutOfMemoryError e) {
+			++numExitExceptions;
+			System.err.println(e);
+			if (maxJobs > 1)
+				System.err.println("Try using the mkgmap --max-jobs option with a value less than " + maxJobs + " to reduce the memory requirement, or use the Java -Xmx option to increase the available heap memory.");
+			else
+				System.err.println("Try using the Java -Xmx option to increase the available heap memory.");
 		} catch (MapFailedException e) {
 			// one of the combiners failed
 			e.printStackTrace();
@@ -152,7 +166,20 @@
 		System.out.println("Number of ExitExceptions: " + numExitExceptions);
 		
 		System.out.println("Time finished: " + new Date());
-		System.out.println("Total time taken: " + (System.currentTimeMillis() - start) + "ms");
+		Duration duration = Duration.between(start, Instant.now());
+		long seconds = duration.getSeconds();
+		if (seconds > 0) {
+			long hours = seconds / 3600;
+			seconds -= hours * 3600;
+			long minutes = seconds / 60;
+			seconds -= minutes * 60;
+			System.out.println("Total time taken: " + 
+								(hours > 0 ? hours + (hours > 1 ? " hours " : " hour ") : "") +
+								(minutes > 0 ? minutes + (minutes > 1 ? " minutes " : " minute ") : "") +
+								(seconds > 0 ? seconds + (seconds > 1 ? " seconds" : " second") : ""));
+		}
+		else
+			System.out.println("Total time taken: " + duration.getNano() / 1000000 + " ms");
 		if (numExitExceptions > 0 || mm.getProgramRC() != 0){
 			return 1;
 		}
@@ -315,13 +342,14 @@
 			checkStyles();
 			break;
 		case "max-jobs":
-			if (val.isEmpty())
-				maxJobs = Runtime.getRuntime().availableProcessors();
-			else
+			if (!val.isEmpty()) {
 				maxJobs = Integer.parseInt(val);
-			if (maxJobs < 1) {
-				log.warn("max-jobs has to be at least 1");
-				maxJobs = 1;
+				if (maxJobs < 1) {
+					log.warn("max-jobs has to be at least 1");
+					maxJobs = 1;
+				}
+				if (maxJobs > Runtime.getRuntime().availableProcessors())
+					log.warn("It is recommended that max-jobs be no greater that the number of processor cores");
 			}
 			break;
 		case "version":
@@ -461,9 +489,38 @@
 		fileOptions(args);
 
 		log.info("Start tile processors");
+		int threadCount = maxJobs;
+		int taskCount = futures.size();
+		Runtime runtime = Runtime.getRuntime();
 		if (threadPool == null) {
-			log.info("Creating thread pool with " + maxJobs + " threads");
-			threadPool = Executors.newFixedThreadPool(maxJobs);
+			if (threadCount == 0) {
+				threadCount = 1;
+				if (taskCount > 2) {
+					//run one task to see how much memory it uses
+					log.info("Max Memory: " + runtime.maxMemory());
+					futures.get(0).run();
+					long maxMemory = 0;
+					for (MemoryPoolMXBean mxBean : ManagementFactory.getMemoryPoolMXBeans())
+					{
+						if (mxBean.getType() == MemoryType.HEAP) {
+							MemoryUsage memoryUsage = mxBean.getPeakUsage();
+							log.info("Max: " + memoryUsage.getMax());
+							log.info("Used: " + memoryUsage.getUsed());
+							if (memoryUsage.getMax() > maxMemory) {
+								maxMemory = memoryUsage.getMax();
+								threadCount = (int)(memoryUsage.getMax() / memoryUsage.getUsed());
+							}
+						}
+					}
+					threadCount = Math.max(threadCount, 1);
+					threadCount = Math.min(threadCount, runtime.availableProcessors());
+					System.out.println("Setting max-jobs to " + threadCount);
+					futures.remove(0);
+				}
+			}
+
+			log.info("Creating thread pool with " + threadCount + " threads");
+			threadPool = Executors.newFixedThreadPool(threadCount);
 		}
 
 		// process all input files
@@ -503,8 +560,8 @@
 						else
 							throw e;
 					}
-				} catch (ExitException ee) {
-					throw ee;
+				} catch (OutOfMemoryError | ExitException e) {
+					throw e;
 				} catch (MapFailedException mfe) {
 //					System.err.println(mfe.getMessage()); // already printed via log
 					numMapFailedExceptions++;
@@ -518,6 +575,12 @@
 			}
 		}
 		System.out.println("Number of MapFailedExceptions: " + numMapFailedExceptions);
+		if ((taskCount > threadCount + 1) && (maxJobs == 0) && (threadCount < runtime.availableProcessors())) {
+			com.sun.management.OperatingSystemMXBean os = (com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
+			long physicalMemory = os.getTotalPhysicalMemorySize();
+			if (runtime.maxMemory() < physicalMemory / 6)
+				System.out.println("To reduce the run time, consider increasing the amnount of memory available for use by mkgmap by using the Java -Xmx flag to set the memory to more than " + ((Math.min(physicalMemory / 4, runtime.maxMemory() * runtime.availableProcessors() / threadCount) / (1024 * 1024 * 100)) + 1) * 100 + " MB.");
+		}
 
 		if (combiners.isEmpty())
 			return;
