12

Why does this:

class OutOfMemoryTest02
{
    static void Main()
    {
        string value = new string('a', int.MaxValue);
    }
}

Throw the exception; but this wont:

class OutOfMemoryTest
{
    private static void Main()
    {
        Int64 i = 0;
        ArrayList l = new ArrayList();
        while (true)
        {
            l.Add(new String('c', 1024));

            i++;
        }
    }
}

Whats the difference?

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
ksm
  • 401
  • 4
  • 12
  • second one jst keeps going till my machine doesnt respond & i have to hard boot it – ksm Nov 22 '10 at 18:57
  • It might be worth noting for future visitors of this post that .net 4.5 removes this limitation if I am reading it correctly. http://msdn.microsoft.com/en-us/library/hh285054(v=vs.110).aspx – TravisWhidden Feb 23 '12 at 04:55

9 Answers9

10

Have you looked up int.MaxValue in the docs? it's the equivalent of 2GB, which is probably more RAM than you have available for a contiguous block of 'a' characters - that is what you are asking for here.

http://msdn.microsoft.com/en-us/library/system.int32.maxvalue.aspx

Your infinite loop will eventually cause the same exception (or a different one indirectly related to overuse of RAM), but it will take a while. Try increasing 1024 to 10 * 1024 * 1024 to reproduce the symptom faster in the loop case.

When I run with this larger string size, I get the exception in under 10 seconds after 68 loops (checking i).

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
  • yup. i understand that fact. i come from the JAVA world, the VM would scream to an halt if there were no more system memory available to allocate. but in .net, particularly the second example... i can virtually bring the system to a non-responsive state & the VM never makes a noise... whats up with that? – ksm Nov 22 '10 at 18:31
  • So, in .Net you will get an `OutOfMemoryException` instead. – Steve Townsend Nov 22 '10 at 18:33
  • in JAVA i cannot add a new string to a list endlessly, JVM does give the error... either its a very large string (as in case 1) or adding many many smaller strings to a list... in both cases the JVM would give the error. – ksm Nov 22 '10 at 18:38
  • Trust me, if you let this run long enough .Net will barf too. What's your default JVM RAM pool set to? I've seen this at 64MB or so - I expect you have way more RAM than that to eat up 1K at a time in the 2nd snippet in your q. Try with a bigger increment in .Net to see how long it takes. – Steve Townsend Nov 22 '10 at 18:38
  • this behavior has baffled me too, hence i've come seeking you guys... i was just playing around with .NET & crashing is fun. JVM gives me -Xmx & -Xms args to play with it, i can get it to cough up sooner than later... so what this tells me is that JVM _never_ goes into HDD paging???? – ksm Nov 22 '10 at 18:47
  • I don't know for sure (not a Java programmer) but I would think that if you use (say) -Xms4096m, you will either blow up right away or page then RAM is exhausted. – Steve Townsend Nov 22 '10 at 18:51
  • i even tried the second prog with try/catch around it * just let it wait... all i see is my RAM get eaten up but i didnt see OOM. – ksm Nov 22 '10 at 18:55
  • Are you sure you don't really want to be a tester? See edit, your second code sample gives an exception quickly with a larger `string`. – Steve Townsend Nov 22 '10 at 19:01
7

Your

new string('a', int.MaxValue);

throws an OutOfMemoryException simply because .NET's string has a length limitation. The "Remarks" section in the MSDN docs says:

The maximum size of a String object in memory is 2 GB, or about 1 billion characters.

On my system (.NET 4.5 x64) new string('a', int.MaxValue/2 - 31) throws, whereas new string('a', int.MaxValue/2 - 32) works.

In your second example, the infinite loop allocates ~2048 byte blocks until your OS cannot allocate any more block in the virtual address space. When this is reached, you'll get an OutOfMemoryException too.

(~2048 byte = 1024 chars * 2 bytes per UTF-16 code point + string overhead bytes)

Try this great article of Eric.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
ulrichb
  • 19,610
  • 8
  • 73
  • 87
5

Because int.MaxValue is 2,147,483,647, or, 2 gigabytes which needs to be allocated contiguously.

In the second example, the OS only needs to find 1024 bytes to allocate each time and can swap to hard-drive. I am sure if you left it running long enough you'd end up in a dark place :)

Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
  • 1
    i did end up in a (very) dark place :) will the VM _never_ tell me that i am going to run out of heap? i can just add numerous small variables to memory... for ever? – ksm Nov 22 '10 at 18:41
4

The String object can use a backing shared string pool to reduce memory usage. In the former case, you're generating one string thats several gigabytes. In the second case, its likely the compiler is auto-interning the string, so you're generating a 1024 byte string, and then referencing that same string many times.

That being said, an ArrayList of that size should run you out of memory, but its likely you haven't let the code run long enough for it to run out of memory.

Kent Murra
  • 234
  • 1
  • 2
  • Actually, the string ctor won't use the shared pool. – SLaks Nov 22 '10 at 18:33
  • -1 This is a runtime generated string it will not be interned. – Tim Lloyd Nov 22 '10 at 18:37
  • i did let it run... in fact, initially i had run the prog without any delay between subsequent allocations & my computer stopped responding in less than 10s... – ksm Nov 22 '10 at 18:38
  • SLAks, chibacity: You're right, my assumption was that the compiler would be smart enough to recognize that the parameters are constant and thus optimize it to be auto interned. – Kent Murra Nov 22 '10 at 18:42
3

The 2nd snippet will crash as well. It just takes a wholeheckofalot longer since it is consuming memory much slower. Pay attention to your hard disk access light, it's furiously blinking while Windows chucks pages out of RAM to make room. The first string constructor immediately fails since the heap manager won't allow you to allocate 4 gigabytes.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    @Moo: Chars are two bytes wide. – SLaks Nov 22 '10 at 18:41
  • correct yes. i am trying to point at the way my program gets my machine to behave. in case one, simple BAM! crash and i see an outofmemoryexception case you, it goes into HDD paging etc etc & gets my system to be non responsive since no programs can page properly! not even my task manager... when i did let it long enough, the VM did not kick in & terminate, rather my system just blanked out :) – ksm Nov 22 '10 at 18:43
  • It is called 'trashing'. The paging faults can indeed make the machine nearly unusable if it doesn't have a lot of RAM or the hard disk is slow or has a badly fragmented paging file. – Hans Passant Nov 22 '10 at 18:48
2

Both versions will cause an OOM exception, it's just that (on a 32bit machine) you will get it immediately with the first version when you try to allocate a "single" very large object.

The second version will take much longer however as there will be a lot of thrashing to get to the OOM condition for a couple of factors:

  • You will be allocating millions of small objects which are all reachable by the GC. Once you start putting the system under pressure, the GC will spend an inordinate amount of time scanning generations with millions and millions of objects in. This will take a considerable amount of time and start to play havoc with paging as cold and hot memory will be constantly paged in and out as generations are scanned.

  • There will be page thrashing as GC scans millions of objects in generations to try and free memory. Scanning will cause huge amounts of memory to be paged in and out constantly.

The thrashing will cause the system to grind to a halt processing overhead and so the OOM condition will take a long time to be reached. Most time will be spent thrashing on the GC and paging for the second version.

Tim Lloyd
  • 37,954
  • 10
  • 100
  • 130
1

In your first sample you are trying to create a 2g string at one time

In the second example you keep adding 1k to an array. You will need to loop more than 2 million times to reach the same amount of consumption.

And it's also not all stored at once, in one variable. Thus, some of your memory usage can be persisted to disk to make room for the new data, I think.

CaffGeek
  • 21,856
  • 17
  • 100
  • 184
1

Because a single object cannot have more than 2 GB:

First some background; in the 2.0 version of the .Net runtime (CLR) we made a conscious design decision to keep the maximum object size allowed in the GC Heap at 2GB, even on the 64-bit version of the runtime

In your first example, you try to allocate one object that 2 GB, with the object overhead (8 Bytes?) it's simply too big.

I don't know how the ArrayList works internally, but you allocate multiple objects of 2 GB each and the ArrayList - to my knowledge - only holds pointers which are 4 (8 on x64?) Bytes, regardless how big the object they point to is.

To quote another article:

Also, objects that have references to other objects store only the reference. So if you have an object that holds references to three other objects, the memory footprint is only 12 extra bytes: one 32-bit pointer to each of the referenced objects. It doesn't matter how large the referenced object is.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Michael Stum
  • 177,530
  • 117
  • 400
  • 535
0

One reason your system could be coming to a halt is because .NET's code runs closer to the metal and you're in a tight loop which should consume 100% CPU provided the process priority allows it to. If you would like to prevent the application from consuming too much CPU while it performs the tight loop you should add something like System.Threading.Thread.Sleep(10) to the end of the loop, which will forcibly yield processing time to other threads.

One major difference between the JVM and .NET's CLR (Common Language Runtime) is that the CLR does not limit the size of your memory on an x64 system/application (in 32bit applications, without the Large Address Aware flag the OS limits any application to 2GB due to addressing limitations). The JIT compiler creates native windows code for your processing architecture and then runs it in the same scope that any other windows application would run. The JVM is a more isolated sandbox which constrains the application to a specified size depending on configuration/command line switches.

As for differences between the two algorithms:

The single string creation is not guaranteed to fail when running in an x64 environment with enough contiguous memory to allocate the 4GB necessary to contain int.MaxValue characters (.NET strings are Unicode by default, which requires 2 bytes per character). A 32 bit application will always fail, even with the Large Address Aware flag set because the maximum memory is still something like 3.5GB).

The while loop version of your code will likely consume more overall memory, provided you have plenty available, before throwing the exception because your strings can be allocated in smaller fragments, but it is guaranteed to hit the error eventually (although if you have plenty of resources, it could happen as a result of the ArrayList exceeding the maximum number of elements in an array rather than the inability to allocate new space for a small string). Kent Murra is also correct about string interning; you will either need to randomize the length of the string or the character contents to avoid interning, otherwise you're simply creating pointers to the same string. Steve Townsend's recommendation to increase string length would also make finding large enough contiguous blocks of memory harder to come by, which will allow the exception to happen more quickly.

EDIT:

Thought I'd give some links people may find handy for understanding .NET memory:

These two articles are a little older, but very good in depth reading:

Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework

Garbage Collection Part 2: Automatic Memory Management in the Microsoft .NET Framework

These are blogs from a .NET Garbage Collection developer for information about newer version of .NET memory management:

So, what’s new in the CLR 4.0 GC?

CLR 4.5: Maoni Stephens - Server Background GC

This SO Question may help you observe the inner workings of .NET memory:

.NET Memory Profiling Tools

Community
  • 1
  • 1
TheXenocide
  • 1,060
  • 8
  • 22