Class loader in Java:
An java app that begins with main method has three class loader level. And remember all class loader are Java classes and all these should have a instance. And when we talk about class loader actually we will be talking about the instance of that class loader.
1. BootStrapClassLoader // That load classes from rt.jar
2. sun.misc.Launcher$ExtClassLoader // That load classes from /usr/lib/jvm/java-7-oracle/jre/lib/ext:/usr/java/packages/lib/ext which you can come to know by using System.getProperty("java.ext.dirs") method.
3. sun.misc.Launcher$AppClassLoader // That load your main class
I have build the scenario. Lets try to understand using that.
I built one class below
package com;
public class CustomClass {
static{
System.out.println("Custom class that is inside /usr/lib/jvm/java-7-oracle/jre/lib/ext dir");
}
public static void check() {
System.out.println("java.ext.dirs = " + System.getProperty("java.ext.dirs"));
}
}
And putted that class inside one jar classloaderunrevieled.jar and putted that jar inside /usr/lib/jvm/java-7-oracle/jre/lib/ext.
And after that I build another class below
package com.tester;
import com.CustomClass;
public class ClassLoaderTester {
public static void main(String[] args) {
CustomClass.check();
System.out.println("ClassLoaderTester ran");
System.out.println("CustomClass.class.getClassLoader() : " + CustomClass.class.getClassLoader());
System.out.println("ClassLoaderTester.class.getClassLoader() : " + ClassLoaderTester.class.getClassLoader());
}
}
Then before running the app I put the same classloaderunrevieled.jar in the sun.misc.Launcher$AppClassLoader class path as that would be the one that will be running the main class.
Then I ran the ClassLoaderTester and the below output I got.
Output:-
Custom class that is inside /usr/lib/jvm/java-7-oracle/jre/lib/ext dir
java.ext.dirs = /usr/lib/jvm/java-7-oracle/jre/lib/ext:/usr/java/packages/lib/ext
ClassLoaderTester ran
CustomClass.class.getClassLoader() : sun.misc.Launcher$ExtClassLoader@60f32dde
ClassLoaderTester.class.getClassLoader() : sun.misc.Launcher$AppClassLoader@2bb0bf9a
--------------------
So using output we can understand when our ClassLoaderTester ran it went to and loaded the class using AppClassLoader and then when it saw it also need CustomClass to load jvm went to AppClassLoader as it was the one that loaded the ClassLoaderTester class. So what AppClassLoader did, it tried to find the class if it has loaded it or not. It saw, no it have not loaded it, AppClassLoader asked it parent ExtClassLoader if it has loaded the class it saw, no it also have not loaded the class. So ExtClassLoader went ahead and asked BootStrapClassLoader if it have loaded the class. It also saw, no it did not load the class. BootStrapClassLoader does not have parent so it will try to load the class if it does not find the class it throw ClassNotFoundException and that will be caught by the ExtClassLoader and it will call its findClass method to find the class. As we know that we have putted the classloaderunrevieled.jar inside ext dir and that is having CustomClass in it. ExtClassLoader will be able to load the class and Jvm will register that class loader as the loader of that class. When next time call will be made to method findLoadedClass which is native method call, from ExtClassLoader, JVM will return the loaded. So ExtClassLoader return the loaded class to AppClassLoader. And then call to check method went ahead. As inside check method we checked who is the loader of the CustomClass it is printing ExtClassLoader.
-------
If we keep every thing same but just remove the classloaderunrevieled.jar from /usr/lib/jvm/java-7-oracle/jre/lib/ext dir the output will be changed to below output
Output:-
Custom class that is inside /usr/lib/jvm/java-7-oracle/jre/lib/ext dir
java.ext.dirs = /usr/lib/jvm/java-7-oracle/jre/lib/ext:/usr/java/packages/lib/ext
ClassLoaderTester ran
CustomClass.class.getClassLoader() : sun.misc.Launcher$AppClassLoader@55c4d594 // *********** Changed line
ClassLoaderTester.class.getClassLoader() : sun.misc.Launcher$AppClassLoader@55c4d594
Now we can easyly understand why it got changed.
So how to write your own class loader?
As we have learned the correct way to load the class is to override the findClass method.
As it will be called when no other class loader will be able to laod the class based upon the above explaination.
Lets write our own class loader. But in this we will override loadClass method. It is for explaining some point that we will discuss leter.
package com.classloader;
public class CustomClassLoader extends ClassLoader {
public CustomClassLoader(ClassLoader parent) {
super(parent);
}
private Class<?> getClass(String name) throws ClassNotFoundException {
String file = name.replace('.', File.separatorChar) + ".class";
file = "bin"+File.separatorChar + file;
byte[] b = null;
try {
b = loadClassData(file);
Class<?> c = defineClass(name, b, 0, b.length);
resolveClass(c);
return c;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("loading class '" + name + "' ");
if (name.startsWith("com.classestolaod")) {
return getClass(name);
}
return super.loadClass(name);
}
private byte[] loadClassData(String name) throws IOException {
name = System.getProperty("user.dir") + File.separatorChar + name;
InputStream stream =new FileInputStream(new File(name));
int size = stream.available();
byte buff[] = new byte[size];
DataInputStream in = new DataInputStream(stream);
in.readFully(buff);
in.close();
return buff;
}
@Override
protected void finalize() throws Throwable {
System.out.println("CustomClassLoader instance getting unloaded");
super.finalize();
}
}
--------------------------------------------------------------------------------------------------
Now lets modify the ClassLoaderTester
package com.tester;
public class ClassLoaderTester {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException,
IllegalAccessException {
CustomClassLoader ccl = new CustomClassLoader(ClassLoaderTester.class.getClassLoader());
Class<?> loadedClass = ccl.loadClass("com.classestolaod.DummyClass");
}
}
-------------------------------------------------------------------------------------------------------
package com.classestolaod;
public class DummyClass {
static {
System.out.println("class loaded "+ DummyClass.class.getClassLoader());
}
public DummyClass(){
System.out.println("DummyClass constructor");
}
}
---------------------------------------------------------------------------------------------------------
Output:-
loading class 'com.classestolaod.DummyClass'
loading class 'java.lang.Object'
-----------------------------------------------------------------------------------------------------------
Lets discuss the flow.
First it will call the loadClass method of CustomClassLoader and it will check if it belong to package com.classestolaod and if it is then it will try to load it. If now then it will call the super class loadClass method. Now CustomClassLoader's getClass method first will call loadClassData(file) method, that will just convert the .class file to byte array. Then it will call the Class<?> c = defineClass(name, b, 0, b.length) method that have very high significance in class loading. Because whatever ClassLoader's implementation call this method, after class loading is done, then jvm call the below ClassLoader's addClass method to record all the loaded class by this class loader. So when next time request will come and findLoadedClass(name); method is invoked, which is native method, jvm will return the loaded class by the current class loader instance if it is already loaded. As we can see while it was loading the DummyClass it also loaded the Object class. Because Object class is extended by DummyClass. And as we know for loading the Object class it will use the DummyClass's class loader to load the Object class hence the jvm made the loadClass method call of CustomClassLoader. As our code is loading the classes from package com.classestolaod it will delegate this to parent ClassLoader.
// Invoked by the VM to record every loaded class with this loader.
void addClass(Class c) {
classes.addElement(c);
}
// Invoked by the VM to record every loaded class with this loader.
void addClass(Class c) {
classes.addElement(c);
}
So now if change the code like below
public class ClassLoaderTester {
public static void main(String[] args){
CustomClassLoader ccl = new CustomClassLoader(ClassLoaderTester.class.getClassLoader()); //1
Class<?> loadedClass = ccl.loadClass("com.classestolaod.DummyClass");//2
Object newInstance = loadedClass.newInstance();//3
System.out.println("Main finished");//4
DummyClass dummyClass = new DummyClass();//5
CustomClassLoader ccl2 = new CustomClassLoader(ClassLoaderTester.class.getClassLoader());//6
Class<?> loadedClass2 = ccl2.loadClass("com.classestolaod.DummyClass");//7
}
}
---------------------------------------------------------------------------------------------------------
Output"-
loading class 'com.classestolaod.DummyClass'
loading class 'java.lang.Object'
loading class 'java.lang.System'
loading class 'java.lang.StringBuilder'
loading class 'java.lang.Class'
loading class 'java.io.PrintStream'
class loaded com.classloader.CustomClassLoader@439a8942
DummyClass constructor
Main finished
class loaded sun.misc.Launcher$AppClassLoader@6da21389
DummyClass constructor
loading class 'com.classestolaod.DummyClass'
loading class 'java.lang.Object'
--------------------------------------------------------------------------------------------------------------
Till line 2 we are clear. So in line 3 when we create the instance of DummyClass it needed other class to load as we have seen erlier it will use the class loader of current class. JVM used the CustomClassLoader to load the System, StringBuilder, Class and PrintStream classes. Actully what JVM do is when it refers a new class it checks the class loader context also. Means when it is loading the class for DummyClass, CustomClassLoader was the class loader for this. So when Object class was needed jvm checked using method findLoadedClass(name) if it currently loaded using the CustomClassLoader or not. It was not. As we know that it must have been already loaded by the BootstrapClassLoader. So jvm called the loadClass method of CustomClassLoader to load the Object class but it delegated it to parent class loader.
When on line 5 executed, jvm checked using findLoadedClass using current class loader in this case it will be AppClassLoader, if DummyClass is already loaded or not. But it was not loaded so it loaded it. As you can see it did not load the Object class here as it was already loaded.
In line 6 and 7 we created the new CustomClassLoader instance and loaded the DummyClass again it did the same thing as earlier as it was new instance of class loader.
--------------------------------------------------------------------------------------------------------------------
What if change the code like below
CustomClassLoader ccl = new CustomClassLoader(ClassLoaderTester.class.getClassLoader()); // 1
Class<?> loadedClass = ccl.loadClass("com.classestolaod.DummyClass"); //2
Class<?> loadedClass2 = ccl.loadClass("com.classestolaod.DummyClass"); //3
----------------------------------------------------------------------------------------------------------------------
Output:-
loading class 'com.classestolaod.DummyClass'
loading class 'java.lang.Object'
loading class 'com.classestolaod.DummyClass'
Exception in thread "main" java.lang.LinkageError: loader (instance of com/classloader/CustomClassLoader): attempted duplicate class definition for name: "com/classestolaod/DummyClass"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
at java.lang.ClassLoader.defineClass(ClassLoader.java:643)
at com.classloader.CustomClassLoader.getClass(CustomClassLoader.java:22)
at com.classloader.CustomClassLoader.loadClass(CustomClassLoader.java:35)
at com.tester.ClassLoaderTester.main(ClassLoaderTester.java:22)
-----------------------------------------------------------------------------------------------------------------
When we tried to load the same class again using the same class loader we got the LinkageError when it called the defineClass method.
As we have mentioned earlier defineClass method has significance. Means jvm record this class loader as loader of the class. So when you tried to load it again using the same class loader it thrown duplicate class loading attempt.
An java app that begins with main method has three class loader level. And remember all class loader are Java classes and all these should have a instance. And when we talk about class loader actually we will be talking about the instance of that class loader.
1. BootStrapClassLoader // That load classes from rt.jar
2. sun.misc.Launcher$ExtClassLoader // That load classes from /usr/lib/jvm/java-7-oracle/jre/lib/ext:/usr/java/packages/lib/ext which you can come to know by using System.getProperty("java.ext.dirs") method.
3. sun.misc.Launcher$AppClassLoader // That load your main class
I have build the scenario. Lets try to understand using that.
I built one class below
package com;
public class CustomClass {
static{
System.out.println("Custom class that is inside /usr/lib/jvm/java-7-oracle/jre/lib/ext dir");
}
public static void check() {
System.out.println("java.ext.dirs = " + System.getProperty("java.ext.dirs"));
}
}
And putted that class inside one jar classloaderunrevieled.jar and putted that jar inside /usr/lib/jvm/java-7-oracle/jre/lib/ext.
And after that I build another class below
package com.tester;
import com.CustomClass;
public class ClassLoaderTester {
public static void main(String[] args) {
CustomClass.check();
System.out.println("ClassLoaderTester ran");
System.out.println("CustomClass.class.getClassLoader() : " + CustomClass.class.getClassLoader());
System.out.println("ClassLoaderTester.class.getClassLoader() : " + ClassLoaderTester.class.getClassLoader());
}
}
Then before running the app I put the same classloaderunrevieled.jar in the sun.misc.Launcher$AppClassLoader class path as that would be the one that will be running the main class.
Then I ran the ClassLoaderTester and the below output I got.
Output:-
Custom class that is inside /usr/lib/jvm/java-7-oracle/jre/lib/ext dir
java.ext.dirs = /usr/lib/jvm/java-7-oracle/jre/lib/ext:/usr/java/packages/lib/ext
ClassLoaderTester ran
CustomClass.class.getClassLoader() : sun.misc.Launcher$ExtClassLoader@60f32dde
ClassLoaderTester.class.getClassLoader() : sun.misc.Launcher$AppClassLoader@2bb0bf9a
--------------------
So using output we can understand when our ClassLoaderTester ran it went to and loaded the class using AppClassLoader and then when it saw it also need CustomClass to load jvm went to AppClassLoader as it was the one that loaded the ClassLoaderTester class. So what AppClassLoader did, it tried to find the class if it has loaded it or not. It saw, no it have not loaded it, AppClassLoader asked it parent ExtClassLoader if it has loaded the class it saw, no it also have not loaded the class. So ExtClassLoader went ahead and asked BootStrapClassLoader if it have loaded the class. It also saw, no it did not load the class. BootStrapClassLoader does not have parent so it will try to load the class if it does not find the class it throw ClassNotFoundException and that will be caught by the ExtClassLoader and it will call its findClass method to find the class. As we know that we have putted the classloaderunrevieled.jar inside ext dir and that is having CustomClass in it. ExtClassLoader will be able to load the class and Jvm will register that class loader as the loader of that class. When next time call will be made to method findLoadedClass which is native method call, from ExtClassLoader, JVM will return the loaded. So ExtClassLoader return the loaded class to AppClassLoader. And then call to check method went ahead. As inside check method we checked who is the loader of the CustomClass it is printing ExtClassLoader.
-------
If we keep every thing same but just remove the classloaderunrevieled.jar from /usr/lib/jvm/java-7-oracle/jre/lib/ext dir the output will be changed to below output
Output:-
Custom class that is inside /usr/lib/jvm/java-7-oracle/jre/lib/ext dir
java.ext.dirs = /usr/lib/jvm/java-7-oracle/jre/lib/ext:/usr/java/packages/lib/ext
ClassLoaderTester ran
CustomClass.class.getClassLoader() : sun.misc.Launcher$AppClassLoader@55c4d594 // *********** Changed line
ClassLoaderTester.class.getClassLoader() : sun.misc.Launcher$AppClassLoader@55c4d594
Now we can easyly understand why it got changed.
So how to write your own class loader?
As we have learned the correct way to load the class is to override the findClass method.
As it will be called when no other class loader will be able to laod the class based upon the above explaination.
Lets write our own class loader. But in this we will override loadClass method. It is for explaining some point that we will discuss leter.
package com.classloader;
public class CustomClassLoader extends ClassLoader {
public CustomClassLoader(ClassLoader parent) {
super(parent);
}
private Class<?> getClass(String name) throws ClassNotFoundException {
String file = name.replace('.', File.separatorChar) + ".class";
file = "bin"+File.separatorChar + file;
byte[] b = null;
try {
b = loadClassData(file);
Class<?> c = defineClass(name, b, 0, b.length);
resolveClass(c);
return c;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("loading class '" + name + "' ");
if (name.startsWith("com.classestolaod")) {
return getClass(name);
}
return super.loadClass(name);
}
private byte[] loadClassData(String name) throws IOException {
name = System.getProperty("user.dir") + File.separatorChar + name;
InputStream stream =new FileInputStream(new File(name));
int size = stream.available();
byte buff[] = new byte[size];
DataInputStream in = new DataInputStream(stream);
in.readFully(buff);
in.close();
return buff;
}
@Override
protected void finalize() throws Throwable {
System.out.println("CustomClassLoader instance getting unloaded");
super.finalize();
}
}
--------------------------------------------------------------------------------------------------
Now lets modify the ClassLoaderTester
package com.tester;
public class ClassLoaderTester {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException,
IllegalAccessException {
CustomClassLoader ccl = new CustomClassLoader(ClassLoaderTester.class.getClassLoader());
Class<?> loadedClass = ccl.loadClass("com.classestolaod.DummyClass");
}
}
-------------------------------------------------------------------------------------------------------
package com.classestolaod;
public class DummyClass {
static {
System.out.println("class loaded "+ DummyClass.class.getClassLoader());
}
public DummyClass(){
System.out.println("DummyClass constructor");
}
}
---------------------------------------------------------------------------------------------------------
Output:-
loading class 'com.classestolaod.DummyClass'
loading class 'java.lang.Object'
-----------------------------------------------------------------------------------------------------------
Lets discuss the flow.
First it will call the loadClass method of CustomClassLoader and it will check if it belong to package com.classestolaod and if it is then it will try to load it. If now then it will call the super class loadClass method. Now CustomClassLoader's getClass method first will call loadClassData(file) method, that will just convert the .class file to byte array. Then it will call the Class<?> c = defineClass(name, b, 0, b.length) method that have very high significance in class loading. Because whatever ClassLoader's implementation call this method, after class loading is done, then jvm call the below ClassLoader's addClass method to record all the loaded class by this class loader. So when next time request will come and findLoadedClass(name); method is invoked, which is native method, jvm will return the loaded class by the current class loader instance if it is already loaded. As we can see while it was loading the DummyClass it also loaded the Object class. Because Object class is extended by DummyClass. And as we know for loading the Object class it will use the DummyClass's class loader to load the Object class hence the jvm made the loadClass method call of CustomClassLoader. As our code is loading the classes from package com.classestolaod it will delegate this to parent ClassLoader.
// Invoked by the VM to record every loaded class with this loader.
void addClass(Class c) {
classes.addElement(c);
}
// Invoked by the VM to record every loaded class with this loader.
void addClass(Class c) {
classes.addElement(c);
}
So now if change the code like below
public class ClassLoaderTester {
public static void main(String[] args){
CustomClassLoader ccl = new CustomClassLoader(ClassLoaderTester.class.getClassLoader()); //1
Class<?> loadedClass = ccl.loadClass("com.classestolaod.DummyClass");//2
Object newInstance = loadedClass.newInstance();//3
System.out.println("Main finished");//4
DummyClass dummyClass = new DummyClass();//5
CustomClassLoader ccl2 = new CustomClassLoader(ClassLoaderTester.class.getClassLoader());//6
Class<?> loadedClass2 = ccl2.loadClass("com.classestolaod.DummyClass");//7
}
}
---------------------------------------------------------------------------------------------------------
Output"-
loading class 'com.classestolaod.DummyClass'
loading class 'java.lang.Object'
loading class 'java.lang.System'
loading class 'java.lang.StringBuilder'
loading class 'java.lang.Class'
loading class 'java.io.PrintStream'
class loaded com.classloader.CustomClassLoader@439a8942
DummyClass constructor
Main finished
class loaded sun.misc.Launcher$AppClassLoader@6da21389
DummyClass constructor
loading class 'com.classestolaod.DummyClass'
loading class 'java.lang.Object'
--------------------------------------------------------------------------------------------------------------
Till line 2 we are clear. So in line 3 when we create the instance of DummyClass it needed other class to load as we have seen erlier it will use the class loader of current class. JVM used the CustomClassLoader to load the System, StringBuilder, Class and PrintStream classes. Actully what JVM do is when it refers a new class it checks the class loader context also. Means when it is loading the class for DummyClass, CustomClassLoader was the class loader for this. So when Object class was needed jvm checked using method findLoadedClass(name) if it currently loaded using the CustomClassLoader or not. It was not. As we know that it must have been already loaded by the BootstrapClassLoader. So jvm called the loadClass method of CustomClassLoader to load the Object class but it delegated it to parent class loader.
When on line 5 executed, jvm checked using findLoadedClass using current class loader in this case it will be AppClassLoader, if DummyClass is already loaded or not. But it was not loaded so it loaded it. As you can see it did not load the Object class here as it was already loaded.
In line 6 and 7 we created the new CustomClassLoader instance and loaded the DummyClass again it did the same thing as earlier as it was new instance of class loader.
--------------------------------------------------------------------------------------------------------------------
What if change the code like below
CustomClassLoader ccl = new CustomClassLoader(ClassLoaderTester.class.getClassLoader()); // 1
Class<?> loadedClass = ccl.loadClass("com.classestolaod.DummyClass"); //2
Class<?> loadedClass2 = ccl.loadClass("com.classestolaod.DummyClass"); //3
----------------------------------------------------------------------------------------------------------------------
Output:-
loading class 'com.classestolaod.DummyClass'
loading class 'java.lang.Object'
loading class 'com.classestolaod.DummyClass'
Exception in thread "main" java.lang.LinkageError: loader (instance of com/classloader/CustomClassLoader): attempted duplicate class definition for name: "com/classestolaod/DummyClass"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
at java.lang.ClassLoader.defineClass(ClassLoader.java:643)
at com.classloader.CustomClassLoader.getClass(CustomClassLoader.java:22)
at com.classloader.CustomClassLoader.loadClass(CustomClassLoader.java:35)
at com.tester.ClassLoaderTester.main(ClassLoaderTester.java:22)
-----------------------------------------------------------------------------------------------------------------
When we tried to load the same class again using the same class loader we got the LinkageError when it called the defineClass method.
As we have mentioned earlier defineClass method has significance. Means jvm record this class loader as loader of the class. So when you tried to load it again using the same class loader it thrown duplicate class loading attempt.
No comments:
Post a Comment