<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.0 20120330//EN" "JATS-archivearticle1.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink">
  <front>
    <journal-meta />
    <article-meta>
      <title-group>
        <article-title>Improving Performance Through Object Lifetime Profiling: the DataFrame Case</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Sebastian Jordan Montaño</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Nahuel Palumbo</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Guillermo Polito</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Stéphane Ducasse</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Pablo Tesone</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <aff id="aff0">
          <label>0</label>
          <institution>Univ. Lille, Inria</institution>
          ,
          <addr-line>CNRS, Centrale Lille, UMR 9189 CRIStAL, Park Plaza, Parc scientifique de la Haute-Borne, 40 Av. Halley Bât A, 59650 Villeneuve-d'Ascq</addr-line>
          ,
          <country country="FR">France</country>
        </aff>
      </contrib-group>
      <abstract>
        <p>Being capable of profiling the object lifetimes of an application gives information that can be used to optimize the GC performance and improve overall execution time. One can pre-tenure objects based on profiler information, tune the GC parameters, or take decisions about pre-allocating bigger memory segments. However, accessing object lifetimes is dificult because it requires monitoring any object GC reclamation. We developed an open-source lifetime profiler. Our current implementation does not require Virtual Machine modification. It is based on ephemerons and method proxies. We profiled DataFrame and we observed a significant number of objects that lived a long time. We used this information to tune the garbage collector parameters and we got up to 6.8 times of performance improvements.</p>
      </abstract>
      <kwd-group>
        <kwd>eol&gt;memory profiler</kwd>
        <kwd>garbage collector</kwd>
        <kwd>object lifetimes</kwd>
        <kwd>optimization</kwd>
      </kwd-group>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>1. Introduction</title>
      <p>between its allocation time and its finalization time ′   =    −
 . An object’s finalization time is when the GC decides to collect it.</p>
      <p>Understanding object lifetimes requires precise profiling information on the allocations and
deallocations (when an object is reclaimed by the GC). Object allocations can be precisely
identified by instrumenting all allocation sites ( e.g., send the message basicNew). However,
in generational GCs is not possible to precisely know when an object becomes unreachable.
It will be detected when the GC traverses the memory. A long time may have passed when
an object becomes unreachable and when it is reclaimed. Understanding object deallocation
requires instrumenting automatic reclamation algorithms such as GCs. Furthermore,
finalization mechanisms such as ephemerons [8] introduce a high memory overhead that reduces
performance [9].</p>
      <p>We developed an Object Lifetimes Profiler as a plugin of Illimani1. Illimani is a memory
profiler for Pharo[ 10] and it runs on an unmodified Pharo virtual machine. It is available under
an open-source MIT license. Our profiler tracks the object lifetimes of a given application via
instrumentation by controlling the execution whenever an object is allocated. MethodProxies2
is a library that decorates and controls the method execution by executing user-defined behavior
before or after a method’s execution. We used this library to do the instrumentation.</p>
      <p>We evaluated our solution by observing how object lifetimes relate to performance
improvements when tuning the GC. We choose as a case study the loading of a 500 MB dataset into a
DataFrame [11]. We have selected DataFrame3 library for our study because it is often used in
memory-intensive applications such as machine learning, data mining, and data analysis [12].
The profiler gave us object lifetimes.</p>
      <p>We observed that our case study has 25% of long-lived objects that represent 40% of the
allocated memory (Figures 4, 5). Applications that have many objects that live a fairly long time
sufer from performance issues [ 13]. Increasing the GC heap size has a significant impact on GC
performance [7, 4]. With this information, we decided to tune the GC parameters to see if we can
get performance improvements [14, 13, 6]. We chose 5 non-default GC parameter configurations
that increase the heap size and we then benchmarked the loading of the DataFrame with these
configurations. We obtained improvements of up to 6.8 times compared to the default GC
parameters when the number of full garbage collections is reduced.</p>
      <p>Paper’s contributions. This paper makes the following contributions:
• It presents the challenges of lifetime profiling opening the door for future profiler
improvements.
• It introduces a lifetime profiler capable of tracking the object lifetimes for a given
application with the unmodified Pharo VM.</p>
      <p>Paper’s outline. Section 2 explains the automatic memory management in Pharo and the
challenges of computing the object lifetimes; Section 3 explains our solution; Section 4 explains
the validation of our solution profiling a memory-intensive application; Section 5 presents the
discussion; Section 6 presents related work; and Section 7 finishes with the conclusion and the
future work.
1https://github.com/jordanmontt/illimani-memory-profiler
2https://github.com/pharo-contributions/MethodProxies
3https://github.com/PolyMathOrg/DataFrame</p>
    </sec>
    <sec id="sec-2">
      <title>2. Automatic memory management in Pharo</title>
      <p>Pharo has a two-generation garbage collector with a young and an old generation. The newly
allocated objects are allocated in the new generation, also called new space. The old space is
where the objects, after surviving a certain number of GC cycles are promoted. This promoting
process is called tenuring.</p>
      <p>Generational GCs are designed to leverage an empirical property of objects: young objects
are more prone to die while older ones tend to persist in memory [13]. These GCs are configured
to exploit this empirical property.</p>
      <p>
        The new space uses a scavenging algorithm while the old space uses a stop-the-world
markand-compact algorithm [
        <xref ref-type="bibr" rid="ref2 ref3">2, 3</xref>
        ]. An incremental garbage collection executes the scavenging
algorithm while a full garbage collection executes both a scavenging and the stop-the-world
mark-and-compact algorithm. Running a full garbage collection is slower than an
incremental one by orders of magnitude [14]. For this reason, the scavengers are executed orders of
magnitude more often.
      </p>
      <p>Each time that a full garbage collection is executed, the GC traverses the objects that are
in the old space to detect the ones that are not reachable to then reclaim them. An object is
not reachable when it stops being accessible and usable by the application. Afterwards, the
GC frees the memory of all the non-reachable objects. Ephemerons [8] are a new finalization
mechanism that is implemented in the Pharo Virtual Machine version 104. They allow one to
perform a user-defined action when an object is about to be reclaimed by the GC.</p>
      <p>In Pharo, almost all computations are done by sending a message [15]. This is the case for
allocating an object too. In Pharo 11, 4 methods are responsible for allocating objects:
Behavior»basicNew , Behavior»basicNew: , Number»@ , and Array class»new: . These methods are
special methods that are executed natively.</p>
      <p>Understanding object lifetimes requires precise profiling information about the allocations
and the garbage collections. Object allocations can be precisely identified by instrumenting the
allocation sites (e.g., the allocator methods). However, as Pharo has a two-generation GC is not
possible to precisely know when an object becomes unreachable (stops being accessible). A
long time may pass until the object became unreachable and when the GC decided to finalize
it. Detecting when an object is being reclaimed by the automatic memory manager requires
instrumenting the GC. The available finalization mechanisms in Pharo, such as ephemerons [ 8],
introduce a high memory overhead that reduces performance [9].</p>
    </sec>
    <sec id="sec-3">
      <title>3. Profiling object lifetimes</title>
      <p>We instrumented the 4 allocator methods (Behavior»basicNew , Behavior»basicNew: ,
Number»@ , and Array class»new: ) to intercept whenever they are invoked using MethodProxies.
MethodProxies allow one to decorate and control the execution of a method. With the
instrumentation, we are able to capture the exact allocation time. Within the instrumentation, we
also installed an ephemeron for each of the allocated objects that will set its finalization time
when the object will be reclaimed by the GC. We also store the object’s size in memory.
4https://github.com/pharo-project/pharo-vm
basicNew</p>
      <p>Instrumentation</p>
      <p>Array class
returnosbjtehcetalocate3d
referen
ces</p>
      <p>Object: Array</p>
      <p>#123
Ephemeron
#111
key
invokes
1
returns it to the s5ender
finalizer</p>
      <p>Model
allocationTime: n
finalizationTime:</p>
      <p>Eph#e1m11eron</p>
      <p>finalize
ronised
e m
m su
e n
Eph co
1
nro
um
2</p>
      <p>Model
allocationTime: n
finalizationTime: m
Object: Array</p>
      <p>#123
3
key</p>
      <p>Object #123 is
4 garbage collected
Finalization Queue</p>
    </sec>
    <sec id="sec-4">
      <title>4. Improving DataFrame performance through lifetime profiling</title>
      <p>This section describes the efectiveness of our solution. We answer the following research
question:
• Does our lifetime profiler provide information that can lead to improvements in GC
performance?</p>
      <p>Application’s Lifetime Profile</p>
      <p>1. Profile the application
3. Chose GC tuned
parameters based on
object lifetimes and
benchmark information
Application to Profile
2. Benchmark it with the
default GC parameters
Default GC parameters</p>
      <p>benchmark
Tuned GC parameters
benchmark
5. Did the performance improved?
GC tuned
parameters</p>
      <p>4. Benchmark the application
again with the tuned GC parameters</p>
      <p>We respond to this question positively. We evaluated our profiler by profiling the execution
of loading a 500 MB CSV file into a DataFrame. We choose DataFrame as our target application
because it is a memory-intensive application that supports loading big files to do data engineering
and machine learning [12].</p>
      <sec id="sec-4-1">
        <title>4.1. Methodology</title>
        <p>We applied the following methodology for validating our profiler: We profile the execution of a
given application and we then benchmark it to know how much time was spent on garbage
collections. Then, we choose non-default GC parameters taking into consideration the object
lifetimes and the GC spent time. If the profiler reports a significant quantity of long-lived
objects, we choose non-default GC parameters that increase the memory heap and reduce the
number of full garbage collections. With these custom GC parameters, we benchmark again
our target application. We finally validate our profiler comparing the performance with the
tuned GC parameters against the default ones. Figure 3 explains this process.
180 MB
16 MB
)e 1 MB
l
a
c145 KB
s
g
lo 13 KB
(
ryo 1 KB
em116 B
M</p>
        <p>Lifetime in seconds</p>
      </sec>
      <sec id="sec-4-2">
        <title>4.2. Lifetime profiling results</title>
        <p>The profiler gives a density chart of the object lifetimes. We grouped the object lifetimes by
intervals of one second. All objects whose lifetime duration has the same second will be in the
same bucket. In Figure 4, we calculated the density as a function of the actual memory size
occupied by the objects. We can observe that around 40% of memory stayed referenced for a
long time.</p>
        <p>In Figure 5 we calculated the density but in function by the number of objects instead of the
occupied memory. Crossing this information with Figure 4 we get that 25% of the objects that
represent 40% of the GC memory stay referenced for a long time.</p>
      </sec>
      <sec id="sec-4-3">
        <title>4.3. Benchmarking DataFrame</title>
        <p>We benchmarked the loading of a DataFrame but this time without the instrumentation. We
used the default GC parameters when running these benchmarks. To improve the reproducibility
of benchmarking, we used the best developer techniques for the benchmarks [16]: we cut the
internet connexion and stopped all non-vital applications. We run each of the benchmarks
n-times and then we reported the average execution time with the standard deviation. We
used benchy5 as the infrastructure for running the benchmarks. Benchy is an open-source
application that serves as configurable infrastructure to run customizable benchmarks in Pharo.
We benchmarked the loading of 3 CSV files of diferent sizes: 500 MB, 1.6 GB, and 3.1 GB.</p>
        <p>We present the results of the benchmarks for the 3 diferent CSV files in Table 1.
5https://github.com/tesonep/benchy
10,371,664 75%
1,723,096</p>
        <p>The benchmarks that are presented in Table 1 show that there is a significant part of the
execution time spent doing garbage collections, going from 15% to 89%, with the default GC
parameters.</p>
      </sec>
      <sec id="sec-4-4">
        <title>4.4. Tuning garbage collector parameters</title>
        <p>Generational GCs sufer from poor performance when dealing with memory-intensive
applications that generate numerous intermediate objects, which live a fairly long time under
the default GC parameters [17, 13]. Our profiler showed that DataFrame is an application
that produces long-lived objects. DataFrame has 25% of the objects that represent 40% of the
allocated memory that live for a fairly long time (Figures 4, 5). Increasing the GC heap size
has a significant impact on GC performance [7, 4]. One can reduce GC time by tuning the GC
parameters [6]. The benchmarks exhibited optimization opportunities by reducing the garbage
collection time.</p>
        <p>Generational GCs are not optimized for applications with a substantial number of long-lived
objects. Instead, they are specifically configured for applications where objects tend to die
young [13]. We discussed with Pharo experts about Pharo’s GC implementation details. With
this internal knowledge of Pharo’s GC implementation and the DataFrame internals, we chose
the 5 custom GC parameters. We chose by hand these 5 custom GC parameters that we knew
will increase the heap size and reduce the number of garbage collections.</p>
        <p>The Pharo GC has 4 available customizable parameters. In Pharo, the heap size can be seen as
the sum of the new and the old space. One can control the heap size by tuning these customizable
GC parameters.</p>
        <p>• Eden size represents 5/7 of the new space and it is where the objects are allocated; the
other 2/7 of space is used for copying objects.
• Growth headroom is the minimum amount of space the GC will request to the operating
system to allocate
• Shrink threshold is the amount of free space that the GC will manage before giving it
back to the OS.
• GC ratio is a percentage that triggers a full garbage collection when the heap will grow
that percentage.</p>
        <p>Then as explained in Figure 3, we benchmarked the loading of the DataFrame with the 3
diferent CSV files with these 5 GC parameter configurations to see if we reduce the garbage
collection time. We obtained improvements of up to 6.8 times compared to the default GC
parameters.</p>
        <p>Table 2 describes the 5 non-default GC parameters that we chose.</p>
        <p>In the following tables, we present the results that we obtained re-running the 3 benchmarks
with the 5 configurations. For the 500 MB CSV, we got from the benchmark in Table 1 that
15% of the time is spent on garbage collecting. In Table 3 we observe that we increased the
performance up to 1.2 times.</p>
        <p>In Table 4 we observe that we increased the performance up to 1.2 times for loading the 1.6
GB CSV file into the DataFrame. For this CSV file, we got 22% execution passed doing garbage
collections. The performance improvements are the same as the precedent benchmark.</p>
        <p>In Table 5 we observe that we increased the performance up to 6.8 times with configuration 5.
This enhancement can be attributed to the fact that, for this 3.1 GB, 89% of the execution time is
spent doing garbage collections. Configuration 5 has a larger memory footprint compared to
the default parameters. Comparing Configuration 5 with the default GC configuration, when
the GC requires more memory it allocates segments of 512 MB instead of 32 MB and the new
space starts with 512 MB instead of 15 MB.</p>
      </sec>
    </sec>
    <sec id="sec-5">
      <title>5. Discussion</title>
      <p>We describe some challenges of object lifetimes profile that we faced during the writing of this
paper:</p>
      <p>GC custom parameters. We chose the GC parameters arbitrarily based on expert knowledge
of Pharo’s GC implementation and the object lifetimes of our target application. The parameters
were chosen with the objective of increasing the heap size and reducing the number of garbage
collections.</p>
      <p>Cost of the instrumentation. Our implementation relies on instrumentation. We measured
the impact of it and we got an overhead of at least 50 times According to our measurements,
the ephemerons have the biggest impact on the overhead. For this reason, we were not able to
profile loading a bigger CSV file. Nevertheless, the lifetime profile that we got was useful for
taking GC optimization decisions for the 3 diferent CSV files that we benchmarked.</p>
      <p>GC Stress. For tracking the object lifetimes we allocate an object’s model that stores the
allocation and finalization time of the object as long as the size in memory. We also allocate an
ephemeron to finalize the object when it will be collected. This introduces stress to the GC as
we are multiplying the allocations by three.</p>
      <p>Ephemeron’s Queue. When the GC detects that an ephemeron is a candidate for collection,
it puts it into a finalization queue. Once in the queue, it creates a strong reference to its key
object, the one that is being garbage collected. If one of the objects in the queue is the root of a
sub-graph of unreachable objects, this strong reference will avoid the whole object sub-graph
from being collected and it will only collect the root. This can delay the collection of some
objects, increasing their lifetime.</p>
      <p>Nepotism. Nepotism [13] happens when tenured garbage makes its referenced objects,
which are also garbage, to be tenured too. This happens when an object that is about to die
gets tenured. As the object is now in the old space, until a full GC is triggered, the GC will not
detect that the object became unreachable, it will treat it as reachable until the next run of the
full garbage collection. If this tenured garbage references other objects, those objects become
candidates for being tenured and can eventually get tenured too. This afects negatively the GC
performance since it passes time copying and updating references of garbage. This problem is
not specific to our profiler nor to the instrumentation but to the generational GCs themselves.</p>
    </sec>
    <sec id="sec-6">
      <title>6. Related work</title>
      <p>Automatic GC parameter tuning. Lengauer et al. [6] proposed a technique to automatically
tune the GC parameters for the Java programming language. They used an optimization
algorithm that adjust the parameters with the objective function being the aggregated garbage
collection time. The authors ran their experiments on the Java 8 HotspotTM VM. The diference
with our work is that they optimized the GC performance automatically in a black-box manner
while we used the object lifetimes information given by our profiler to take decisions about the
appropriate GC parameters.</p>
      <p>Adaptative tenuring policies. Ungar et al. [13] instrumented a Smalltalk VM to track the
object lifetimes. They use this information to change the tenuring policies to reduce the amount
of tenuring garbage. Their objective was to optimize the GC performance. Their work difers
from ours in the sense that they used to object lifetimes information to change the tenuring
policies while we used it to tune the GC parameters.</p>
      <p>GC infrastructure evaluation. Kaleba et al. [17] benchmarked the GC of the Cog VM for
one memory-intensive application. The diference with our work is that we used the object
lifetimes profile to choose the custom GC parameters for our application. We then benchmarked
our application with these diferent GC configurations and compared their performance. Kaleba
et at. ran their benchmarks with diferent GC algorithms and modified only one GC parameter:
the Eden size. They chose the Eden size value with their application’s knowledge without any
profile information.</p>
      <p>Profiled-based pretenuring Blackburn [5] et al. used profile information to make decisions
for pre-tenuring objects. In their approach, they took objects allocation information such as the
object lifetimes to pre-tenure objects for increasing performance. Our approach is diferent in
the sense that we use the object lifetimes to tune the GC parameters.</p>
      <p>Hybrid finalization mechanism . Valloud [9] implemented a new finalization mechanism
for the HPS Smalltalk VM. Valloud explains that there were performance issues with the two
available finalization mechanisms: Weak Arrays and Ephemerons. Weak arrays are described
as ineficient and ephemerons introduce a large memory overhead. The author developed a
new finalization mechanism that combines both approaches for improving performance. We
observed the same large memory overhead caused by the ephemerons in our use case.</p>
    </sec>
    <sec id="sec-7">
      <title>7. Conclusion and future work</title>
      <p>We developed a lifetime profiler that tracks the lifetime of objects that were allocated during
the execution of an application. It tracks the object lifetimes of a given application via
instrumentation by controlling the execution whenever an object is allocated. We profiled the object
lifetimes of a DataFrame when loading a big CSV file. We evaluated our solution by observing
how object lifetimes relate to performance improvements when tuning the GC. We obtained
a profile that exhibited that there were 25% of objects that represent 40% of the memory that
lived for a fairly long time.</p>
      <p>We discussed with Pharo experts about the implementation details of Pharo’s GC. With the
knowledge of how Pharo’s GC is implemented and the fact that DataFrame produces a fairly
large number of long-lived objects, we chose 5 GC parameter configurations that increase the
heap size. We then benchmarked the loading of 3 big CSV files into a DataFrame with these 5
custom parameters and we observed a performance increase up to 6.8 times compared with the
default GC parameters.</p>
      <p>As future work, we plan to profile the object lifetimes at the virtual machine level to reduce
the overhead that the instrumentation introduces. To mitigate the GC stress we want to explore
the idea of sampling the object allocations and compare the sampling precision with taking all
the allocations. We also aim to explore automated techniques for detecting an optimal set of GC
parameters to enhance an application’s performance. Finally, we also want to explore taking
pre-tenuring decisions using the object lifetimes profile information.</p>
    </sec>
    <sec id="sec-8">
      <title>8. Acknowledgments</title>
      <p>We express our gratitude to the anonymous reviewers for providing valuable comments on the
draft of this paper.
Virtual Machines and Intermediate Languages (VMIL’18), ACM, 2018, pp. 57–66. doi:10.
1145/3281287.3281295.
[4] T. Brecht, E. Arjomandi, C. Li, H. Pham, Controlling garbage collection and heap growth
to reduce the execution time of java applications, ACM Sigplan Notices 36 (2001) 353–366.
[5] S. M. Blackburn, M. Hertz, K. S. Mckinley, J. E. B. Moss, T. Yang, Profile-based pretenuring,</p>
      <p>ACM Transactions on Programming Languages and Systems (TOPLAS) 29 (2007) 2–es.
[6] P. Lengauer, H. Mössenböck, The taming of the shrew: Increasing performance by
automatic parameter tuning for java garbage collectors, in: Proceedings of the 5th ACM/SPEC
international conference on Performance engineering, 2014, pp. 111–122.
[7] T. Yang, M. Hertz, E. D. Berger, S. F. Kaplan, J. E. B. Moss, Automatic heap sizing: Taking
real memory into account, in: Proceedings of the 4th international symposium on Memory
management, 2004, pp. 61–72.
[8] B. Hayes, Ephemerons: A new finalization mechanism, in: International Conference on
Object-Oriented Programming Systems Languages and Applications (OOPSLA’97), 1997.
doi:10.1145/263700.263733.
[9] A. Valloud, Linked weak reference arrays: A hybrid approach to eficient bulk finalization,
in: Proceedings of the International Workshop on Smalltalk Technologies, 2015, pp. 1–6.
[10] S. Ducasse, G. Rakic, S. Kaplar, Q. D. O. written by A. Black, S. Ducasse, O. Nierstrasz,
D. P. with D. Cassou, M. Denker, Pharo 9 by Example, Book on Demand – Keepers of the
lighthouse, 2022. URL: http://books.pharo.org.
[11] O. Zaytsev, N. Papoulias, S. Stinckwich, Towards exploratory data analysis for pharo, in:
Proceedings of the 12th edition of the International Workshop on Smalltalk Technologies,
2017, pp. 1–6.
[12] O. Zaitsev, S. Jordan Montaño, S. Ducasse, How fast is ai in pharo?
benchmarking linear regression, in: IWST 2022-International Workshop on Smalltalk
Technologies, 2022.
[13] D. Ungar, F. Jackson, An adaptive tenuring policy for generation scavengers, ACM</p>
      <p>Transactions on Programming Languages and Systems (TOPLAS) 14 (1992) 1–27.
[14] D. Ungar, F. Jackson, Tenuring policies for generation-based storage reclamation, in:</p>
      <p>Proceedings OOPSLA ’88, volume 23, 1988, pp. 1–17.
[15] A. Bergel, Counting messages as a proxy for average execution time in pharo,
in: Proceedings of the 25th European Conference on Object-Oriented Programming
(ECOOP’11), LNCS, Springer-Verlag, 2011, pp. 533–557. URL: http://bergel.eu/download/
papers/Berg11c-compteur.pdf.
[16] A. Georges, D. Buytaert, L. Eeckhout, Statistically rigorous java performance evaluation, in:
Proceedings of the 22nd Annual ACM SIGPLAN Conference on Object-Oriented
Programming Systems, Languages and Applications, OOPSLA ’07, Association for Computing
Machinery, New York, NY, USA, 2007, pp. 57–76. URL: https://doi.org/10.1145/1297027.1297033.
doi:10.1145/1297027.1297033.
[17] S. Kaleba, C. Béra, E. Miranda, Garbage collection evaluation infrastructure for the cog vm,
in: Implementation, Compilation, Optimization of Object-Oriented Languages, Programs
and Systems Workshop, ICOOOLPS’18, 2018.</p>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [1]
          <string-name>
            <given-names>R.</given-names>
            <surname>Jones</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Hosking</surname>
          </string-name>
          ,
          <string-name>
            <surname>E. Moss,</surname>
          </string-name>
          <article-title>The garbage collection handbook: the art of automatic memory management</article-title>
          , CRC Press,
          <year>2016</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [2]
          <string-name>
            <given-names>G.</given-names>
            <surname>Polito</surname>
          </string-name>
          ,
          <string-name>
            <given-names>P.</given-names>
            <surname>Tesone</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Privat</surname>
          </string-name>
          ,
          <string-name>
            <given-names>N.</given-names>
            <surname>Palumbo</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Ducasse</surname>
          </string-name>
          ,
          <article-title>Heap fuzzing: Automatic garbage collection testing with expert-guided random events</article-title>
          ,
          <source>in: International Conference on Software Testing</source>
          ,
          <year>2023</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref3">
        <mixed-citation>
          [3]
          <string-name>
            <given-names>E.</given-names>
            <surname>Miranda</surname>
          </string-name>
          ,
          <string-name>
            <given-names>C.</given-names>
            <surname>Béra</surname>
          </string-name>
          ,
          <string-name>
            <given-names>E. G.</given-names>
            <surname>Boix</surname>
          </string-name>
          ,
          <string-name>
            <given-names>D.</given-names>
            <surname>Ingalls</surname>
          </string-name>
          ,
          <article-title>Two decades of Smalltalk VM development: live VM development through simulation tools</article-title>
          , in: Proceedings of International Workshop on
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>