运行时数据区

方法区是JVM用来存储加载类的信息、常量、静态变量、编译后的代码等数据虚拟机规范中这是一个逻辑区域。具体实现根据不同来实现。如:oracle的HotSpot在java7中方法区放在永久代,java8放在元数据空间,并且通过GC机制对这个区域进行管理.
一个类加载后的信息最终是放在了方法区里面,但是这个过程究竟是由谁来做的呢?要想知道class是怎么加载的得从类的生命周期开始说起
类生命周期

类加载器
类加载器负责装入类,搜索网络、jar、zip、文件夹、二进制数据、内存等指定位置的类资源。一个java程序运行,最少有三个类加载器实例,负责不同类的加载.
核心类库加载器(Bootstrap Loader)
Bootstrap Loader 是C++实现,无对应java类.加载JRE_HOME/jre/lib目录或用户配置的目录.
JDK核心类库rt.jar.比如String、Object这些jdk提供的类就是由这个加载器去加载
扩展类库加载器(Extension class Loader)
ExtClassLoader的实例,加载JRE_HOME/jre/lib/ext目录,JDK扩展包或用户配置的目录.专门加载ext下面的扩展类库
应用程序加载器(application class Loader)
appClassLoaderr的实例,加载java.class.path指定的目录,用户应用程序class-path 或者java命令运行时参数-cp…。专门负责加载用户写的应用
演示
import com.sun.nio.zipfs.ZipInfo;
public class Demo {
public static void main(String[] args) {
// 核心类库加载器是C语言写的 所以是null
System.out.println("核心类库加载器:" + Object.class.getClassLoader());
// 扩展类加载器
System.out.println("扩展类库加载器:" + ZipInfo.class.getClassLoader());
// 应用程序加载器
System.out.println("应用程序加载器:" + Demo.class.getClassLoader());
}
}

通过这个演示,我们可以知道,java中的每一个类都是由一个专门的类加载器去加载的
JVM怎么知道类在哪里
结论
class信息存放在不同的位置如:桌面jar、项目bin目录、target目录等等,那么jvm是怎么知道去加载的呢?
查看jdk源代码:sun.misc.Launcher.AppClassLoader

JVM通过读取java.class.path的配置,指定去哪些地址加载类资源
验证过程
相关命令
1.jps命令查看本机java进程
2.jcmd命令查看运行时配置
准备程序
import java.io.IOException;
/**
* @author pangbohuan
* @date 2020/5/29 0029 19:30
* @description
*/
public class Demo {
public static void main(String[] args) throws IOException {
System.out.println("Hello World!");
System.in.read();
}
}
启动程序,让他处于阻塞状态!哈哈,一切从Hello World开始然后又回到了Hello World!
开始验证
-
1.打开CMD命令窗口
-
2.输入jps命令查看本机java进程

查看到我们的程序demo的id是2020
-
3.输入jcmd 2020 help 命令查看运行时配置

可以看到这里面有个参数是VM.system_properties查看系统环境变量
-
4.输入 jcmd 2020 VM.system_properties 查看运行时环境变量

这里面有很多配置信息,但是我们这一次找到java.class.path的信息看一下,里面包含了JDK存在的目录,maven的依赖jar包地址,编写的程序存放地址等这些信息。所以得出结论为什么我们如此轻松的跑起来java程序,是因为开发工具或是框架帮我们做了很多事情,配置了很多环境变量
类加载案例
自定义类加载器
需要测试加载的demo.java
package com.pbh.common.basics;
/**
* @author pangbohuan
* @date 2020/5/29 0029 19:30
* @description
*/
public class Demo {
public static void main(String[] args) {
System.out.println();
}
static {
System.out.println("静态方法执行");
}
public void test() {
System.out.println("2222222222");
}
}
准备一个类加载工作
package com.sydata.cscm.transfer.action;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.TimeUnit;
/**
* @author pangbohuan
* @date 2020/6/1 0001 15:25
* @description 类加载测试
*/
public class LoadText {
public static void main(String[] args) throws Exception {
URL url = new URL("file:E:\\word\\pbh.Cloud.Parent\\common-basics\\target\\classes\\");
URL[] urls = new URL[]{url};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
while (true) {
Class<?> aClass = urlClassLoader.loadClass("com.pbh.common.basics.Demo");
System.out.println("Demo使用的的类加载器:" + aClass.getClassLoader());
Object object = aClass.newInstance();
Object test = aClass.getMethod("test").invoke(object);
System.out.println("调用Demo的test方法,返回值是" + test);
//睡眠3秒执行一次
TimeUnit.SECONDS.sleep(3);
}
}
}

从这个执行结果可以看到:加载外部类成功.静态代码块是在类第一次实例化的时候触发的.而且一次加载只会执行一次.加载已经成功了,那么卸载的动作是在什么时候做的呢?
一个类的卸载前提条件是加载这个类的加载器先被GC回收后才会卸载这个类
跟踪类的加载和卸载
将类加载器和类的实例置为空,跳出循环并通知一下GC
package com.sydata.cscm.transfer.action;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.TimeUnit;
/**
* @author pangbohuan
* @date 2020/6/1 0001 15:25
* @description 类加载测试
*/
public class LoadText {
public static void main(String[] args) throws Exception {
URL url = new URL("file:E:\\word\\pbh.Cloud.Parent\\common-basics\\target\\classes\\");
URL[] urls = new URL[]{url};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
while (true) {
if(urlClassLoader==null){
break;
}
System.out.println("开始加载com.pbh.common.basics.Demo类了");
Class<?> aClass = urlClassLoader.loadClass("com.pbh.common.basics.Demo");
System.out.println("Demo使用的的类加载器:" + aClass.getClassLoader());
Object object = aClass.newInstance();
Object test = aClass.getMethod("test").invoke(object);
System.out.println("调用Demo的test方法,返回值是" + test);
//睡眠3秒执行一次
TimeUnit.SECONDS.sleep(3);
object = null;
urlClassLoader = null;
}
System.out.println("开始卸载com.pbh.common.basics.Demo类了");
System.gc();
TimeUnit.SECONDS.sleep(10);
}
}
idea 设置一下vm参数 -verbose:class

然后点击运行,运行结果如下

可以看到jvm加载了很多类进来,其中Demo类加载是由urlClassLoader.loadClass(“com.pbh.common.basics.Demo”)触发的

可以看到当我们将类加载器和类的实例置空后,手动调用GC会卸载Demo类.手动调用GC并不一定会触发,只是给了GC一个参考建议.
小结
既然说一个class不能被同一个类加载器重复加载,那么用两个不同的加载器行不行?我们将URLClassLoader放入循环中,每次执行都实例化一个新的类加载器进行测试一下
package com.sydata.cscm.transfer.action;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.TimeUnit;
/**
* @author pangbohuan
* @date 2020/6/1 0001 15:25
* @description 类加载测试
*/
public class LoadText {
public static void main(String[] args) throws Exception {
URL url = new URL("file:E:\\word\\pbh.Cloud.Parent\\common-basics\\target\\classes\\");
URL[] urls = new URL[]{url};
while (true) {
URLClassLoader urlClassLoader = new URLClassLoader(urls);
System.out.println("开始加载com.pbh.common.basics.Demo类了");
Class<?> aClass = urlClassLoader.loadClass("com.pbh.common.basics.Demo");
System.out.println("Demo使用的的类加载器:" + aClass.getClassLoader());
Object object = aClass.newInstance();
Object test = aClass.getMethod("test").invoke(object);
System.out.println("调用Demo的test方法,返回值是" + test);
//睡眠3秒执行一次
TimeUnit.SECONDS.sleep(3);
}
}
}

可以看到,Demo类每次循环都被重新加载了,因为每次加载他的加载器都是不同的实例,如果是同一个类加载器实例加载同一个类,那么这个类就不会被重新加载.这也是很多框架实现的热加载功能
可以总结出来:一个类是否会被重复加载取决于类加载器实例ID+类的全名
双亲委派模型
概念
什么是双亲委派模型,这是一种对类加载器的逻辑定义方式.没有明确的指定父子关系

这是为了避免重复加载和保证沙箱安全的一种手段,由下到上逐级往上委托,由上到下逐级查找
首先不会自己尝试去加载类,而是把这个请求委派给父加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给上层的核心类库加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载.
这种设计也挺好理解,如果不走这个模式,假设开发者自己也定义一个java.lang.String的类,这时候java到底该使用哪个String呢?所以官方的说法是为了保证沙箱安全
案例
在之前类加载案例-小结中说到一个类是否会被重复加载取决于类加载器实例ID+类的全名,现在有了双亲委派的概念以后我们再测试一下,是否真的就像我们所说的一样,子类不会自己尝试去加载而是先交由它的父类加载
声明一个父类加载器加载urls让它可以搜素到子类加载器加载类的地方,然后实例化子类加载器时传入父类加载器定义一个逻辑关系,如下
package com.sydata.cscm.transfer.action;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.TimeUnit;
/**
* @author pangbohuan
* @date 2020/6/1 0001 15:25
* @description 类加载测试
*/
public class LoadText {
public static void main(String[] args) throws Exception {
URL url = new URL("file:E:\\word\\pbh.Cloud.Parent\\common-basics\\target\\classes\\");
URL[] urls = new URL[]{url};
// 定义一个父类加载器
URLClassLoader parentClassLoader = new URLClassLoader(urls);
System.out.println("父类加载器:" + parentClassLoader);
while (true) {
//子类加载器实例化时声明一下所属的父类加载器
URLClassLoader urlClassLoader = new URLClassLoader(urls, parentClassLoader);
Class<?> aClass = urlClassLoader.loadClass("com.pbh.common.basics.Demo");
System.out.println("Demo使用的的类加载器:" + aClass.getClassLoader());
Object object = aClass.newInstance();
Object test = aClass.getMethod("test").invoke(object);
System.out.println("调用Demo的test方法,返回值是" + test);
//睡眠3秒执行一次
TimeUnit.SECONDS.sleep(3);
}
}
}

从输出结果可以看到
Demo使用的类加载器从始至终都是父类加载器的地址,也就证明了子类不会尝试自己去加载
Demo类的静态方法执行只调用了一次,也就证明Demo只被加载了一次
7769

被折叠的 条评论
为什么被折叠?



