OutOfMemoryError: Java heap space when casting a numeric primitive to char
Asked Answered
B

1

8

I have been studying Decorator pattern and developed simple class ToUpperCaseInputStream. I overrode read() method so it could convert all chars from InputStream to uppercase. Code of the method is shown below (throws OutOfMemoryError):

@Override
public int read() throws IOException {
    return Character.toUpperCase((char)super.read());
}

As I figured out later, casting to char is redundant, but it's not the point. I'm having "java.lang.OutOfMemoryError: Java heap space" when the code:

((char) super.read())  

evaluates. To make this simpler I wrote the same method (this one throws OutOfMemoryError):

@Override
public int read() throws IOException {
    int c =(char) super.read();
    return (c == -1 ? c : Character.toUpperCase(c));
} 

And this one does not:

@Override
public int read() throws IOException {
    int c = super.read();
    return (c == -1 ? c : Character.toUpperCase(c));
} 

When I remove casting from the assignment the code runs with no errors and results in all text uppercased. As it's said at Oracle tutorials:

An assignment to an array component of reference type (§15.26.1), a method invocation expression (§15.12), or a prefix or postfix increment (§15.14.2, §15.15.1) or decrement operator (§15.14.3, §15.15.2) may all throw an OutOfMemoryError as a result of boxing conversion (§5.1.7).

It seems that autoboxing is used, but as for me it's not the case. Both variants of the same method result in OutOfMemoryError. If I am wrong, please explain this to me, because it will blow up my head.

To provide more info there is the client code:

public class App {
public static void main(String[] args) throws IOException {

    try (InputStream inet = new ToUpperCaseInputStream(new FileInputStream("d:/TEMP/src.txt"));
        FileOutputStream buff = new FileOutputStream("d:/TEMP/dst.txt")) {
        copy(inet, buff);
    }
}

public static void copy(InputStream src, OutputStream dst) throws IOException {
    int elem;
    while ((elem = src.read()) != -1) {
        dst.write(elem);
    }
}

}

What it does is just prints simple message from one file to another.

Although the case is solved I want to share a really good explanation of how casting is done. https://mcmap.net/q/274385/-why-does-39-int-char-byte-2-39-produce-65534-in-java

Baxley answered 17/1, 2015 at 19:6 Comment(11)
That's got to be a coincidence. The two versions are identical.Cyclopentane
"when I remove casting from the assignment and changing variable primitive type from char to int, the code runs with no errors...both methods result in OutOfMemoryException" Please edit the question so it is clear to us what throws and what does not.Giro
"changing variable primitive type from char to int" I see in both cases the primitive is an int, what did you want to say ?Rowen
@Mondkin I'm sorry, I had to edit this a couple of times due to previous comments. Now it is clear and correct.Baxley
Now that you added more code, @yurgis answer makes sense. Put a println inside the while to see how many times it is being executed in each case. I guess in the second case the while condition always satisfies.Rowen
@Mondkin somehow I figured out that after aquiring -1 end-point while loops on "65535". And it doesn't matter whether I checked for -1 or no. The problem is in "int c = (char) super.read();". Somehow casting to char in this line makes code unable to catch "-1 end-point"Baxley
Although you corrected your code it is still wrong. After the cast c is never -1Predisposition
@Mondkin Got this!) The reason "while loop" never ends because when I cast -1 of type int it results in smth my IDE doesn't know how to render - , and so does not know char. The line "int c = (char) super.read();" produces "65535" in an endless loop when reaches the end of file (-1).Baxley
It's not that your IDE does not know how to render, it's as yurgis pointed in their answer below. That 65535 is something normal in that cast you did.Rowen
@Mondkin I mean, try to System.out.println((char) -1); and you'll see what I ment. I made this casting on purpose and I still can't get it why the line of code "int i = (char) -1;" results in 65535Baxley
Check this: docs.oracle.com/javase/specs/jls/se7/html/jls-5.htmlRowen
P
4

Before you cast to char you need to check for -1 (signaling end of input data).

char in Java is an unsigned short, meaning that when -1 is returned, your cast will make it 65535. Even if you did not have OutOfMemory, your code is still broken.

Regarding why you are getting OOM error, it is hard to say without full code, maybe, later in the code there are some memory allocations based on value of the character.

So try this and see if it helps:

@Override
public int read() throws IOException {
    int c = super.read();
    if (c == -1) return c;

    char ch = (char) c;
    return Character.toUpperCase(ch);
} 
Predisposition answered 17/1, 2015 at 19:51 Comment(4)
You misunderstood me. Your code works fine as intended, but when I add casting like that: int c = (char) super.read(); everything crashes...Baxley
You can't cast before checking for -1. After the cast you will never get -1 because char is always positive.Predisposition
So the line "int i = (char) -1;" always results in max char?Baxley
yes, try this code { char c = 0; c--; System.out.println(c); } More about unsigned values: en.wikipedia.org/wiki/SignednessPredisposition

© 2022 - 2024 — McMap. All rights reserved.