-
Java虚拟机
- jvm的四大部分
ClassLoader:根据特定格式,加载class文件
Execution Engine:对命令进行解析
Native Interface: 融合不同开发语言的原生库 native方法(class.forName())
Runtime Data Area:JVM内存空间结构模型 - Jvm如何加载.class文件
通过Class Loader符合其格式要求的 .class加载到内存,通过ExecutionEngine对class的字节码进行解析为机器码,交给操作系统去执行 - 谈谈你对反射的理解?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
- jvm的四大部分
-
谈谈ClassLoader?
- ClassLoader在java中有着非常重要的作用,它主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流。它是JAVA的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接,初始化等操作
- ClassLoader的种类
-
启动类加载器(Bootstrap ClassLoader):这个类加载器负责装载Java API(java 核心类库) 。将存放在\lib 目录中的或者被-Xbootclasspath参数所指定的路径中的,并且是被虚拟机识别的类库加载到虚拟机内存中。这个类加载器是在JVM启动的时候创建的,和其他的类装载器不同的地方在于这个装载器是通过native code来实现的,而不是用Java代码。
-
扩展类加载器(Extension classLoader):它负责加载\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可用直接使用扩展类加载器。
-
应用程序类加载器(Application ClassLoader):也称为系统类加载器,它负责加载类路径(ClassPath) 上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己
-
自定义加载类
- 代码
public class MyClassLoader extends ClassLoader{
//指定加载类的目录 private String path; public MyClassLoader(String path){ this.path = path; } @Override public Class findClass(String className){ byte [] b = loadClassData(className); System.out.println(b.length); return defineClass(null, b, 0, b.length,null); } private byte[] loadClassData(String className) { FileInputStream in = null; ByteArrayOutputStream out = null; try{ String classpath = path+className+".class"; in = new FileInputStream(classpath); out = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int len = -1; while((len=in.read(b))!=-1){ out.write(b,0,len); } } catch (IOException e) { e.printStackTrace(); }finally{ try { in.close(); out.close(); } catch (IOException e) {} } return out.toByteArray(); }
}
-
-
谈谈ClassLoader的双亲委派机制
- 1.自底向上检查类是否已经被加载
- 2.自顶向下尝试加载类
执行流程:比如现在我们第一次加载String类,首先到Custom ClassLoader即自定义的ClassLoader进行查找是否曾经加载过String类,如果有,直接返回Class对象。如果没有,到父级的AppClassLoadr进行查找是否曾经加载过String类,如果没有,继续往父级ExtensionClassLoader中检查是否已经加载过。如果仍然没有到BootStrap ClassLoader中检查是否被加载过。
我们知道String类是核心类(Bootstrap ClassLoader):这个类加载器负责装载Java API(java 核心类库)。如果到BootStrap ClassLoader中检查也没被加载过。就会到对应的路径下的jar包找到指定的class文件进行加载。
这就是自底向上检查,自顶向下加载的流程- 下面深入到源码进行解释
-
ClassLoader的子父关系
public class TestMyClassLoader { public static void main(String[] args) throws Exception { MyClassLoader myClassLoader = new MyClassLoader("D:\\idea\\intellij Work_Space\\study\\out\\production\\study\\jvm\\"); Class clazz = myClassLoader.findClass("Myclass"); Object o = clazz.newInstance(); System.out.println(myClassLoader.getParent()); System.out.println(myClassLoader.getParent().getParent()); System.out.println(myClassLoader.getParent().getParent().getParent()); } }
输出结果
486
这是自定义加载器测试类
sun.misc.Launcher A p p C l a s s L o a d e r @ 18 b 4 a a c 2 s u n . m i s c . L a u n c h e r AppClassLoader@18b4aac2 sun.misc.Launcher AppClassLoader@18b4aac2sun.misc.LauncherExtClassLoader@1540e19d
null
可以看到自定义加载器的父类是AppClassLoader,AppClassLoader的父类是ExtClassLoader,
ExtClassLoader父类显示为null。为什么ExtClassLoader的父级为空呢?因为ExtClassLoader的父级时BootStrapClassLoader是底层的代码,我们看不到我们接着分析源码!!!
下面我们来看ClassLoader的loadClass方法
在loadclass方法中
1.首先判断当前加载器是否加载过该类字节码文件,如果加载过直接返回类的Class对象//12.如果当前加载器没有加载过,如果有父级加载器,调用父级的loadclass方法去加载//2//3
3.如果没有父级加载器,调用findBootstrapClassOrNull(name),调用底层的BootStrapClassLoadder加载器检查该类是否被加载过,如果加载过直接返回Class对象,如果没有加载过,到对应的路径下的jar包寻找该类进行加载并返回//4
4.如果对应的jar包找不到该类字节码文件 调用子级的加载器到对应路径进行加载 //5
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);//1 if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) {//2 c = parent.loadClass(name, false);//3 } else { c = findBootstrapClassOrNull(name);//4 } } 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);//5 // 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; } }
-
- 为什么要用双亲委派机制进行加载?
节约内存空间,如果某个加载器进行加载过类,它会到内存中保存一份。如果又要用到该类时,自底向上检查类加载器,检查到了直接返回即可。 - ClassLoader.loadClass()和Class.forName() 区别?
类的加载步骤
class加载到JVM中有三个步骤-
装载:(loading)找到class对应的字节码文件。
-
连接:(linking)将对应的字节码文件读入到JVM中。
-
初始化:(initializing)对class做相应的初始化动作
1.用ClassLoader.loaderClass()加载不会进行连接,初始化操作
protected Class<?> loadClass(String name, boolean resolve)
我们看loadClass方法中的resovle参数,如果为false表示不进行连接操作,而底层默认调用的
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
2.用Class.forName()加载会进行初始化操作
初始化操作指的是初始化静态变量,执行静态代码块等 -
栗子
JDBC注册驱动用 Class.forName(“com.mysql.jdbc.Driver”)
public class Driver extends NonRegisteringDriver
implements java.sql.Driver
{
//注意,这里有一个static的代码块,这个代码块将会在class初始化的时候执行
static
{
try
{
//将这个驱动Driver注册到驱动管理器上
DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException(“Can’t register driver!”);
}
}
}我们看出mysql 的驱动包源码中有一个静态代码块用来注册驱动,所以我们用Class.forName()
如果我们用ClassLoader.loaderClass()加载,驱动就不会注册到驱动管理器上,我们就不能正常和数据库连接了!
-
-
java内存模型
- 从线程角度来考虑
- 每个线程独占的有程序计数器,虚拟机栈,本地方法栈
-
程序计数器:
1.当前线程所执行的字节码行号指示器
2.改变计数器的值来选取下一条需要执行的字节码指令
3.如果时Native方法则计数器的值为Undefine -
虚拟机栈:每次java方法调用后创建栈帧压入栈顶,方法执行结束后栈帧弹出栈,释放内存空间
1.java方法执行的内存模型
2.包含多个栈帧
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191018234401673.JPG主要有局部变量表,操作栈,操作指令,返回地址构成
操作栈用作计算结果,局部变量表用作缓存参与计算的值。动态连接和返回地址在方法的上下文切换上起作用。
StackOverflow异常:每调用一次方法就会给虚拟机栈压入栈帧,当递归调用的层数很深时,栈就会爆掉
OutOfMemerory异常:如果方法中定义了很多局部变量,方法又递归的调用自身。造成内存不足
虚拟机栈为什么么不用gc?因为调用时会生成栈帧,执行结束后会自动销毁 -
本地方法栈
和虚拟机栈相同,用于native方法执行的内存模型
-
- 线程共享的
- 1.方法区
主要用于存储类的信息、常量池、方法数据、方法代码
在jdk1.8以前叫做永久代,使用的是jvm内存
jdk1.8以后叫做元空间,使用的是本地内存,字符串常量池换到了堆中存储
为什么替换?
字符串常量池存在永久代中,容易出现性能问题和内存溢出
类和方法的信息大小难以确定,给永久代的大小指定带来困难
永久带会为GC带来不必要的复杂性 - 2.堆空间
堆空间用来保存实例变量的信息
- 1.方法区
- 每个线程独占的有程序计数器,虚拟机栈,本地方法栈
- 从线程角度来考虑
-
java内存模型面试题目
- jvm三大调优性能参数 -Xms,-Xmx,-Xss
-Xss 虚拟机栈的大小
-Xms head的初始值
-Xmx head的最大值
Xms 和 Xmx的值最好设置相同,否则扩容的是否会出现内存抖动现象。影响程序的稳定性 - Java内存中堆和栈的区别
堆空间和栈的联系:引用对象,数组时,栈里定义变量保存堆中目标的引用
- jvm三大调优性能参数 -Xms,-Xmx,-Xss
区别:
管理方式:栈自动释放,堆需要GC
空间大小:栈比堆小
碎片相关:栈产生的碎片远小于堆
分配方式:栈支持静态分配和动态分配,而堆空间仅支持动态分配
效率:栈的效率比堆高
总结:元空间:存储类的字段,方法。
堆空间:存储实例变量。
线程独占空间:有本地栈空间,虚拟机栈空间(方法链接,局部变量表,操作栈,操作指令),程序计数器。