jvm之java程序从编写到执行的结构链路
![](https://i-blog.csdnimg.cn/blog_migrate/40c2d301e17174d2b763ca4524ce031c.png)
1.java前端编译器负责将java源代码编译为字节码-->前端编译器
2.java虚拟机负责将编译好的字节码装载进内部-->java类加载步骤和运行时区域
类加载过程:加载、链接、初始化
类加载器
![](https://i-blog.csdnimg.cn/blog_migrate/05acfdd8239e8583d00b31240fc9ec36.png)
双亲委派模型
双亲委派模型(Parent Delegation Model):
类的加载过程采用双亲委托机制,这种机制能更好的保证 Java 平台的安全。
该模型要求除了顶层的Bootstrap class loader启动类加载器外,其余的类加载器都应当有自己的父类加载器
。子类加载器和父类加载器不是以继承(Inheritance)的关系
来实现,而是通过组合(Composition)关系
来复用父加载器的代码。每个类加载器都有自己的命名空间(由该加载器及所有父类加载器所加载的类组成,在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类)
双亲委派模型的工作过程为:
1.当前 ClassLoader 首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存, 等下次加载的时候就可以直接返回了。
2.当前 classLoader 的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到 bootstrap ClassLoader.
3. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
使用这种模型来组织类加载器之间的关系的好处:
主要是为了安全性
,避免用户自己编写的类动态替换 Java 的一些核心类,比如 String,同时也避免了重复加载
,因为 JVM 中区分不同类,不仅仅是根据类名,相同的 class 文件被不同的 ClassLoader 加载就是不同的两个类,如果相互转型的话会抛java.lang.ClassCaseException.
类加载源码
ClassLoader 是一个抽象类,负责加载类,像 ExtClassLoader,AppClassLoader 都是由该类派生出来,实现不同的类装载机制。这块的源码太多了,就不贴了。
我们来看下 它的核心方法loadClass(),传入需要加载的类名,它会帮你加载:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 一开始先 检查是否已经加载该类
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果未加载过类,则遵循 双亲委派机制,来加载类
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果父类是null就是BootstrapClassLoader,使用 启动类类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
long t1 = System.nanoTime();
// 如果还是没有加载成功,调用findClass(),让当前类加载器加载
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 Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
自定义类加载器(并且打破双亲委派加载)
编写一个自定义的类加载器
那我们仿照ExtClassLoader,AppClassLoader 来实现一个自定义的类加载器,我们同样是继承ClassLoader类
编写一个测试类TestPerson
public class TestPerson {
String name = "xiao ming";
public void print(){
System.out.println("hello my name is: "+ name);
}
}
接着 编写一个自定义类加载器MyTestClassLoader:
public class MyTestClassLoader extends ClassLoader {
final String classNameSpecify = "TestPerson";
public MyTestClassLoader() {
}
public MyTestClassLoader(ClassLoader parent)
{
super(parent);
}
protected Class<?> findClass(String name) throws ClassNotFoundException
{
File file = getClassFile(name);
try
{
byte[] bytes = getClassBytes(file);
Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
return c;
}
catch (Exception e)
{
e.printStackTrace();
}
return super.findClass(name);
}
private File getClassFile(String name)
{
File file = new File("D:\\ideaProjects\\src\\main\\java\\com\\zj\\ideaprojects\\test2\\"+ classNameSpecify+ ".class");
return file;
}
private byte[] getClassBytes(File file) throws Exception
{
// 这里要读入.class的字节,因此要使用字节流
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel wbc = Channels.newChannel(baos);
ByteBuffer by = ByteBuffer.allocate(1024);
while (true)
{
int i = fc.read(by);
if (i == 0 || i == -1)
break;
by.flip();
wbc.write(by);
by.clear();
}
fis.close();
return baos.toByteArray();
}
//我们这边要打破双亲委派模型,重写整个loadClass方法
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
if (c == null && name.contains(classNameSpecify)){//指定的类,不走双亲委派机制,自定义加载
c = findClass(name);
if (c != null){
return c;
}
}
return super.loadClass(name);
}
}
最后在编写一个测试controller:
@RestController
public class TestClassController {
@RequestMapping(value = "testClass",method = {RequestMethod.GET, RequestMethod.POST})
public void testClassLoader() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
MyTestClassLoader myTestClassLoader = new MyTestClassLoader();
Class<?> c1 = Class.forName("com.zj.ideaprojects.test2.TestPerson", true, myTestClassLoader);
Object obj = c1.newInstance();
System.out.println("当前类加载器:"+obj.getClass().getClassLoader());
obj.getClass().getMethod("print").invoke(obj);
}
}
结果:
当前类加载器:com.zj.ideaprojects.test2.MyTestClassLoader @1d75e392 hello my name is: xiao ming
3.最后解释/编译为对应平台上的机器指令执行-->jvm执行引擎和垃圾回收GC
Java字节码的执行有两种方式:
1.即时编译方式:解释器先将字节码编译成机器码,然后再执行该机器码。
2.解释执行方式:解释器通过每次解释并执行一小段代码来完成Java字节码程 序的所有操作。
hotspot vm 是jdk和openjdk中缺省自带的一款虚拟机。在hotspot vm内部,即时编译器和解释器是并存的。这是因为在虚拟机启动的时候,解释器可以首先发挥作用,而不必等待编译器全部编译完成再执行,这样可以省去许多不必要的编译时间。并且随着程序运行时间的推移,编译器逐渐发挥作用,根据热点探测功能,将有价值(频繁被调用的方法)的字节码编译成本地机器指令,以换取更高的程序执行效率。