Relation between garbage collection and swapping,paging - java

I am a newbie to Java. I understand what garbage collection and paging, swapping do in isolation. I wanted to understand how they relate to each other. Does GC cause paging or swapping? I know that the primary job of GC is to reclaim memory. But does it reclaim by paging or swapping if needed or is it not relevant to GC and is done by OS?

To understand the relation, note that Java performs generational garbage collection. There is a young and and old generation of objects allocated on the heap. From the JVM's point of view, it will not care about swapping but use the heap size it was configured with. However, the heap size will of course dictate the swapping behaviour of the OS that manages the JVM process.
In the young generation's collection, only rather new objects are collected. These objects should not have been swapped out by the OS due to their recent allocation. Of course, if you chose a size bigger than your RAM for your young generation, swapping will be required even for collecting the young generation what will slow down your garbage collector.
In the tenured generation, the performance of garbage collection will firstly depend on the strategy for collection. Consider a naive algorithm that performs a full garbage collection. This algorithm will have to check the entire application's object graph what requires access to the entire JVM heap. Obviously, the entire heap should fit into the RAM. Otherwise, a lot of swapping will be required for garbage collection what will result in a bad performance. In reality, the collector will not check the entire object graph. However, it remains a good practise to choose a heap size that fits into your RAM in order to avoid excessive swapping when for example configuring a Java application's production server.

Actually, I am a newbie in Java world too. I come here because I get confused with this just like you. However, if you thinking about the mechanism of swapping, you may find the difference between swap and garbage collection. swapping is a behaviour of operator system. and garbage collection is a behaviour of Java jvm which is a process/thread of the system.
So, swapping and garbage collection is different level thing. If memory is not enough, swapping will happen to figure out memory that unused temporary and then swapping out.
Above all, swapping may happen before full GC. and at the same time, garbage collection i.e. full gc. also may happen without swapping. A simple example is that: we config the JVM with a small heap size . bug we allocate a large buffer space, at this situation, full GC may happen and also OOM may happen.

Related

Garbage Collector does not release memory on his own

Why does my garbage collector(G1 GC) do not release memory altough he could?
The Used Heap is going up over time, the step fall at the end is because I forced the GC to release memory through jcmd <pid> GC.run
Why is that? Is that normal behaviour?
I started the GC with these parameter -Xms64m -Xmx256m -XX:G1ReservePercent=50
That graph looks like normal behavior.
G1 GC is a generational collector. That means that the heap is divided into (in this case) 2 generations, and the new generation is collected more frequently than the old one. Objects that survive a number of new generation collections get promoted to the old generation. The new generation is relatively small and is GC'd frequently. The old generation is larger, but it is only GC'd when it is starting to get full.
So ... the graph shows a series of "fine" sawtooths that correspond to the new generation collections. The line trends upwards as survivor objects get promoted to the old generation. And the old generation collection is not running ... because the JVM hasn't figured that the old generation is full enough to warrant collection.
Then, you called jcmd <pid> GC.run. That triggered an old generation collection, and you got back about 10MB of memory.
Then the new collection pattern resumed.
Q: Is this normal?
A: Yes.
Q: Is there a problem here?
A: There is no evidence of a problem. Certainly not with the GC itself. It is possible that your application has a memory leak, but there is no clear evidence one way or the other.
Q: What if you hadn't called jcmd <pid> GC.run?
A: At some point, the JVM will decide that the old generation is full enough to warrant starting an old generation collection. There is no need to force it. In fact, it is generally a bad idea to force it.
Q: Is this a flaw with GC? Why doesn't the GC release memory when >I< think it is free?
A: No it isn't a flaw. This is by design.
There is a trade-off between the (supposed) benefit of reclaiming memory early versus the cost of running the garbage collector. As a general rule, GC algorithms are most efficient when the ratio of collectable garbage to non-collectable objects is high; i.e. when the heap is getting close (but not too close) to full.
Note that even if you do force the GC to run, the JVM will be reluctant to give memory back to the OS for other processes to use.
It is totally common and its one of the flaws of Garbagecollection.
You can tell the Garbage collector that he should start cleaning your memory but it doesnt mean he will do it.
System.gc();
The official Oracle documentation states that:
Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse

Zig-zag heap memory patterns in Akka http scala service

I've a AKKA-HTTP based service which is written in scala. This service works as a proxy for an API call. It creates a host connection pool for calling API using
https://doc.akka.io/docs/akka-http/current/client-side/host-level.html
The service is integrated with NewRelic and has the attached snapshots
I would like to understand the reasons for this kind of zig-zag patterns even when there is no traffic on the service and the connections in the host-pool gets terminated because of idle-timeout.
Moreover, I would also like to know Does the FULL GC will only occur after it reached a threshold say 7GB? or it can also occur at some other time when there is no traffic?
The service has XmX of 8GB. Moreover, there are also multiple dispatchers(fork-join-executor) which performs multiple tasks.
First, your graphs show a very healthy application. This "chainsaw" pattern is overall seen as a very good thing, without much to worry about.
When exactly a Full GC is going to happen is a bit hard to predict (I would use the word impossible, too). When your "live" objects have nowhere to move (because there is simply no space for that), a Full GC may be triggered. There are certain thresholds of when a concurrent phase (marking) is going to be initiated, but if that results in a Full GC or not is decided later.
Considering that G1 also re-sizes regions (makes them less/more) based on heuristics, and the fact that it can also shrink or grow your heap (up to -Xmx), the exact conditions when a Full GC might happen is not easy to predict (I guess some GC experts that know the exact internal details might be able to do that). Also, G1GC can do partial collections: when it collects young regions + some of the old regions (not all), still making it far better than a Full GC time-wise.
Unfortunately, your point about no traffic is correct. When there is very limited traffic, you might not get a Full GC, but immediately as traffic comes in, such a thing might happen. Old regions might slowly build up during your "limited traffic" and as soon as you have a spike - surprise. There are ways to trigger a Full GC on demand, and though I have heard of such applications that do this - I have not worked with one in practice.
In general with a GC that's not reference-counting, you'll see that zig-zag pattern because memory is only reclaimed when a GC runs.
G1 normally only collects areas of the heap where it expects to find a lot of garbage relative to live objects ("garbage collection" is a bit of a misnomer: it actually involves collecting the live objects and (in the case of a relocating garbage collector like G1) moving the live objects to a different area of the heap, which allows the area it collected in to then be declared ready for new allocations; therefore the fewer live objects it needs to handle, the less work it needs to do relative to the memory freed up).
At a high-level, G1 works by defining an Eden (a young generation) where newly created objects where newly created objects are allocated and it divides Eden into multiple regions with each thread being mapped to a region. When a region fills up, only that region is collected, with the survivors being moved into an older generation (this is simplifying). This continues until the survivor generation is full, at which point the survivor and eden generations are collected, with the surviving survivors being promoted to the old generation, and when the old generation fills up, you have a full GC.
So there isn't necessarily a fixed threshold where a full GC will get triggered, but in general the more heap gets used up, the more likely it becomes that a full GC will run. Beyond that, garbage collectors on the JVM tend to be more or less autonomous: most will ignore System.gc and/or other attempts to trigger a GC.
Conceivably with G1, if you allocated a multi-GiB array at startup, threw away the reference, and then after every period of idleness reallocated an array of the same size as the one you allocated at startup and then threw away the reference, you'd have a decent chance of triggering a full GC. This is because that array is big enough to bypass eden and go straight to the old generation where it will consume heap until the next full GC. Eventually there won't be enough contiguous free space in the old generation to allocate these arrays, and that will trigger a full GC. The only complications to this approach are that:
You'll eventually have to outsmart the JIT optimizer, which will see that you're allocating this array and throwing it away and decide that it doesn't actually have to allocate the array
If you have a long enough busy time that a full GC ran since the last allocate-and-throw-away, there's no guarantee that the allocation of the large array will succeed after a full GC, which will cause an OOM.

Java - Why does GC times increase as heap grows (in terms of capacity)?

At least in old GCs, it holds true. (I know there are new ones like ZGC and Shenandoah that aim to eliminate that)
As far as I know GC keeps tracks of living objects, so shouldn't the GC times be mostly affected by the number of objects (living/needs to be cleared)?
EDIT:
I meant grows in terms of capacity, meaning bigger heap but same utilization of it by the application
Didn't you answer your own question?
As far as I know GC keeps tracks of living objects, so shouldn't the GC times be mostly affected by the number of objects (living/needs to be cleared)?
The more the heap grows, the more live objects it has, the slower the GC (I'm sure there are exceptions to this rule, in particular for minor collections, but that's the rough idea). The number of objects to be cleared is irrelevant, what matters most is the total number of live objects. Now if your heap is growing because you're storing long-lived objects, it might be ok as long as you don't keep on adding more and more of them. Eventually, long-lived objects will move towards the survivor space and will only be subject to major collections and not minor ones. As long as the minor GC always achieve sufficient memory freeing from the young generation, major GC won't be triggered on all objects (which includes long-lived ones).
I have also observed a different behaviour with G1. We had a low-latency application (40ms p99), so we attempted to configure G1 to make very short pauses (can't remember how much, maybe 5ms or so). What happened is that G1 was more or less meeting the 5ms target, but it had to run extremely frequently because 5ms was not enough to cope with all dead objects we had in our heap. Therefore, it's not exactly true to say individual garbage collection runs are going to get slower with increased heap size, however the average time spent in garbage collection in a given period of time is most likely going to increase.
There are many different algorithms that can be used to implement garbage collection. Not all of them exhibit the behaviour you mention.
In the case of your question, you are referring to algorithms that use a form of mark-sweep. If we take the HostSpot JVM as an example, the old generation can be collected using the CMS collector. This uses a marking phase, where all objects that are accessible from application code are marked. Initially, a root set of directly accessible objects (object references on the stack, registers, etc.) is created. Each object in this set has the mark-bit set in its header to indicate it is still in use. All references from these objects are recursively followed and ulitmately every accessible object has the mark-bit set. How long this takes is proportional to the number of live objects, not the size of the heap.
The sweeping phase then has to sweep throught the entire heap, looking for objects with the mark-bit set and determining the gaps between them so that they can be added to free lists. These are used to allocate space for objects being promoted from the young generation. Since the whole heap must be swept, the time this takes is proportional to the size of the heap, regardless of how much live data is in the heap.
In the case of G1, the algorithm is similar but each generation of the heap is divided into regions so that space can be reclaimed in a more efficent way.

Does garbage collection change the object addresses in Java?

I read that garbage collection can lead to memory fragmentation problem at run-time. To solve this problem, compacting is done by the JVM where it takes all the active objects and assigns them contiguous memory.
This means that the object addresses must change from time to time? Also, if this happens,
Are the references to these objects also re-assigned?
Won't this cause significant performance issues? How does Java cope with it?
I read that garbage collection can lead to memory fragmentation problem at run-time.
This is not an exclusive problem of garbage collected heaps. When you have a manually managed heap and free memory in a different order than the preceding allocations, you may get a fragmented heap as well. And being able to have different lifetimes than the last-in-first-out order of automatic storage aka stack memory, is one of the main motivations to use the heap memory.
To solve this problem, compacting is done by the JVM where it takes all the active objects and assigns them contiguous memory.
Not necessarily all objects. Typical implementation strategies will divide the memory into logical regions and only move objects from a specific region to another, but not all existing objects at a time. These strategies may incorporate the age of the objects, like generational collectors moving objects of the young generation from the Eden space to a Survivor space, or the distribution of the remaining objects, like the “Garbage First” collector which will, like the name suggests, evacuate the fragment with the highest garbage ratio first, which implies the smallest work to get a free contiguous memory block.
This means that the object addresses must change from time to time?
Of course, yes.
Also, if this happens,
Are the references to these objects also re-assigned?
The specification does not mandate how object references are implemented. An indirect pointer may eliminate the need to adapt all references, see also this Q&A. However, for JVMs using direct pointers, this does indeed imply that these pointers need to get adapted.
Won't this cause significant performance issues? How does Java cope with it?
First, we have to consider what we gain from that. To “eliminate fragmentation” is not an end in itself. If we don’t do it, we have to scan the reachable objects for gaps between them and create a data structure maintaining this information, which we would call “free memory” then. We also needed to implement memory allocations as a search for matching chunks in this data structure or to split chunks if no exact match has been found. This is a rather expensive operation compared to an allocation from a contiguous free memory block, where we only have to bump the pointer to the next free byte by the required size.
Given that allocations happen much more often than garbage collection, which only runs when the memory is full (or a threshold has been crossed), this does already justify more expensive copy operations. It also implies that just using a larger heap can solve performance issues, as it reduces the number of required garbage collector runs, whereas the number of survivor objects will not scale with the memory (unreachable objects stay unreachable, regardless of how long you defer the collection). In fact, deferring the collection raises the chances that more objects became unreachable in the meanwhile. Compare also with this answer.
The costs of adapting references are not much higher than the costs of traversing references in the marking phase. In fact, non-concurrent collectors could even combine these two steps, transferring an object on first encounter and adapting subsequently encountered references, instead of marking the object. The actual copying is the more expensive aspect, but as explained above, it is reduced by not copying all objects but using certain strategies based on typical application behavior, like generational approaches or the “garbage first” strategy, to minimize the required work.
If you move an object around the memory, its address will change. Therefore, references pointing to it will need to be updated. Memory fragmentation occurs when an object in a contigous (in memory) sequence of objects gets deleted. This creates a hole in the memory space, which is generally bad because contigous chunks of memory have faster access times and a higher probability of fitting in chache lines, among other things. It should be noted that the use of indirection tables can prevent reference updates up to the maximum level of indirection used.
Garbage collection has a moderate performance overhead, not just in Java but in other languages as well, such as C# for example. As how Java copes with this, the strategies for performing garbage collection and how to minimize its impact on performance depends on the particular JVM being used, since each JVM can implement garbage collection however it pleases; the only requirement is that it meets the JVM specification.
However, as a programmer, there are some best practices you should follow to make the best out of garbage collection and to minimze its performance hit on your application. See this, also this, this, this blog post, and this other blog post. You might want to check the JVM specs but it's a bit dense.

Large collection of long lived objects vs garbage collection

In my application I have a large collection of long lived objects. I am using relatively large heap-size of 100gb (xmx and xms) and once my application hits about 30gb of used heap the garbage collector starts a really long stop-the-world pause that can take up to 15 minutes. After a while the application terminates with the gc overhead limit exceeded exception.
I want to keep all of the objects for whole application's lifetime, so freeing any memory is not an option.
I know that one of the solutions would be to use off-heap storage, but I'd like to avoid that at the moment.
The other would be to tune garbage collector's parameters. I tried with different algorithms and young generation sizes, but it didn't help and I don't know where to go from there.
My problem lied in wrong order of jvm parameters. I wasn't aware you have to specify all of them before -jar switch.
I ended up setting the bigger heap size correctly and using G1GC with relatively small young generation (ratio 20). Now the GC pauses are much more manageable.
Performance-wise rewriting the code for off-heap storage would probably be preferable in this use case though.
I deleted my comments to avoid potential confusion.

Categories