Java dynamic class loading (java.lang.ClassLoader)
Это глава из будущей книги «Readings in Java Type System».
---
Вопросы, на которые дам ответы:
1) можно ли загрузить «системный класс» (String, Thread, Class, Object, ClassLoader, ...) by user defined class loader?
2) можно ли организовать повторную загрузку класса?
3) есть ли в Java выгрузка классов?
4) как происходят утечки памяти через загрузку классов у популярных библиотек?
5) анонимные загрузчики классов в Java 7
6) параллельная загрузка классов
7) инструментирование классов при загрузке
.
Пример: исключение при попытке «привести класс к самому себе»:
public class NameSpaceTest {
public static void main(String[] args) throws Exception {
final String className = NameSpaceTest.class.getName();
final byte[] byteCodes = ClassLoaderUtil.loadByteCode(className);
NameSpaceTest ref = (NameSpaceTest) new MyClassLoader()
.defineClass(className, byteCodes)
.newInstance();
}
}
>> Exception in thread «main» java.lang.ClassCastException: NameSpaceTest cannot be cast to NameSpaceTest.
Пример: переполнение PermGen
import static java.lang.String.format;
public class OutOfMemoryError_PermGen {
public static void main(String[] args) throws Exception {
Class<?> clazz = OutOfMemoryError_PermGen.class;
byte[] buffer = ClassLoaderUtil.loadByteCode(clazz.getName());
MyClassLoader loader = new MyClassLoader();
for (long index = 0; index < Long.MAX_VALUE; index++) {
String newClassName = "_" + format("%0" + (clazz.getSimpleName().length() - 1) + "d", index);
byte[] newClassData = new String(buffer, "latin1")
.replaceAll(clazz.getSimpleName(), newClassName)
.getBytes("latin1");
System.out.println("Count of loaded classes: " + index);
loader.defineClass(
clazz.getName().replace(clazz.getSimpleName(), newClassName),
newClassData);
}
}
}
Count of loaded classes: 0...
Count of loaded classes: 31075
Exception in thread «main» java.lang.OutOfMemoryError: PermGen space
// todo: пересмотреть в виду PermGen->Metaspace в JRE 8 от Oracle.
.
Пример: демонстрация выгрузки классов
import static java.lang.String.format;
public class ClassUnloading {
public static void main(String[] args) throws Exception {
Class<?> clazz = ClassUnloading.class;
byte[] buffer = ClassLoaderUtil.loadByteCode(clazz.getName());
MyClassLoader loader;
for (long index = 1; index < Long.MAX_VALUE; index++) {
String newClassName = "_" + format("%0" + (clazz.getSimpleName().length() - 1) + "d", index);
byte[] newClassData = new String(buffer, "latin1")
.replaceAll(clazz.getSimpleName(), newClassName)
.getBytes("latin1");
loader = new MyClassLoader();
System.out.println("Count of loaded classes: " + index);
loader.defineClass(
clazz.getName().replace(clazz.getSimpleName(), newClassName),
newClassData);
}
}
}
Count of loaded classes: 0...
Count of loaded classes: 100000
...
Count of loaded classes: 1000000
...
Count of loaded classes: 10000000
...
.
Пример: демонстрация наличия отношения делегирования между загрузчиками классов
public interface Root {public Leaf getLeaf();}
public interface Leaf {}
public class LeafImpl implements Leaf {}
public class RootImpl implements Root {
private Leaf leaf = new LeafImpl();
@Override
public Leaf getLeaf() {
return leaf;
}
}
public class ChainTest {
public static void main(String[] args) throws Exception {
final String className = RootImpl.class.getName();
final byte[] byteCodes = ClassLoaderUtil.loadByteCode(className);
// comparing RootImpl classes
Root rootA = (Root) loadClass(className, byteCodes);
Root rootB = (Root) loadClass(className, byteCodes);
System.out.println(rootA.getClass() == rootB.getClass());
// comparing LeafImpl classes
Leaf leafA = rootA.getLeaf();
Leaf leafB = rootB.getLeaf();
System.out.println(leafA.getClass() == leafB.getClass());
}
private static Object loadClass(String className, byte[] byteCodes) throws Exception {
return new MyClassLoader().defineClass(className, byteCodes).newInstance();
}
}
falsetrue
.
Пример: демонстрация наличия кэша загруженных классов
public class ClassCache {
public static void main(String[] args) throws Exception {
String name = ClassCache.class.getName();
byte[] byteCodes = ClassLoaderUtil.loadByteCode(name);
// load - load
MyClassLoader loaderLL = new MyClassLoader();
Class<?> clazzLLA = loaderLL.loadClass(name);
Class<?> clazzLLB = loaderLL.loadClass(name);
System.err.println("load " + (clazzLLA == clazzLLB ? "==" : "!=") + " load");
// load - define
MyClassLoader loaderLD = new MyClassLoader();
Class<?> clazzLDA = loaderLD.loadClass(name);
Class<?> clazzLDB = loaderLD.defineClass(name, byteCodes);
System.err.println("load " + (clazzLDA == clazzLDB ? "==" : "!=") + " define");
// define - load
MyClassLoader loaderDL = new MyClassLoader();
Class<?> clazzDLA = loaderDL.defineClass(name, byteCodes);
Class<?> clazzDLB = loaderDL.loadClass(name);
System.err.println("define " + (clazzDLA == clazzDLB ? "==" : "!=") + " load");
// define - define
MyClassLoader loaderDD = new MyClassLoader();
Class<?> clazzDDA = loaderDD.defineClass(name, byteCodes);
Class<?> clazzDDB = loaderDD.defineClass(name, byteCodes);
System.err.println("define " + (clazzDDA == clazzDDB ? "==" : "!=") + " define");
}
}
load == loadload != define
define == load
... LinkageError: ... attempted duplicate class definition ...
.
Пример:
.
Утилитарные классы:
public class MyClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] byteCodes) {
return super.defineClass(name, byteCodes, 0, byteCodes.length);
}
}
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
public class ClassLoaderUtil {
public static byte[] loadByteCode(String className) throws IOException {
String fileName = "/" + className.replaceAll("\\.", "/") + ".class";
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
URL url = ClassLoaderUtil.class.getResource(fileName);
Files.copy(Paths.get(url.getPath().substring(1)), buffer);
return buffer.toByteArray();
}
}
.Литература
[DCLinJVM] S. Liang, G. Bracha, «Dynamic Class Loading in the JavaTM Virtual Machine», 1998
10 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів