The core language
Implementations of the Java programming language provide strong
memory safety, even in the presence of data races in concurrent
code. This prevents a large range of security vulnerabilities
from occurring, unless certain low-level features are used; see
.
Increasing robustness when reading arrays
External data formats often include arrays, and the data is
stored as an integer indicating the number of array elements,
followed by this number of elements in the file or protocol data
unit. This length specified can be much larger than what is
actually available in the data source.
To avoid allocating extremely large amounts of data, you can
allocate a small array initially and grow it as you read more
data, implementing an exponential growth policy. See the
readBytes(InputStream, int) function in
.
Incrementally reading a byte array
When reading data into arrays, hash maps or hash sets, use the
default constructor and do not specify a size hint. You can
simply add the elements to the collection as you read them.
Resource management
Unlike C++, Java does not offer destructors which can deallocate
resources in a predictable fashion. All resource management has
to be manual, at the usage site. (Finalizers are generally not
usable for resource management, especially in high-performance
code; see .)
The first option is the
try-finally construct, as
shown in .
The code in the finally block should be as short as
possible and should not throw any exceptions.
Resource management with a
try-finally block
Note that the resource allocation happens
outside the try block,
and that there is no null check in the
finally block. (Both are common artifacts
stemming from IDE code templates.)
If the resource object is created freshly and implements the
java.lang.AutoCloseable interface, the code
in can be
used instead. The Java compiler will automatically insert the
close() method call in a synthetic
finally block.
Resource management using the
try-with-resource construct
To be compatible with the try-with-resource
construct, new classes should name the resource deallocation
method close(), and implement the
AutoCloseable interface (the latter breaking
backwards compatibility with Java 6). However, using the
try-with-resource construct with objects that
are not freshly allocated is at best awkward, and an explicit
finally block is usually the better approach.
In general, it is best to design the programming interface in
such a way that resource deallocation methods like
close() cannot throw any (checked or
unchecked) exceptions, but this should not be a reason to ignore
any actual error conditions.
Finalizers
Finalizers can be used a last-resort approach to free resources
which would otherwise leak. Finalization is unpredictable,
costly, and there can be a considerable delay between the last
reference to an object going away and the execution of the
finalizer. Generally, manual resource management is required;
see .
Finalizers should be very short and should only deallocate
native or other external resources held directly by the object
being finalized. In general, they must use synchronization:
Finalization necessarily happens on a separate thread because it is
inherently concurrent. There can be multiple finalization
threads, and despite each object being finalized at most once,
the finalizer must not assume that it has exclusive access to
the object being finalized (in the this
pointer).
Finalizers should not deallocate resources held by other
objects, especially if those objects have finalizers on their
own. In particular, it is a very bad idea to define a finalizer
just to invoke the resource deallocation method of another object,
or overwrite some pointer fields.
Finalizers are not guaranteed to run at all. For instance, the
virtual machine (or the machine underneath) might crash,
preventing their execution.
Objects with finalizers are garbage-collected much later than
objects without them, so using finalizers to zero out key
material (to reduce its undecrypted lifetime in memory) may have
the opposite effect, keeping objects around for much longer and
prevent them from being overwritten in the normal course of
program execution.
For the same reason, code which allocates objects with
finalizers at a high rate will eventually fail (likely with a
java.lang.OutOfMemoryError exception) because
the virtual machine has finite resources for keeping track of
objects pending finalization. To deal with that, it may be
necessary to recycle objects with finalizers.
The remarks in this section apply to finalizers which are
implemented by overriding the finalize()
method, and to custom finalization using reference queues.
Recovering from exceptions and errors
Java exceptions come in three kinds, all ultimately deriving
from java.lang.Throwable:
Run-time exceptions do not have to be
declared explicitly and can be explicitly thrown from any
code, by calling code which throws them, or by triggering an
error condition at run time, like division by zero, or an
attempt at an out-of-bounds array access. These exceptions
derive from from the
java.lang.RuntimeException class (perhaps
indirectly).
Checked exceptions have to be declared
explicitly by functions that throw or propagate them. They
are similar to run-time exceptions in other regards, except
that there is no language construct to throw them (except
the throw statement itself). Checked
exceptions are only present at the Java language level and
are only enforced at compile time. At run time, the virtual
machine does not know about them and permits throwing
exceptions from any code. Checked exceptions must derive
(perhaps indirectly) from the
java.lang.Exception class, but not from
java.lang.RuntimeException.
Errors are exceptions which typically
reflect serious error conditions. They can be thrown at any
point in the program, and do not have to be declared (unlike
checked exceptions). In general, it is not possible to
recover from such errors; more on that below, in .
Error classes derive (perhaps indirectly) from
java.lang.Error, or from
java.lang.Throwable, but not from
java.lang.Exception.
The general expection is that run-time errors are avoided by
careful programming (e.g., not dividing by zero). Checked
exception are expected to be caught as they happen (e.g., when
an input file is unexpectedly missing). Errors are impossible
to predict and can happen at any point and reflect that
something went wrong beyond all expectations.
The difficulty of catching errors
Errors (that is, exceptions which do not (indirectly) derive
from java.lang.Exception), have the
peculiar property that catching them is problematic. There
are several reasons for this:
The error reflects a failed consistenty check, for example,
java.lang.AssertionError.
The error can happen at any point, resulting in
inconsistencies due to half-updated objects. Examples are
java.lang.ThreadDeath,
java.lang.OutOfMemoryError and
java.lang.StackOverflowError.
The error indicates that virtual machine failed to provide
some semantic guarantees by the Java programming language.
java.lang.ExceptionInInitializerError
is an example—it can leave behind a half-initialized
class.
In general, if an error is thrown, the virtual machine should
be restarted as soon as possible because it is in an
inconsistent state. Continuing running as before can have
unexpected consequences. However, there are legitimate
reasons for catching errors because not doing so leads to even
greater problems.
Code should be written in a way that avoids triggering errors.
See
for an example.
It is usually necessary to log errors. Otherwise, no trace of
the problem might be left anywhere, making it very difficult
to diagnose realted failures. Consequently, if you catch
java.lang.Exception to log and suppress all
unexpected exceptions (for example, in a request dispatching
loop), you should consider switching to
java.lang.Throwable instead, to also cover
errors.
The other reason mainly applies to such request dispatching
loops: If you do not catch errors, the loop stops looping,
resulting in a denial of service.
However, if possible, catching errors should be coupled with a
way to signal the requirement of a virtual machine restart.