A long time perplexed issue is that a Java stored procedure in Oracle throws a java.lang.OutOfMemoryError very seldom, once a couple of months since a few years.
Analog to Java non-standard option: -Xmx, the appended test code 1 attempts to use:
OracleRuntime.setMaxMemorySize
to provoke OutOfMemoryError.
On a Sqlplus window, when one runs:
set serveroutput on size 50000
exec dbms_java.set_output(50000);
exec testp1(500000);
we get the error:
Exception in thread "Root Thread" java.lang.Throwable:
--- Message ---
null
--- Cause ---
null
--- StackTrace ---
java.lang.OutOfMemoryError
at JavaOutput.output(JavaOutput:20)
at JavaOutput.output(JavaOutput:28)
Open a new Sqlplus window (hence a new Oracle session), when one runs:
set serveroutput on size 50000
exec dbms_java.set_output(50000);
exec testp1(900000);
call succeeds. If one call again: exec testp1(500000);
no more error.
Test Code 2 is the original Java stored procedure. It tries to make some post-processing after OutOfMemoryError, which causes further OutOfMemoryError.
There are two deficiencies with this code.
(1). It converts OutOfMemoryError (a subclass of Error) into RuntimeException (a subclass of Exception). And Java Doc on Class Error said:
An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.
(2). There is no OutOfMemoryError printed in the output:
Exception in thread "Root Thread" JavaOutput$WrapException:
--- Message ---
null
at JavaOutput$ExceptHelper.setError(JavaOutput:41)
at JavaOutput.output(JavaOutput:25)
and the root cause is discarded.The reason is that "message" and "cause" are optional in Java VM implementation and it seems
that Oracle JVM does not include them. Here is an excerpt of Java Doc about class Throwable:
It can also contain a message string that gives more information about the error.
Finally, it can contain a cause: another throwable that caused this throwable to get thrown.
Test Code 1
(tested in Oracle 11.2.0.3.0 on AIX and Solaris):
CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "JavaOutput" as
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.Writer;
import java.io.StringWriter;
import java.io.PrintWriter;
import oracle.sql.BLOB;
import oracle.aurora.vm.OracleRuntime;
public class JavaOutput {
public static void output(String file, BLOB blob, long rtMaxMSSet) throws Throwable {
InputStream inStream = null;
OutputStream outStream = null;
try {
OracleRuntime.setMaxMemorySize(rtMaxMSSet);
int bufferSize = blob.getBufferSize();
inStream = blob.getBinaryStream();
outStream = new FileOutputStream(file, true);
byte[] buffer = new byte[bufferSize];
int length = -1;
while ((length = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, length);
}
} catch (Throwable t) {
String msg = t.getMessage();
String stackTrc = getStackTrace(t);
throw new Throwable("\n --- Message ---\n" + t.getMessage() +
"\n --- Cause ---\n" + t.getCause() +
"\n --- StackTrace ---\n" + stackTrc);
} finally {
if (inStream != null) {
inStream.close();
}
if (outStream != null) {
outStream.close();
}
}
}
static String getStackTrace(Throwable t)
{
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
t.printStackTrace(printWriter);
return writer.toString();
}
}
/
create or replace procedure java_output(
p_file_name varchar2
,p_blob blob
,p_max_size number
) as language java name 'JavaOutput.output(java.lang.String, oracle.sql.BLOB, long)';
/
create or replace procedure testp1(p_max_size number) as
l_blob_huge blob := empty_blob;
l_file_name varchar2(200) ;
l_raw raw(100);
begin
select diagnostic_dest||'/diag/rdbms/' || db_name||'/'|| db_name||'/trace/ksun_output.txt'
into l_file_name
from ( select
(select value from v$parameter where name = 'diagnostic_dest') diagnostic_dest
,(select value from v$parameter where name = 'db_name') db_name
from dual);
dbms_lob.createtemporary(l_blob_huge, true);
l_raw := '4b53554e'; --ksun;
dbms_lob.writeappend(l_blob_huge, lengthb(l_raw)/2, l_raw);
java_output(l_file_name, l_blob_huge, p_max_size);
end testp1;
/
Test Code 2:
CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "JavaOutput" as
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.sql.SQLException;
import oracle.sql.BLOB;
import oracle.aurora.vm.OracleRuntime;
public class JavaOutput {
public static void output(String file, BLOB blob, long rtMaxMSSet) throws IOException, SQLException {
InputStream inStream = null;
OutputStream outStream = null;
try {
OracleRuntime.setMaxMemorySize(rtMaxMSSet);
int bufferSize = blob.getBufferSize();
inStream = blob.getBinaryStream();
outStream = new FileOutputStream(file, true);
byte[] buffer = new byte[bufferSize];
int length = -1;
while ((length = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, length);
}
} catch (Throwable t) {
ExceptHelper.setError(t);
} finally {
if (inStream != null) {
inStream.close();
}
if (outStream != null) {
outStream.close();
}
}
}
static class ExceptHelper {
static void setError(Throwable t) {
try {
byte[] errReport = new byte[400000];
} finally {
throw new WrapException("\n --- Message ---\n" + t.getMessage());
}
}
}
static class WrapException extends RuntimeException {
WrapException(String msg) {
super(msg);
}
}
}
/