<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body style="overflow-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;">I always found the parented class loader structure to be essential in applications like those that Java was initially targeted for.  Applications that all share data, need that data to come from the same class loader, or else there will be a plethora of coder-decoder interchanges that add lots of overhead and lots of problems around versioning.<div> </div><div>In the Jini world, which has been snuffed out by numerous events, I could use remove services which provided delegate service access classes that the application downloaded with a URL class loader.  A Jini application would provide parent class loader assignments directly or indirectly that would allow or disallow data sharing between these services, and applications.</div><div><br></div><div>I wrote a Jini desktop application that allowed me to use JavaSpaces and messaging services with raw objects, no codecs required, because all of the class parenting provided for data sharing, even across</div><div>version changes in the services and their APIs.</div><div><br></div><div>I found that my desktop environment started up much faster when I set the compile limit to 100 or less instead of 10,000 or 100,000 invocations, because at startup, there is a lot of infrastructure in most applications that should be compiled very quickly.  The JIT should almost force itself to compile everything loaded within the first 1 second of execution.</div><div><br></div><div>The size of applications that are building more and more things together are showing the need for better optimization strategies.  I don’t use Java any more, as it’s just too much to deal with all of these modularity changes for the headaches and the lack of solutions to the problems people actually have on the platform.</div><div><br></div><div>Gregg Wonderly  <br id="lineBreakAtBeginningOfMessage"><div><br><blockquote type="cite"><div>On Dec 14, 2024, at 11:10 AM, David Lloyd <david.lloyd@redhat.com> wrote:</div><br class="Apple-interchange-newline"><div><meta charset="UTF-8"><br class="Apple-interchange-newline"><br style="caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><div class="gmail_quote gmail_quote_container" style="caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><div dir="ltr" class="gmail_attr">On Sat, Dec 14, 2024 at 1:57 AM Alan Bateman <<a href="mailto:alan.bateman@oracle.com">alan.bateman@oracle.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><u></u><div>On 14/12/2024 00:02, David Lloyd wrote:<br><blockquote type="cite"><div dir="ltr"><div dir="ltr"><div style="font-family: arial, helvetica, sans-serif;">:<br></div></div><div class="gmail_quote"><div><br></div><div style="font-family: arial, helvetica, sans-serif;">The problem with having one (or a few) broad layer for all named modules is twofold: first, that every module that *might* be needed for an application must be found and loaded before *any* module is able to be loaded. This works in some simpler packaging scenarios but is too startup-heavy in cases with very large numbers of modules. The "--add-modules" switch on the Java launcher is a direct result of forbidding late binding of modules. From a usability perspective, this is already far from ideal, and if you start talking about hundreds or thousands of modules, it becomes completely unworkable. The second problem is that it makes it very difficult to support any kind of dynamicity (for example adding additional plugins/service implementations at run time) since an outsized amount of static analysis must be done to categorize the layers, whereas lazily loading layers solves the problem easily and elegantly.</div></div><br></div></blockquote>If the real issue here is "too startup-heavy" then that might be something to focus on.<br><br>The --add-modules command line option serves many cases where additional modules may be needed. The intention with `--add-modules ALL-DEFAULT` was to help container like applications that in turn load other applications at run-time. The container can of course create a module layer before creating layers for applications and the only real challenge there is the JDK "platform modules", cue the requirement for "dynamic augmentation of platform modules". We only got so far on this topic, but enough for needs such as allow the JMX agent or a Java agent be loaded into a running VM when the modules required to support that are not in the boot layer.<br><br>Module layers works well for plugins and services and are of course created on-demand. I don't think I understand what you mean by "outsized amount of static analysis must be done to categorize the layers", is this a reference to your exploration into multi-parent configurations and one-module-per-layer?<br></div></blockquote><div><br></div><div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;">The reason to lazily load and link modules is basically analogous to the reason that we lazily load and link classes. In fact there is very little difference. While requiring all classes to be preloaded may have some benefits in certain circumstances (take GraalVM for example), it also significantly restricts flexibility in a few important ways, and of course has a heavy performance cost, and so is not generally considered to be a good strategy for application runtimes or even the Java launcher itself.</div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;"><br></div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;">For modules, we face the same issue. I can, for example, create a single application layer with a thousand modules in it. Before the application can start, every JAR has to be opened and every module has to be loaded, which entails parsing or dynamically creating descriptors (generally a combination of these things), each with dozens of support objects for things like dependencies, exports, and services, and then their internal graphs have to be resolved and wired and checked for consistency. So there is a significant performance cost there. But, that is not the only problem. A user application is often a combination of many modules in addition to our own basic modules, plus a significant amount of generated code. The resultant module graph might even have internal consistency problems (for example, having multiple versions of a module, or cyclical dependencies) that can't realistically be resolved socially because many of these modules are going to be from third parties that we may or may not have any control over, and might even be dependencies brought in by the user which we know nothing about. These graphs could be the result of very complex resolution of Maven artifacts for example.</div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;"><br></div></div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;">Now I could be clever and break this application up into different layers that I can lazily resolve as a unit. As designed, layers are strictly hierarchical (i.e. non cyclical). So I need a way to analyze my thousand JARs and find islands of interdependence that can work as a layer (which is to say, that do not need to be encapsulated from "lower" layers or can work as independent layers), for example by analyzing the service graphs of every module. This is not an easy problem. Sometimes an application is logically layered, but usually it is not. Most often, the layers themselves end up being quite large anyway once all the interdependencies are worked out.</div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;"><br></div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;">Layering (or more generally, arranging program modules into a DAG) has always been, in my opinion, a poor way to model a complex program; it creates an arbitrary constraint that never quite pays off in terms of performance, usability, or maintainability (one symptom of the flaws in this design is that services found via multiple routes to a single parent are reported multiple times; another is the parent-first/child-first dichotomy which has subtle implications). We moved from layered class loading to parentless, arbitrary class loader graphs for the JBoss application service about 15 years ago for this reason (one class loader per (pre-JDK9) module), and this move essentially saw the end of "JAR hell" for us and brought many benefits (such as a massive reduction in maintenance costs, better startup time, and much stronger encapsulation) besides. The improvement was so dramatic in fact that it is now my belief that the parent delegation model was essentially a design error. So from my view, the question isn't "why can you not conform to the design of the module system" but rather "why doesn't the module system design conform to the requirements of those who seek to use it". The only way to bring our desired model to the JPMS appears to be by using a layer per module (and a corresponding class loader per module). This way, we don't have to do any analysis of the module graph beyond a single module, and only on demand, which makes things much simpler and faster for us at startup and as the application runs. The application can start up quickly regardless of whether there are only a handful or modules or many thousands of them. Many inconsistencies that don't affect the integrity of the application can be handled gracefully.</div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;"><br></div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;">The service loader problem is the first, and so far only, insurmountable road block for me in pursuing this model, because with separate layers per module, no module can find services from any other module under any circumstances (unless they use a different service loading API that we provide, or use some other provided API to find the layers to search, which as I said before isn't likely to ever happen).</div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;"><br></div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;">One workaround I was playing with was defining a special kind of "reverse dependency" which causes the layers of all modules containing implementations of a service to be parents of the module layers that require it. Obviously this is fragile in the face of cycles though, and service requirements may commonly be cyclical, so it's not really a viable solution for us.</div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;"><br></div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;">Another workaround is to put anything which provides a service in an unnamed module. This actually works fairly well, because then my class loader implementation can reroute services just like we did in the old days. In this case though, the service provider won't be able to use the `provider` method mechanism for service loading. But, any library which expects to function correctly as an unnamed module (i.e. most of them) generally cannot take advantage of this mechanism anyway in most circumstances. We also lose some encapsulation features, and finding the name of a class's module is a bit trickier. We also lose some nice stack trace stuff, but I work around this by naming the class loader with the module name and version, but wrapped in brackets `[` `]` so that we know at a glance that the module is unnamed.</div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;"><br></div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;">My current experiment is to instrument all loaded code and intercept all attempts to load services, and mediate these requests through our own API. I had a problem with the classfile API that held me up last week but I expect that I'll have some results from this next week. That said, it's not a realistic long term solution, because instrumenting classes at run time with the classfile API has too high a cost.</div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;"><br></div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;">What I would really like to see is a way for us to gain control over service loading when using custom layers or class loaders. One idea would be for class loaders to gain an API which is used by the service loader to find all services for a given service type e.g. `protected List<String> findServiceProviders(Class<?> serviceType)`. The default implementation might find all `META-INF/services/*` resources and read the module descriptor of the layer for example. But custom run time systems would be able to find services based on other kinds of dependencies that are not known to the JPMS.</div><div class="gmail_default" style="font-family: arial, helvetica, sans-serif;"><br></div></div><span class="gmail_signature_prefix" style="caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;">--<span class="Apple-converted-space"> </span></span><br style="caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><div dir="ltr" class="gmail_signature" style="caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><div dir="ltr">- DML • he/him</div></div></div></blockquote></div><br></div></body></html>