JVM深入学习

JVM深入学习

对于反射中的Method类invoke()方法的理解:
invoke方法的主要作用是传递 调用该方法的对象
先讲一下java中的反射:
反射就是将类别的各个组成部分进行剖析,可以得到每个组成部分,就可以对每一部分进行操作。在比较复杂的程序或框架中来使用反射技术,可以简化代码提高程序的复用性。
ClassLoader类的衍生类及其作用:
1.BootStrapClassLoader:C++编写,加载核心库Java.;
2.ExtClassLoader:Java编写;加载扩展库(JRE下的jar包)Java.
;
3.AppClassLoader:Java编写;加载Path程序所在目录(即:idea工作空间);
4.自定义ClassLoader:Java编写,自定义程序文件加载;对存放目录与编写语言无限制;
那么Java里有哪些ClassLoader类加载器呢?简单来说有下面几种:
(1)启动类加载器
Bootstrap ClassLoader,他主要是负责加载我们在机器上安装的Java目录下的核心类的
相信大家都知道,如果你要在一个机器上运行自己写好的Java系统,无论是windows笔记本,还是linux服务器,是不是都得装一下JDK?
那么在你的Java安装目录下,就有一个“lib”目录,大家可以自己去找找看,这里就有Java最核心的一些类库,支撑你的Java系统的运行。
所以一旦你的JVM启动,那么首先就会依托启动类加载器,去加载你的Java安装目录下的“lib”目录中的核心类库。
(2)扩展类加载器
Extension ClassLoader,这个类加载器其实也是类似的,就是你的Java安装目录下,有一个“lib\ext”目录
这里面有一些类,就是需要使用这个类加载器来加载的,支撑你的系统的运行。
那么你的JVM一旦启动,是不是也得从Java安装目录下,加载这个“lib\ext”目录中的类?
(3)应用程序类加载器
Application ClassLoader,这类加载器就负责去加载“ClassPath”环境变量所指定的路径中的类
其实你大致就理解为去加载你写好的Java代码吧,这个类加载器就负责加载你写好的那些类到内存里。
(4)自定义类加载器
除了上面那几种之外,还可以自定义类加载器,去根据你自己的需求加载你的类。
构造方法:
package com.wx.reflect;

public class Robot {
private String name;
public void sayHi(String str){
System.out.println(“sayHiStr:”+str);
System.out.println(“sayHiName:”+name);
}
private String throwHello(String tag){
return “throwHelloStr:”+tag;
}
}
java反射Method类invoke方法的应用:
package com.wx.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestInvoke {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
/* 获取字节码对象 /
Class c = (Class) Class.forName(“com.wx.reflect.Robot”);
/
获取一个构造对象 /
Constructor con = c.getConstructor();
/
构建一个实例 /
Robot robot = (Robot) con.newInstance();
/
通过forNameh方法获取一个Method对象 /
Method method = c.getMethod(“sayHi”,String.class);
/
如传递参数为数组 /
/
第二个参数是一个可变参数类型,这个可变参数类型怎么能传递给一个数组类型呢?一个是多个参数。一个是一个数组参数,显然参数的个数不匹配,怎么解决呢?
解决办法就是将可变参数变成一个参数:
将传递进去的str强转为Object类型
将str重新包装成一个Object数组 /
/String [] str = new String[]{“aa”,“bb”,“cc”};
Method method = c.getMethod(“sayHi”,String[].class);
// 将传递进去的str强转为Object类型 //
method.invoke(robot,new Object[]{str});
// 将str重新包装成一个Object数组 //
method.invoke(robot, (Object) str);
/
/
调用invoke方法来传递参数 */
method.invoke(robot,“hello”);
}
}
java反射getDeclaredMethod;getMethod;getDeclaredField方法的应用
package com.wx.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectSample {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException {
/加载类路径/
Class rc = Class.forName(“com.wx.reflect.Robot”);
/获取实例/
Robot r = (Robot) rc.newInstance();
System.out.println(“Class name is:”+rc.getName());
/通过反射映射到声明的方法:throwHello/
Method throwHello = rc.getDeclaredMethod(“throwHello”, String.class);
/默认是false无法获取私有方法/
throwHello.setAccessible(true);
/* invoke方法用来在运行时动态地调用某个实例的方法 */
Object str = throwHello.invoke(r, “Bob”);
System.out.println(“throwHello result is:”+str);

/通过反射映射到方法;getMethod只能获取公共的方法:sayHi/
Method sayHi = rc.getMethod(“sayHi”, String.class);
sayHi.invoke(r,“Welcome”);

Field name = rc.getDeclaredField(“name”);
name.setAccessible(true);
/* set name = Alice */
name.set(r,“Alice”);
sayHi.invoke(r,“Welcome”);

System.out.println("BootstrapClassLoader加载Jar包路径: "+System.getProperty(“sun.boot.class.path”));
System.out.println("ExtClassLoader加载Jar包路径: "+System.getProperty(“java.ext.dirs”));
System.out.println("AppClassLoader加载Jar包路径: "+System.getProperty(“java.class.path”));

}
}
自定义ClassLoader应用:
1.编写自定义类并通过javac命令编译为二进制.class文件:
public class Wali{
static{
System.out.println(“Hello Wali”);
}
}
3.编写自定义MyClassLoader继承ClassLoader;重写findClass通过调用defineClass方法加载二进制。class文件
package com.wx.reflect;

import java.io.*;

public class MyClassLoader extends ClassLoader {
private String path;
private String classLoaderName;

public MyClassLoader(String path,String classLoaderName){
this.path = path;
this.classLoaderName = classLoaderName;

}
/* 用于寻找类文件 /
@Override
public Class findClass(String name){
byte[] b = loadClassData(name);
/
*/
return defineClass(name,b,0,b.length);
}

/* 用于加载类文件 /
private byte[] loadClassData(String name) {
/
获取字节码文件 /
name = path + name +".class";
/
输入流 /
InputStream in = null;
/
输出流 /
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i = 0;
/
循环遍历输入流二进制文件 /
while ( (i=in.read()) !=-1 ){
/
写入输出流 /
out.write(i);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
/
关闭流 /
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/
返货二进制输出流 */
return out.toByteArray();
}
}
3.编写调用类:
package com.wx.reflect;

public class ClassLoaderChecker {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
/* 获取加载字节码文件路径 /
MyClassLoader m = new MyClassLoader("/C:/Users/Man/Desktop/",“myClass”);
/
获取加载字节码文件 */
Class c = m.loadClass(“Wali”);

/* 输出加载到的类 /
System.out.println(c.getClassLoader());
/
生成新的实列 */
c.newInstance();
}
}
ClassLoader双亲委派加载:
java虚拟机是如何判定两个class是相同的:不仅要看类的全名是否相同,还要看加载器是否相同。
比如Hello.class是编译后的字节码,如果不自定义加载器,那么只会加载一次这个class,但是如果自定义了两个同级classloader,那么则可以加载两个class。
双亲委托主要概念见下图,设计思想主要是避免同一个类被不同的加载器加载,导致额外问题。
加载类之前先看看父类有没有,没有的话看看父类能不能加载,父类有的直接复用,父类能加载的自己绝不加载。(自定义类加载器除外,某些时刻属于定制加功能)

JVM的类加载器是有亲子层级结构的,就是说启动类加载器是最上层的,扩展类加载器在第二层,第三层是应用程序类加载器,最后一层是自定义类加载器。
在这里插入图片描述
然后,基于这个亲子层级结构,就有一个双亲委派的机制
什么意思呢?
就是假设你的应用程序类加载器需要加载一个类,他首先会委派给自己的父类加载器去加载,最终传导到顶层的类加载器去加载
但是如果父类加载器在自己负责加载的范围内,没找到这个类,那么就会下推加载权利给自己的子类加载器。
听完了上面一大堆绕口令,是不是很迷茫?别着急,咱们用一个例子来说明一下。
比如你的JVM现在需要加载“ReplicaManager”类,此时应用程序类加载器会问问自己的爸爸,也就是扩展类加载器,你能加载到这个类吗?
然后扩展类加载器直接问自己的爸爸,启动类加载器,你能加载到这个类吗?
启动类加载器心想,我在Java安装目录下,没找到这个类啊,自己找去!
然后,就下推加载权利给扩展类加载器这个儿子,结果扩展类加载器找了半天,也没找到自己负责的目录中有这个类。
这时他很生气,说:明明就是你应用程序加载器自己负责的,你自己找去。
然后应用程序类加载器在自己负责的范围内,比如就是你写好的那个系统打包成的jar包吧,一下子发现,就在这里!然后就自己把这个类加载到内存里去了。
这就是所谓的双亲委派模型:先找父亲去加载,不行的话再由儿子来加载。
这样的话,可以避免多层级的加载器结构重复加载某些类。
最后,给大家来一张图,感受一下类加载器的双亲委派模型。
在这里插入图片描述
在这里插入图片描述
如上图所示:双亲委派ClassLoader加载时序图。查找class对象,首先从自定义ClassLoader开始查找;如果没有则向上加载父类AppClassLoader->ExtClassLoader->BootStrapClassLoader->对应路径%JAVA_HOME%\jre\lib中加载->对应路径%JAVA_HOME%\jre\lib\ext中加载->对应路径%JAVA_HOME%\中加载->自定义路径加载(通过ClassLoader类findClass方法加载自定义.class二进制编译文件;详情见上图自定义ClassLoader应用);
在这里插入图片描述
为什么要使用双亲委派机制去加载类:避免多分相同的字节码文件被加载,消耗内存
类的加载方式:
在这里插入图片描述
在这里插入图片描述
隐式加载:new
显示加载:loadClass;forName等(详情见上 java反射Method类invoke方法的应用);
loadClass与forName的区别:
loadClass属于延迟加载在调用时只实现了第一步加载;
forName属于及时加载,在调用时就实现了对象的加载-链接-初始化;
Class.forName得到的class是已经初始化完成的
ClassLoader.loadClass得到的class是还没有链接的
loadClass与forName的应用场景:
Spring在加载Bean对象时就需要使用loadClass方法延迟加载,为节省时间,将Bean一次全部加载进内存,具体需要使用到那个Bean对象时在执行初始化;
mysql 获取Driver信息时就需要使用forName方法及时加载;
在这里插入图片描述
loadClass方法应用
在这里插入图片描述
在这里插入图片描述
forName方法应用:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值