对于Java的内存管理,我认为可以分为2类:
内存分配:这里也可以理解为类加载(ClassLoader)
内存释放:也就是Java GC
本文主要介绍类加载,了解类加载的过程对Java类可以有更好的理解,比如static块里面的代码为什么比构造方法还先执行。
首先咱们先了解Java代码的执行过程。
从这个框图很容易大体上了解 java 程序工作原理。首先,你写好 java 代码,保存到硬盘当中。然后你在命令行中输入
javac YourClassName.java
此时,你的 java 代码就被编译成字节码(.class)。此时的 class 文件依然是保存在硬盘中,因此,当你在命令行中运行
java YourClassName
就完成了上面红色方框中的工作。JRE 的来加载器从硬盘中读取 class 文件,载入到系统分配给 JVM 的内存区域–运行数据区(Runtime Data Areas). 然后执行引擎解释或者编译类文件,转化成特定 CPU 的机器码,CPU 执行机器码,至此完成整个过程。
而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader
)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader
就是用来动态加载class文件到内存当中用的。
在上一小节中介绍了整个过程,这里主要看上面红框框里面的部分。
JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)。这和我们在操作系统中所学的知识很相似哦。
一些简单的说明:
ClassLoader
)说了这么就,那么什么是ClassLoader
呢?
A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a “class file” of that name from a file system.
class loader用于把一个类的二进制的名称,去定位这个类的数据,并为这个类构造一个定义。其中的二进制名字也就是类的完整名字(包名+类名,如”java.lang.String”)。
JDK提供了以下的ClassLoader
Bootstrap ClassLoader
:Bootstrap加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib以及%JAVA_HOME%/jre/classes中的类,是最顶级的ClassLoader。Extensions ClassLoader
:Extensions ClassLoader是用java写的,且它的父加载器是Bootstrap。Extensions ClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中的类库。System ClassLoader
:系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。它的父加载器为Extensions ClassLoader。除了JDK提供的ClassLoader
还可以使用开发人员自己定义的类加载器。
就像上图所表面的一样。
在ClassLoader
里面有如下的方法:
protected ClassLoader(ClassLoader parent); // Creates a new class loader using the specified parent class loader for delegation.
public Class> loadClass(String name) throws ClassNotFoundException // Loads the class with the specified binary name.重写这个方法,不遵从委托模型
protected Class> findClass(String name)
throws ClassNotFoundException // Finds the class with the specified binary name,遵守委托模型,重写这个方法就好。
protected final Class> defineClass(String name,
byte[] b,
int off,
int len)
throws ClassFormatError // Converts an array of bytes into an instance of class Class. Before the Class can be used it must be resolved.即把数组b中的内容转换成Java 类。
The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine’s built-in class loader, called the “bootstrap class loader”, does not itself have a parent but may serve as the parent of a ClassLoader instance.
ClassLoader类使用一种委托模型来查找类和资源。每个ClassLoader实例都会关联1个父ClassLoader。当需要查询类和资源的时候,一个ClassLoader实例在查询类或资源之前会先委托给它的父ClassLoader去查询。Bootstrap ClassLoader是最顶层的加载器,并且可以作为其它ClassLoader实例的父ClassLoader。
由此看见,这个“委托模型”的安全性是很高的,Bootstrap是最顶层的加载器,这样比如加载 java.lang.String 的时候,永远都会被Bootstrap加载(Bootstrap ClassLoader会加载%JAVA_HOME%/jre/lib中rt.jar里的String类)。 这样用户自定义的java.lang.String永远都不会被加载,这样就避免了多个java.lang.String造成的混乱现象。
在ClassLoader
源码里面就可以看到这样的代码:
// Java 7
protected synchronized Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name); //先查找这个类是否已经加载过,每个加载器都有自己的缓存
if (c == null) {
try {
if (parent != null) { //父加载器存在的话先使用父加载器加载
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name); //没有父加载器的话使用bootstrap加载
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name); //如果父加载器没有找到,那么自身查找
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
在Java8里面,我们可以看到,synchronized的位置不一样了。锁的粒度变小了。原来是整个方法,现在只是每个要加载的类。
// Java 8
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
可以看到findClass
与loadClass
方法。符合上文所说的委托模型。
1:要被加载的类
public class Obj {
@Override
public String toString() {
return "Hello,World!";
}
}
2:类加载器
public class WQHClassLoader extends ClassLoader {
private String directory;
public WQHClassLoader(String directory) {
this.directory = directory;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] clsBytes = getClassBytes(name);
if (clsBytes == null)
throw new ClassNotFoundException();
return defineClass(name, clsBytes, 0, clsBytes.length);
}
private byte[] getClassBytes(String name) {
String location = getClassLoc(name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileInputStream fis = null;
try {
fis = new FileInputStream(location);
byte[] buffer = new byte[4096];
int readLen = 0;
while ((readLen = fis.read(buffer)) != -1) {
baos.write(buffer, 0, readLen);
}
baos.flush();
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private String getClassLoc(String name) {
return this.directory + File.separatorChar + name.replace('.', File.separatorChar) + ".class";
}
}
3.Main
public class Main {
@Test
public void test() {
WQHClassLoader fscl = new WQHClassLoader("/classloader");
try {
System.out.println(fscl.loadClass("Obj").newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:
Hello,World!
ClassLoader
有什么用呢?热部署,代码加密等领域。加载jar里面的class文件就是用了动态加载啦。可以参考这里:知乎-Java 类加载器(ClassLoader)的实际使用场景有哪些?参考:
1:浅析java类加载器ClassLoader
2:官网