[{"data":1,"prerenderedAt":1619},["ShallowReactive",2],{"blog-\u002Fblog\u002Fjava-classloading":3},{"id":4,"title":5,"body":6,"description":1605,"difficulty":1606,"extension":1607,"framework":1608,"frameworkSlug":150,"meta":1609,"navigation":566,"order":199,"path":1610,"qaPath":1611,"seo":1612,"stem":1613,"subtopic":1614,"topic":1615,"topicSlug":1616,"updated":1617,"__hash__":1618},"blog\u002Fblog\u002Fjava-classloading.md","Java Class Loading Explained — ClassLoaders, Delegation, and Metaspace Leaks",{"type":7,"value":8,"toc":1586},"minimark",[9,14,23,27,39,62,65,69,72,82,112,128,146,215,219,226,232,250,254,259,269,273,276,282,306,312,316,327,452,459,463,481,622,625,629,632,697,785,802,806,820,1030,1039,1042,1046,1056,1062,1116,1134,1138,1149,1156,1159,1193,1310,1324,1328,1331,1435,1459,1463,1474,1497,1540,1548,1552,1582],[10,11,13],"h2",{"id":12},"why-class-loading-comes-up-in-interviews","Why class loading comes up in interviews",[15,16,17,18,22],"p",{},"Class loading underpins some of the trickiest Java bugs: ",[19,20,21],"code",{},"ClassCastException"," despite\nidentical class names, Metaspace growth after hot-redeploy, JDBC drivers that silently\nfail to register, and plugin systems that conflict with the main application. If you\nunderstand the ClassLoader model, these failures all become predictable rather than\nmysterious. That's why senior-level interviews probe it.",[10,24,26],{"id":25},"when-does-the-jvm-load-a-class","When does the JVM load a class?",[15,28,29,30,34,35,38],{},"The JVM loads classes ",[31,32,33],"strong",{},"lazily"," — a ",[19,36,37],{},".class"," file is not read until the class is first\nactively used. \"Actively used\" means one of:",[40,41,42,49,52,59],"ul",{},[43,44,45,46],"li",{},"An instance of the class is created with ",[19,47,48],{},"new",[43,50,51],{},"A static field or method of the class is accessed",[43,53,54,55,58],{},"The class is named in a ",[19,56,57],{},"Class.forName()"," call",[43,60,61],{},"The class is a subclass or superinterface of a class that is actively used",[15,63,64],{},"This means your application can start even if some optional classes are missing on the\nclasspath, as long as those code paths are never exercised at runtime.",[10,66,68],{"id":67},"the-three-built-in-classloaders","The three built-in ClassLoaders",[15,70,71],{},"The JVM ships with a hierarchy of ClassLoaders:",[73,74,79],"pre",{"className":75,"code":77,"language":78},[76],"language-text","Bootstrap ClassLoader  (native, Java object = null)\n        │\n        ▼\nPlatform ClassLoader  (Java 9+, formerly Extension CL)\n        │\n        ▼\nApplication ClassLoader  (loads your classpath)\n","text",[19,80,77],{"__ignoreMap":81},"",[15,83,84,87,88,91,92,95,96,99,100,103,104,107,108,111],{},[31,85,86],{},"Bootstrap ClassLoader"," — implemented in native code; no Java object. Loads the core\nJDK classes from ",[19,89,90],{},"java.base"," (and other core modules in Java 9+): ",[19,93,94],{},"java.lang.*",",\n",[19,97,98],{},"java.util.*",", ",[19,101,102],{},"java.io.*",", etc. This is why ",[19,105,106],{},"String.class.getClassLoader()"," returns\n",[19,109,110],{},"null",".",[15,113,114,117,118,99,121,99,124,127],{},[31,115,116],{},"Platform ClassLoader"," (Java 9+, Extension ClassLoader in Java 8) — loads the\nremaining JDK named modules (",[19,119,120],{},"java.sql",[19,122,123],{},"java.logging",[19,125,126],{},"java.xml",", security providers,\netc.).",[15,129,130,133,134,137,138,141,142,145],{},[31,131,132],{},"Application ClassLoader"," — loads classes from the user classpath (",[19,135,136],{},"-cp"," \u002F ",[19,139,140],{},"-classpath",")\nor module path. This is ",[19,143,144],{},"ClassLoader.getSystemClassLoader()"," and is what loads your\napplication code.",[73,147,151],{"className":148,"code":149,"language":150,"meta":81,"style":81},"language-java shiki shiki-themes github-light github-dark","System.out.println(String.class.getClassLoader());     \u002F\u002F null (Bootstrap)\nSystem.out.println(Connection.class.getClassLoader()); \u002F\u002F PlatformClassLoader (java.sql)\nSystem.out.println(MyApp.class.getClassLoader());      \u002F\u002F AppClassLoader\n","java",[19,152,153,179,197],{"__ignoreMap":81},[154,155,158,162,166,169,172,175],"span",{"class":156,"line":157},"line",1,[154,159,161],{"class":160},"sVt8B","System.out.",[154,163,165],{"class":164},"sScJk","println",[154,167,168],{"class":160},"(String.class.",[154,170,171],{"class":164},"getClassLoader",[154,173,174],{"class":160},"());     ",[154,176,178],{"class":177},"sJ8bj","\u002F\u002F null (Bootstrap)\n",[154,180,182,184,186,189,191,194],{"class":156,"line":181},2,[154,183,161],{"class":160},[154,185,165],{"class":164},[154,187,188],{"class":160},"(Connection.class.",[154,190,171],{"class":164},[154,192,193],{"class":160},"()); ",[154,195,196],{"class":177},"\u002F\u002F PlatformClassLoader (java.sql)\n",[154,198,200,202,204,207,209,212],{"class":156,"line":199},3,[154,201,161],{"class":160},[154,203,165],{"class":164},[154,205,206],{"class":160},"(MyApp.class.",[154,208,171],{"class":164},[154,210,211],{"class":160},"());      ",[154,213,214],{"class":177},"\u002F\u002F AppClassLoader\n",[10,216,218],{"id":217},"the-parent-delegation-model","The parent-delegation model",[15,220,221,222,225],{},"Before loading a class, a ClassLoader always ",[31,223,224],{},"delegates to its parent first",". If the\nparent (or its parent) can supply the class, that result is used. The current loader\nonly attempts to load the class if the parent chain returns empty-handed.",[73,227,230],{"className":228,"code":229,"language":78},[76],"Thread asks AppClassLoader for \"com.example.MyClass\"\n  → AppCL delegates to PlatformCL\n      → PlatformCL delegates to BootstrapCL\n          → Bootstrap: not in java.base → null\n      ← PlatformCL: not in platform modules → null\n  ← AppCL: not found by parents → AppCL searches classpath → found ✓\n",[19,231,229],{"__ignoreMap":81},[15,233,234,237,238,241,242,245,246,249],{},[31,235,236],{},"Why this matters:"," it ensures that a ",[19,239,240],{},"java\u002Flang\u002FString.class"," placed on the classpath\ncannot shadow the real ",[19,243,244],{},"String",". Bootstrap always wins for JDK classes. This is also\nwhy you cannot override ",[19,247,248],{},"java.lang.String"," simply by putting a different version on your\nclasspath — the Bootstrap loader finds the real one first.",[10,251,253],{"id":252},"the-three-phases-loading-linking-initialisation","The three phases: loading, linking, initialisation",[255,256,258],"h3",{"id":257},"phase-1-loading","Phase 1 — Loading",[15,260,261,262,264,265,268],{},"The ClassLoader reads the binary ",[19,263,37],{}," data from its source (file, JAR, URL, database,\ngenerated bytecode) and creates a ",[19,266,267],{},"Class\u003C?>"," object in Metaspace. The bytecode is stored\nbut not yet verified or executed.",[255,270,272],{"id":271},"phase-2-linking","Phase 2 — Linking",[15,274,275],{},"Linking has three sub-steps:",[15,277,278,281],{},[31,279,280],{},"Verification"," — the bytecode verifier checks that the class file is structurally valid\nand obeys JVM safety rules (no out-of-bounds stack ops, no invalid type conversions).\nMalformed bytecode is rejected here.",[15,283,284,287,288,291,292,99,295,99,297,300,301,305],{},[31,285,286],{},"Preparation"," — memory is allocated for static fields and they are set to their\n",[31,289,290],{},"default values"," (",[19,293,294],{},"0",[19,296,110],{},[19,298,299],{},"false","). The values you declared in the source code\nare ",[302,303,304],"em",{},"not"," assigned yet — that happens in initialisation.",[15,307,308,311],{},[31,309,310],{},"Resolution"," — symbolic references in the constant pool (class names, field names,\nmethod descriptors encoded as strings) are replaced with direct pointers to memory\nlocations. Resolution may be deferred to first use (lazy resolution).",[255,313,315],{"id":314},"phase-3-initialisation","Phase 3 — Initialisation",[15,317,318,319,322,323,326],{},"The class's ",[19,320,321],{},"\u003Cclinit>"," method runs — this is the compiled form of all static initialiser\nblocks and static field assignment expressions, in source order. It runs ",[31,324,325],{},"exactly once",",\nguarded by a JVM-internal lock, so it is thread-safe even under concurrent access.",[73,328,330],{"className":148,"code":329,"language":150,"meta":81,"style":81},"class Config {\n    static final int MAX;\n    static {\n        String val = System.getenv(\"MAX_CONNECTIONS\");\n        MAX = (val != null) ? Integer.parseInt(val) : 100; \u002F\u002F runs in \u003Cclinit>\n    }\n}\n\u002F\u002F MAX is 0 after Preparation; its real value is set when Config is first used\n",[19,331,332,344,358,364,389,434,440,446],{"__ignoreMap":81},[154,333,334,338,341],{"class":156,"line":157},[154,335,337],{"class":336},"szBVR","class",[154,339,340],{"class":164}," Config",[154,342,343],{"class":160}," {\n",[154,345,346,349,352,355],{"class":156,"line":181},[154,347,348],{"class":336},"    static",[154,350,351],{"class":336}," final",[154,353,354],{"class":336}," int",[154,356,357],{"class":160}," MAX;\n",[154,359,360,362],{"class":156,"line":199},[154,361,348],{"class":336},[154,363,343],{"class":160},[154,365,367,370,373,376,379,382,386],{"class":156,"line":366},4,[154,368,369],{"class":160},"        String val ",[154,371,372],{"class":336},"=",[154,374,375],{"class":160}," System.",[154,377,378],{"class":164},"getenv",[154,380,381],{"class":160},"(",[154,383,385],{"class":384},"sZZnC","\"MAX_CONNECTIONS\"",[154,387,388],{"class":160},");\n",[154,390,392,395,397,400,403,407,410,413,416,419,422,425,428,431],{"class":156,"line":391},5,[154,393,394],{"class":160},"        MAX ",[154,396,372],{"class":336},[154,398,399],{"class":160}," (val ",[154,401,402],{"class":336},"!=",[154,404,406],{"class":405},"sj4cs"," null",[154,408,409],{"class":160},") ",[154,411,412],{"class":336},"?",[154,414,415],{"class":160}," Integer.",[154,417,418],{"class":164},"parseInt",[154,420,421],{"class":160},"(val) ",[154,423,424],{"class":336},":",[154,426,427],{"class":405}," 100",[154,429,430],{"class":160},"; ",[154,432,433],{"class":177},"\u002F\u002F runs in \u003Cclinit>\n",[154,435,437],{"class":156,"line":436},6,[154,438,439],{"class":160},"    }\n",[154,441,443],{"class":156,"line":442},7,[154,444,445],{"class":160},"}\n",[154,447,449],{"class":156,"line":448},8,[154,450,451],{"class":177},"\u002F\u002F MAX is 0 after Preparation; its real value is set when Config is first used\n",[15,453,454,455,458],{},"The guarantee: you will never observe a partially-initialised static field from another\nthread ",[302,456,457],{},"after"," the class has finished initialising.",[10,460,462],{"id":461},"class-identity-name-classloader","Class identity: name + ClassLoader",[15,464,465,466,469,470,473,474,476,477,480],{},"A class in the JVM is identified by ",[31,467,468],{},"both"," its fully qualified name ",[31,471,472],{},"and"," the\nClassLoader instance that loaded it. Two ",[19,475,267],{}," objects with the same binary name\nbut different loaders are ",[31,478,479],{},"different types"," — you cannot cast between them.",[73,482,484],{"className":148,"code":483,"language":150,"meta":81,"style":81},"URLClassLoader loader1 = new URLClassLoader(urls, parent);\nURLClassLoader loader2 = new URLClassLoader(urls, parent);\nClass\u003C?> a = loader1.loadClass(\"com.example.Widget\");\nClass\u003C?> b = loader2.loadClass(\"com.example.Widget\");\n\nSystem.out.println(a == b);   \u002F\u002F false\nObject obj = a.getDeclaredConstructor().newInstance();\nb.cast(obj);                  \u002F\u002F ClassCastException — same source, different types\n",[19,485,486,502,515,540,562,568,586,608],{"__ignoreMap":81},[154,487,488,491,493,496,499],{"class":156,"line":157},[154,489,490],{"class":160},"URLClassLoader loader1 ",[154,492,372],{"class":336},[154,494,495],{"class":336}," new",[154,497,498],{"class":164}," URLClassLoader",[154,500,501],{"class":160},"(urls, parent);\n",[154,503,504,507,509,511,513],{"class":156,"line":181},[154,505,506],{"class":160},"URLClassLoader loader2 ",[154,508,372],{"class":336},[154,510,495],{"class":336},[154,512,498],{"class":164},[154,514,501],{"class":160},[154,516,517,520,522,525,527,530,533,535,538],{"class":156,"line":199},[154,518,519],{"class":160},"Class\u003C",[154,521,412],{"class":336},[154,523,524],{"class":160},"> a ",[154,526,372],{"class":336},[154,528,529],{"class":160}," loader1.",[154,531,532],{"class":164},"loadClass",[154,534,381],{"class":160},[154,536,537],{"class":384},"\"com.example.Widget\"",[154,539,388],{"class":160},[154,541,542,544,546,549,551,554,556,558,560],{"class":156,"line":366},[154,543,519],{"class":160},[154,545,412],{"class":336},[154,547,548],{"class":160},"> b ",[154,550,372],{"class":336},[154,552,553],{"class":160}," loader2.",[154,555,532],{"class":164},[154,557,381],{"class":160},[154,559,537],{"class":384},[154,561,388],{"class":160},[154,563,564],{"class":156,"line":391},[154,565,567],{"emptyLinePlaceholder":566},true,"\n",[154,569,570,572,574,577,580,583],{"class":156,"line":436},[154,571,161],{"class":160},[154,573,165],{"class":164},[154,575,576],{"class":160},"(a ",[154,578,579],{"class":336},"==",[154,581,582],{"class":160}," b);   ",[154,584,585],{"class":177},"\u002F\u002F false\n",[154,587,588,591,593,596,599,602,605],{"class":156,"line":442},[154,589,590],{"class":160},"Object obj ",[154,592,372],{"class":336},[154,594,595],{"class":160}," a.",[154,597,598],{"class":164},"getDeclaredConstructor",[154,600,601],{"class":160},"().",[154,603,604],{"class":164},"newInstance",[154,606,607],{"class":160},"();\n",[154,609,610,613,616,619],{"class":156,"line":448},[154,611,612],{"class":160},"b.",[154,614,615],{"class":164},"cast",[154,617,618],{"class":160},"(obj);                  ",[154,620,621],{"class":177},"\u002F\u002F ClassCastException — same source, different types\n",[15,623,624],{},"This is the mechanism that lets Java EE containers, OSGi frameworks, and plugin systems\nisolate applications from each other: each gets its own ClassLoader, so their classes\nnever collide.",[10,626,628],{"id":627},"classnotfoundexception-vs-noclassdeffounderror","ClassNotFoundException vs NoClassDefFoundError",[15,630,631],{},"These two are frequently confused:",[633,634,635,654],"table",{},[636,637,638],"thead",{},[639,640,641,644,649],"tr",{},[642,643],"th",{},[642,645,646],{},[19,647,648],{},"ClassNotFoundException",[642,650,651],{},[19,652,653],{},"NoClassDefFoundError",[655,656,657,669,686],"tbody",{},[639,658,659,663,666],{},[660,661,662],"td",{},"Kind",[660,664,665],{},"Checked exception",[660,667,668],{},"Error (unchecked)",[639,670,671,674,683],{},[660,672,673],{},"Source",[660,675,676,677,679,680],{},"Explicit call to ",[19,678,57],{}," or ",[19,681,682],{},"ClassLoader.loadClass()",[660,684,685],{},"JVM resolving a class reference that was present at compile time but absent at runtime",[639,687,688,691,694],{},[660,689,690],{},"Typical cause",[660,692,693],{},"Missing JAR, typo in class name string",[660,695,696],{},"Deployment error: forgot to include a JAR in the runtime classpath",[73,698,700],{"className":148,"code":699,"language":150,"meta":81,"style":81},"\u002F\u002F ClassNotFoundException — you asked for it by name:\ntry {\n    Class.forName(\"com.mysql.cj.jdbc.Driver\");\n} catch (ClassNotFoundException e) { \u002F* MySQL JAR not on classpath *\u002F }\n\n\u002F\u002F NoClassDefFoundError — JVM couldn't resolve a static reference:\n\u002F\u002F MyService references com.example.Dep, which is missing at runtime\nMyService svc = new MyService(); \u002F\u002F throws NoClassDefFoundError: com\u002Fexample\u002FDep\n",[19,701,702,707,714,729,753,757,762,767],{"__ignoreMap":81},[154,703,704],{"class":156,"line":157},[154,705,706],{"class":177},"\u002F\u002F ClassNotFoundException — you asked for it by name:\n",[154,708,709,712],{"class":156,"line":181},[154,710,711],{"class":336},"try",[154,713,343],{"class":160},[154,715,716,719,722,724,727],{"class":156,"line":199},[154,717,718],{"class":160},"    Class.",[154,720,721],{"class":164},"forName",[154,723,381],{"class":160},[154,725,726],{"class":384},"\"com.mysql.cj.jdbc.Driver\"",[154,728,388],{"class":160},[154,730,731,734,737,740,744,747,750],{"class":156,"line":366},[154,732,733],{"class":160},"} ",[154,735,736],{"class":336},"catch",[154,738,739],{"class":160}," (ClassNotFoundException ",[154,741,743],{"class":742},"s4XuR","e",[154,745,746],{"class":160},") { ",[154,748,749],{"class":177},"\u002F* MySQL JAR not on classpath *\u002F",[154,751,752],{"class":160}," }\n",[154,754,755],{"class":156,"line":391},[154,756,567],{"emptyLinePlaceholder":566},[154,758,759],{"class":156,"line":436},[154,760,761],{"class":177},"\u002F\u002F NoClassDefFoundError — JVM couldn't resolve a static reference:\n",[154,763,764],{"class":156,"line":442},[154,765,766],{"class":177},"\u002F\u002F MyService references com.example.Dep, which is missing at runtime\n",[154,768,769,772,774,776,779,782],{"class":156,"line":448},[154,770,771],{"class":160},"MyService svc ",[154,773,372],{"class":336},[154,775,495],{"class":336},[154,777,778],{"class":164}," MyService",[154,780,781],{"class":160},"(); ",[154,783,784],{"class":177},"\u002F\u002F throws NoClassDefFoundError: com\u002Fexample\u002FDep\n",[15,786,787,788,790,791,794,795,797,798,801],{},"The key: ",[19,789,648],{}," is thrown by ",[302,792,793],{},"your code"," when you dynamically name a\nclass; ",[19,796,653],{}," is thrown by the ",[302,799,800],{},"JVM"," when it fails to resolve a class\nreference baked into compiled bytecode.",[10,803,805],{"id":804},"writing-a-custom-classloader","Writing a custom ClassLoader",[15,807,808,809,812,813,815,816,819],{},"Override ",[19,810,811],{},"findClass()"," — ",[302,814,304],{}," ",[19,817,818],{},"loadClass()"," — to preserve parent delegation:",[73,821,823],{"className":148,"code":822,"language":150,"meta":81,"style":81},"public class EncryptedJarLoader extends ClassLoader {\n\n    private final Path encryptedJar;\n\n    EncryptedJarLoader(Path jar, ClassLoader parent) {\n        super(parent);              \u002F\u002F wire up delegation\n        this.encryptedJar = jar;\n    }\n\n    @Override\n    protected Class\u003C?> findClass(String name) throws ClassNotFoundException {\n        byte[] plain = decrypt(readEntry(encryptedJar, name));\n        if (plain == null) throw new ClassNotFoundException(name);\n        return defineClass(name, plain, 0, plain.length);\n    }\n}\n",[19,824,825,844,848,858,862,882,893,906,910,915,924,955,977,1003,1020,1025],{"__ignoreMap":81},[154,826,827,830,833,836,839,842],{"class":156,"line":157},[154,828,829],{"class":336},"public",[154,831,832],{"class":336}," class",[154,834,835],{"class":164}," EncryptedJarLoader",[154,837,838],{"class":336}," extends",[154,840,841],{"class":164}," ClassLoader",[154,843,343],{"class":160},[154,845,846],{"class":156,"line":181},[154,847,567],{"emptyLinePlaceholder":566},[154,849,850,853,855],{"class":156,"line":199},[154,851,852],{"class":336},"    private",[154,854,351],{"class":336},[154,856,857],{"class":160}," Path encryptedJar;\n",[154,859,860],{"class":156,"line":366},[154,861,567],{"emptyLinePlaceholder":566},[154,863,864,867,870,873,876,879],{"class":156,"line":391},[154,865,866],{"class":164},"    EncryptedJarLoader",[154,868,869],{"class":160},"(Path ",[154,871,872],{"class":742},"jar",[154,874,875],{"class":160},", ClassLoader ",[154,877,878],{"class":742},"parent",[154,880,881],{"class":160},") {\n",[154,883,884,887,890],{"class":156,"line":436},[154,885,886],{"class":405},"        super",[154,888,889],{"class":160},"(parent);              ",[154,891,892],{"class":177},"\u002F\u002F wire up delegation\n",[154,894,895,898,901,903],{"class":156,"line":442},[154,896,897],{"class":405},"        this",[154,899,900],{"class":160},".encryptedJar ",[154,902,372],{"class":336},[154,904,905],{"class":160}," jar;\n",[154,907,908],{"class":156,"line":448},[154,909,439],{"class":160},[154,911,913],{"class":156,"line":912},9,[154,914,567],{"emptyLinePlaceholder":566},[154,916,918,921],{"class":156,"line":917},10,[154,919,920],{"class":160},"    @",[154,922,923],{"class":336},"Override\n",[154,925,927,930,933,935,938,941,944,947,949,952],{"class":156,"line":926},11,[154,928,929],{"class":336},"    protected",[154,931,932],{"class":160}," Class\u003C",[154,934,412],{"class":336},[154,936,937],{"class":160},"> ",[154,939,940],{"class":164},"findClass",[154,942,943],{"class":160},"(String ",[154,945,946],{"class":742},"name",[154,948,409],{"class":160},[154,950,951],{"class":336},"throws",[154,953,954],{"class":160}," ClassNotFoundException {\n",[154,956,958,961,964,966,969,971,974],{"class":156,"line":957},12,[154,959,960],{"class":336},"        byte",[154,962,963],{"class":160},"[] plain ",[154,965,372],{"class":336},[154,967,968],{"class":164}," decrypt",[154,970,381],{"class":160},[154,972,973],{"class":164},"readEntry",[154,975,976],{"class":160},"(encryptedJar, name));\n",[154,978,980,983,986,988,990,992,995,997,1000],{"class":156,"line":979},13,[154,981,982],{"class":336},"        if",[154,984,985],{"class":160}," (plain ",[154,987,579],{"class":336},[154,989,406],{"class":405},[154,991,409],{"class":160},[154,993,994],{"class":336},"throw",[154,996,495],{"class":336},[154,998,999],{"class":164}," ClassNotFoundException",[154,1001,1002],{"class":160},"(name);\n",[154,1004,1006,1009,1012,1015,1017],{"class":156,"line":1005},14,[154,1007,1008],{"class":336},"        return",[154,1010,1011],{"class":164}," defineClass",[154,1013,1014],{"class":160},"(name, plain, ",[154,1016,294],{"class":405},[154,1018,1019],{"class":160},", plain.length);\n",[154,1021,1023],{"class":156,"line":1022},15,[154,1024,439],{"class":160},[154,1026,1028],{"class":156,"line":1027},16,[154,1029,445],{"class":160},[15,1031,1032,1035,1036,1038],{},[19,1033,1034],{},"defineClass()"," passes the raw bytecode to the JVM, which runs verification and\npreparation, then returns a live ",[19,1037,267],{}," object.",[15,1040,1041],{},"Common uses: loading classes from encrypted JARs, generated bytecode (ASM, ByteBuddy),\ndatabases, remote URLs, or creating isolated namespaces for plugin architectures.",[10,1043,1045],{"id":1044},"serviceloader-the-standard-plugin-mechanism","ServiceLoader — the standard plugin mechanism",[15,1047,1048,1049,1052,1053,1055],{},"For discovering and loading interface implementations at runtime, prefer\n",[19,1050,1051],{},"java.util.ServiceLoader"," over rolling your own ",[19,1054,57],{}," dispatch:",[73,1057,1060],{"className":1058,"code":1059,"language":78},[76],"# src\u002Fmain\u002Fresources\u002FMETA-INF\u002Fservices\u002Fcom.example.Plugin\ncom.example.impl.FooPlugin\ncom.example.impl.BarPlugin\n",[19,1061,1059],{"__ignoreMap":81},[73,1063,1065],{"className":148,"code":1064,"language":150,"meta":81,"style":81},"ServiceLoader\u003CPlugin> loader = ServiceLoader.load(Plugin.class);\nfor (Plugin p : loader) {\n    p.execute();\n}\n",[19,1066,1067,1089,1102,1112],{"__ignoreMap":81},[154,1068,1069,1072,1075,1078,1080,1083,1086],{"class":156,"line":157},[154,1070,1071],{"class":160},"ServiceLoader\u003C",[154,1073,1074],{"class":336},"Plugin",[154,1076,1077],{"class":160},"> loader ",[154,1079,372],{"class":336},[154,1081,1082],{"class":160}," ServiceLoader.",[154,1084,1085],{"class":164},"load",[154,1087,1088],{"class":160},"(Plugin.class);\n",[154,1090,1091,1094,1097,1099],{"class":156,"line":181},[154,1092,1093],{"class":336},"for",[154,1095,1096],{"class":160}," (Plugin p ",[154,1098,424],{"class":336},[154,1100,1101],{"class":160}," loader) {\n",[154,1103,1104,1107,1110],{"class":156,"line":199},[154,1105,1106],{"class":160},"    p.",[154,1108,1109],{"class":164},"execute",[154,1111,607],{"class":160},[154,1113,1114],{"class":156,"line":366},[154,1115,445],{"class":160},[15,1117,1118,1119,1122,1123,1126,1127,1130,1131,111],{},"JDBC 4.0+ drivers self-register via ",[19,1120,1121],{},"ServiceLoader","; you no longer need\n",[19,1124,1125],{},"Class.forName(\"com.mysql.cj.jdbc.Driver\")",". In Java 9+ modules, the equivalent is\n",[19,1128,1129],{},"provides com.example.Plugin with com.example.impl.FooPlugin"," in ",[19,1132,1133],{},"module-info.java",[10,1135,1137],{"id":1136},"class-unloading-and-metaspace-leaks","Class unloading and Metaspace leaks",[15,1139,1140,1141,1144,1145,1148],{},"A class can only be unloaded when its ",[31,1142,1143],{},"ClassLoader"," becomes unreachable. JDK classes\n(loaded by Bootstrap) are never unloaded. Application classes (loaded by AppClassLoader)\nare permanent for the JVM's lifetime. Only classes loaded by a ",[31,1146,1147],{},"custom"," ClassLoader\ncan be unloaded — when the loader itself is GC'd.",[15,1150,1151,1152,1155],{},"This is the source of ",[31,1153,1154],{},"Metaspace leaks in hot-deploy scenarios",". Every time you\nredeploy a web app in Tomcat without restarting the JVM, a new ClassLoader is created.\nIf anything outside the old ClassLoader still holds a reference to it (or to any object\nof a class it loaded), the old loader cannot be GC'd — Metaspace grows with each deploy.",[15,1157,1158],{},"Common anchor points:",[40,1160,1161,1171,1177,1187],{},[43,1162,1163,1166,1167,1170],{},[31,1164,1165],{},"ThreadLocals"," — a value whose class was loaded by the webapp ClassLoader, stored in\na server thread's ",[19,1168,1169],{},"ThreadLocal",", never cleared on undeploy.",[43,1172,1173,1176],{},[31,1174,1175],{},"Static fields in shared libraries"," — a library loaded by the parent ClassLoader\nholds a reference to a webapp object (e.g., a listener registered in a static list).",[43,1178,1179,1182,1183,1186],{},[31,1180,1181],{},"JDBC drivers"," — registered globally with ",[19,1184,1185],{},"DriverManager"," but never deregistered.",[43,1188,1189,1192],{},[31,1190,1191],{},"Logging configuration"," — log4j2\u002Flogback holding class references.",[73,1194,1196],{"className":148,"code":1195,"language":150,"meta":81,"style":81},"\u002F\u002F ServletContextListener — cleanup on undeploy:\n@Override\npublic void contextDestroyed(ServletContextEvent sce) {\n    \u002F\u002F Deregister JDBC drivers loaded by this webapp:\n    Enumeration\u003CDriver> drivers = DriverManager.getDrivers();\n    while (drivers.hasMoreElements()) {\n        try { DriverManager.deregisterDriver(drivers.nextElement()); }\n        catch (SQLException ignored) {}\n    }\n    \u002F\u002F Clear ThreadLocals your code set on server threads\n}\n",[19,1197,1198,1203,1210,1223,1228,1249,1263,1283,1297,1301,1306],{"__ignoreMap":81},[154,1199,1200],{"class":156,"line":157},[154,1201,1202],{"class":177},"\u002F\u002F ServletContextListener — cleanup on undeploy:\n",[154,1204,1205,1208],{"class":156,"line":181},[154,1206,1207],{"class":160},"@",[154,1209,923],{"class":336},[154,1211,1212,1214,1217,1220],{"class":156,"line":199},[154,1213,829],{"class":336},[154,1215,1216],{"class":336}," void",[154,1218,1219],{"class":164}," contextDestroyed",[154,1221,1222],{"class":160},"(ServletContextEvent sce) {\n",[154,1224,1225],{"class":156,"line":366},[154,1226,1227],{"class":177},"    \u002F\u002F Deregister JDBC drivers loaded by this webapp:\n",[154,1229,1230,1233,1236,1239,1241,1244,1247],{"class":156,"line":391},[154,1231,1232],{"class":160},"    Enumeration\u003C",[154,1234,1235],{"class":336},"Driver",[154,1237,1238],{"class":160},"> drivers ",[154,1240,372],{"class":336},[154,1242,1243],{"class":160}," DriverManager.",[154,1245,1246],{"class":164},"getDrivers",[154,1248,607],{"class":160},[154,1250,1251,1254,1257,1260],{"class":156,"line":436},[154,1252,1253],{"class":336},"    while",[154,1255,1256],{"class":160}," (drivers.",[154,1258,1259],{"class":164},"hasMoreElements",[154,1261,1262],{"class":160},"()) {\n",[154,1264,1265,1268,1271,1274,1277,1280],{"class":156,"line":442},[154,1266,1267],{"class":336},"        try",[154,1269,1270],{"class":160}," { DriverManager.",[154,1272,1273],{"class":164},"deregisterDriver",[154,1275,1276],{"class":160},"(drivers.",[154,1278,1279],{"class":164},"nextElement",[154,1281,1282],{"class":160},"()); }\n",[154,1284,1285,1288,1291,1294],{"class":156,"line":448},[154,1286,1287],{"class":336},"        catch",[154,1289,1290],{"class":160}," (SQLException ",[154,1292,1293],{"class":742},"ignored",[154,1295,1296],{"class":160},") {}\n",[154,1298,1299],{"class":156,"line":912},[154,1300,439],{"class":160},[154,1302,1303],{"class":156,"line":917},[154,1304,1305],{"class":177},"    \u002F\u002F Clear ThreadLocals your code set on server threads\n",[154,1307,1308],{"class":156,"line":926},[154,1309,445],{"class":160},[15,1311,1312,1313,1316,1317,1320,1321,1323],{},"Detect Metaspace leaks by enabling ",[19,1314,1315],{},"-XX:NativeMemoryTracking=summary"," and running\n",[19,1318,1319],{},"jcmd \u003Cpid> VM.native_memory summary"," before and after redeploy. A growing \"Class\"\nsection confirms a ClassLoader leak. Confirm with a heap dump: look for ",[19,1322,1143],{},"\ninstances that should be dead and trace their retaining references in Eclipse MAT.",[10,1325,1327],{"id":1326},"the-initialization-on-demand-holder-idiom","The Initialization-on-demand Holder idiom",[15,1329,1330],{},"The class initialisation guarantee (runs once, thread-safe) enables a clean lazy-singleton\npattern without explicit locks:",[73,1332,1334],{"className":148,"code":1333,"language":150,"meta":81,"style":81},"public final class ConnectionPool {\n    private ConnectionPool() {}\n\n    private static final class Holder {\n        static final ConnectionPool INSTANCE = new ConnectionPool();\n    }\n\n    public static ConnectionPool getInstance() {\n        return Holder.INSTANCE;\n    }\n}\n",[19,1335,1336,1349,1358,1362,1378,1396,1400,1404,1420,1427,1431],{"__ignoreMap":81},[154,1337,1338,1340,1342,1344,1347],{"class":156,"line":157},[154,1339,829],{"class":336},[154,1341,351],{"class":336},[154,1343,832],{"class":336},[154,1345,1346],{"class":164}," ConnectionPool",[154,1348,343],{"class":160},[154,1350,1351,1353,1355],{"class":156,"line":181},[154,1352,852],{"class":336},[154,1354,1346],{"class":164},[154,1356,1357],{"class":160},"() {}\n",[154,1359,1360],{"class":156,"line":199},[154,1361,567],{"emptyLinePlaceholder":566},[154,1363,1364,1366,1369,1371,1373,1376],{"class":156,"line":366},[154,1365,852],{"class":336},[154,1367,1368],{"class":336}," static",[154,1370,351],{"class":336},[154,1372,832],{"class":336},[154,1374,1375],{"class":164}," Holder",[154,1377,343],{"class":160},[154,1379,1380,1383,1385,1388,1390,1392,1394],{"class":156,"line":391},[154,1381,1382],{"class":336},"        static",[154,1384,351],{"class":336},[154,1386,1387],{"class":160}," ConnectionPool INSTANCE ",[154,1389,372],{"class":336},[154,1391,495],{"class":336},[154,1393,1346],{"class":164},[154,1395,607],{"class":160},[154,1397,1398],{"class":156,"line":436},[154,1399,439],{"class":160},[154,1401,1402],{"class":156,"line":442},[154,1403,567],{"emptyLinePlaceholder":566},[154,1405,1406,1409,1411,1414,1417],{"class":156,"line":448},[154,1407,1408],{"class":336},"    public",[154,1410,1368],{"class":336},[154,1412,1413],{"class":160}," ConnectionPool ",[154,1415,1416],{"class":164},"getInstance",[154,1418,1419],{"class":160},"() {\n",[154,1421,1422,1424],{"class":156,"line":912},[154,1423,1008],{"class":336},[154,1425,1426],{"class":160}," Holder.INSTANCE;\n",[154,1428,1429],{"class":156,"line":917},[154,1430,439],{"class":160},[154,1432,1433],{"class":156,"line":926},[154,1434,445],{"class":160},[15,1436,1437,1440,1441,1444,1445,1447,1448,1451,1452,1455,1456,111],{},[19,1438,1439],{},"Holder"," is not loaded until ",[19,1442,1443],{},"getInstance()"," is first called. The JVM's ",[19,1446,321],{}," lock\nensures ",[19,1449,1450],{},"INSTANCE"," is created exactly once, safely. No ",[19,1453,1454],{},"synchronized",", no ",[19,1457,1458],{},"volatile",[10,1460,1462],{"id":1461},"java-9-module-system-changes","Java 9 module system changes",[15,1464,1465,1466,1469,1470,1473],{},"Java 9 split ",[19,1467,1468],{},"rt.jar"," into named JDK modules. Class loading mechanics (parent delegation,\n",[19,1471,1472],{},"defineClass",") are unchanged, but:",[40,1475,1476,1484,1487],{},[43,1477,1478,1479,1481,1482,111],{},"Bootstrap loads only ",[19,1480,90],{}," (and a few low-level modules), not all of ",[19,1483,1468],{},[43,1485,1486],{},"Platform ClassLoader loads remaining JDK modules.",[43,1488,1489,1492,1493,1496],{},[31,1490,1491],{},"Strong encapsulation",": a module must ",[19,1494,1495],{},"exports"," a package for code outside the module\nto access it. Reflection into non-exported packages is blocked by default.",[73,1498,1502],{"className":1499,"code":1500,"language":1501,"meta":81,"style":81},"language-bash shiki shiki-themes github-light github-dark","# Frameworks that rely on deep reflection need --add-opens:\njava --add-opens java.base\u002Fjava.lang=ALL-UNNAMED \\\n     --add-opens java.base\u002Fjava.lang.reflect=ALL-UNNAMED \\\n     -jar myapp.jar\n","bash",[19,1503,1504,1509,1522,1532],{"__ignoreMap":81},[154,1505,1506],{"class":156,"line":157},[154,1507,1508],{"class":177},"# Frameworks that rely on deep reflection need --add-opens:\n",[154,1510,1511,1513,1516,1519],{"class":156,"line":181},[154,1512,150],{"class":164},[154,1514,1515],{"class":405}," --add-opens",[154,1517,1518],{"class":384}," java.base\u002Fjava.lang=ALL-UNNAMED",[154,1520,1521],{"class":405}," \\\n",[154,1523,1524,1527,1530],{"class":156,"line":199},[154,1525,1526],{"class":405},"     --add-opens",[154,1528,1529],{"class":384}," java.base\u002Fjava.lang.reflect=ALL-UNNAMED",[154,1531,1521],{"class":405},[154,1533,1534,1537],{"class":156,"line":366},[154,1535,1536],{"class":405},"     -jar",[154,1538,1539],{"class":384}," myapp.jar\n",[15,1541,1542,1547],{},[31,1543,1544],{},[19,1545,1546],{},"ALL-UNNAMED"," grants access to all code on the unnamed module path (the classpath).\nThis is a compatibility bridge for libraries not yet modularised.",[10,1549,1551],{"id":1550},"recap","Recap",[15,1553,1554,1555,1558,1559,1562,1563,1566,1567,1570,1571,1574,1575,1577,1578,1581],{},"Java class loading is ",[31,1556,1557],{},"lazy"," — classes are read, verified, and initialised only when\nfirst actively used. The ",[31,1560,1561],{},"Bootstrap → Platform → Application"," ClassLoader hierarchy\nenforces ",[31,1564,1565],{},"parent delegation",", ensuring JDK classes always take precedence. Loading\nproceeds through ",[31,1568,1569],{},"loading → linking (verify, prepare, resolve) → initialisation",";\nstatic fields get their real values only in initialisation. A class's identity is\n",[31,1572,1573],{},"(name, ClassLoader)"," — the same bytecode loaded by two different loaders produces\nincompatible types. Custom ClassLoaders enable plugins, encryption, and hot-reload by\noverriding ",[19,1576,811],{},". Classes can only be unloaded when their ClassLoader is GC'd;\nfailing to clear ThreadLocals, event listeners, or JDBC drivers on undeploy causes\n",[31,1579,1580],{},"Metaspace leaks"," that grow with each hot-deploy cycle.",[1583,1584,1585],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":81,"searchDepth":181,"depth":181,"links":1587},[1588,1589,1590,1591,1592,1597,1598,1599,1600,1601,1602,1603,1604],{"id":12,"depth":181,"text":13},{"id":25,"depth":181,"text":26},{"id":67,"depth":181,"text":68},{"id":217,"depth":181,"text":218},{"id":252,"depth":181,"text":253,"children":1593},[1594,1595,1596],{"id":257,"depth":199,"text":258},{"id":271,"depth":199,"text":272},{"id":314,"depth":199,"text":315},{"id":461,"depth":181,"text":462},{"id":627,"depth":181,"text":628},{"id":804,"depth":181,"text":805},{"id":1044,"depth":181,"text":1045},{"id":1136,"depth":181,"text":1137},{"id":1326,"depth":181,"text":1327},{"id":1461,"depth":181,"text":1462},{"id":1550,"depth":181,"text":1551},"How Java class loading works — the Bootstrap\u002FPlatform\u002FApp ClassLoader hierarchy, parent-delegation model, loading\u002Flinking\u002Finitialisation phases, class identity, ClassNotFoundException vs NoClassDefFoundError, custom ClassLoaders, class unloading, and diagnosing Metaspace leaks in hot-deploy environments.","hard","md","Java",{},"\u002Fblog\u002Fjava-classloading","\u002Fjava\u002Fjvm-internals\u002Fclassloading",{"title":5,"description":1605},"blog\u002Fjava-classloading","Class Loading","JVM Internals","jvm-internals","2026-06-20","Uy-4B7vcfXumQ_p9Y4FAADjPP9vv2M-bBIq9bsYsumg",1782244090026]