JVM
一、jvm运行流程
二、jvm结构
1.jvm基本结构
类加载器,执行引擎,运行时数据区,本地接口
Class Files -> ClassLoader -> 运行时数据区 -> 执行引擎,本地库接口 -> 本地方法库
其中对于方法区,很多人更愿意称为:“永久代(Permanent Generation)”,不过本质上两者并不等价,仅仅是因为习惯使用HotSpot虚拟机的设计团队选择吧GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器就可以像管理Java堆一样管理这部分内存,能够省去专门为方法区变编写内存管理代码的工作。不过对于其他虚拟机(如BEA JRockit、IBM J9等)来说并不存在永久代的概念
这是jdk1.8之前的内存模型,其中方法区和堆是是线程共享的,但是在jdk1.8之后
元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存
- 程序计数器(PC)
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来取下一条需要执行的字节码指令,分支、跳转、循环、异常处理、线程恢复等基础功能都需要这个计数器来完成
注:程序计数器是线程私有的,每条线程都会有一个独立的程序计数器 - Java栈(虚拟机栈)
Java栈就是Java中的方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,这个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
注:Java栈也是线程私有的。
异常可能性:对于栈有两种异常情况:如果线程请求的栈深度大于栈所允许的深度,将抛出StackOverflowError异常,如果虚拟机栈可以动态拓展,在拓展的时无法申请到足够的内存,将会抛出OutOfMemoryError异常 - 本地方法栈
本地方法栈与Java栈所发挥的作用是非常相似的,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法。
注:本地方法栈也是线程私有的
异常可能性:和Java栈一样,可能抛出StackOverflowError和OutOfMemeryError异常 - Java堆
对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,当然我们后面说到的垃圾回收器的内容的时候,其实Java堆就是垃圾回收器管理的主要区域。
注:堆是线程共享的
异常可能性:如果堆中没有内存完成实例分配,并且堆也无法再拓展时,将会抛出OutOfMemeryError异常 - 方法区
方法区它用于存储已被虚拟机加载的类信息、常量、静态常量、即时编译器编译后的代码等数据。
注:方法区和堆一样是线程共享的
异常可能性:当方法区无法满足内存分配需求时,将抛出OutOfMemeryError异常
2.类的加载
加载 , 连接( 验证 , 准备 , 解析 ) , 初始化 , 使用 , 卸载
-
加载: 生成Class 保存类的定义或者结构,放在堆中
-
连接:
- 验证: 文件格式,元数据,字节码,符号引用进行验证,保证class文件字节流中的信息合法;
- 准备: 正式为类属性分配内存并为属性设置初始值,内存将在方法区中分配(static,static final对象赋值)
- 解析: 常量池中的符号引用替换为直接引用
- i. 符号引用: 字符串,引用对象不一定被加载
- ii. 直接引用: 指针或地址偏移量,引用对象一定在内存中
-
初始化: 执行类的构造器
,为类的静态变量赋予正确的初始值 -
使用
-
卸载
3.类加载器
1. 三个java默认加载器
BootStrap ClassLoader 启动 ClassLoader 加载 rt.jar , resource.jar和class等
Extnsion ClassLoader extends ClassLoader -> %JRE_HOME%libext*.jar
App ClassLoader extends ClassLoader -> Classpath
自定义加载器 extends ClassLoader 完成自定义加载路径
2. 双亲委派机制
当一个ClassLoader实例需要加载某个类时,它会试图先委托给父类的加载器,在它的搜索路径下查找,这个过程是自上而下检查的,从最顶层的bootstrap classloader试图加载,如果没加载到,则转交给下一层类加载器,如果这三个加载器都没有加载到,则会进行回调到委托的发起者,由它指定的文件系统或网络等URL中加载该类.如果都没有加载到该类,则会抛出ClassNotFoundException异常.否则将这个找到的类生成一个类的定义,并将它加载到内存中,最后返回该类在内存中的Class示例对象.
- 作用:
- 避免重复加载
- 考虑到安全因素,避免自定义的类替换系统类,如String
- jvm判断两个class是否相同?
- 类名相同
- 类加载器实例相同
3. custom classloader:自定义classloader
- java提供的默认classloader只会加载指定目录下的jar和class,但加载其他位置的类或者jar,需要使用自定义classloader.
- 以下为代码实现demo,主要分为两步 1. 继承classloader 2. 重写父类的findClass()方法
/**
* 自定义加载器
*/
public class MyClassLoader extends ClassLoader{
private String path;//加载类的路径
private String name;//类加载器名称
public MyClassLoader(String name, String path){
super();//让系统类加载器成为该类的父加载器
this.name = name;
this.path = path;
}
public MyClassLoader(ClassLoader parent, String name, String path){
super(parent);//显示指定父加载器
this.name = name;
this.path = path;
}
/**
* 加载自定义的类,通过自定义的ClassLoader
* com.xxx.xxxx.Demo
*/
@Override
protected Class<?> findClass(String name) throw ClassNotFoundException {
byte[] data = readClassFileToByteArray(name);
return this.defineClass(name,data,0,data.length());
}
/**
* 重写toString,打印该类的类名
*/
@Override
public String toString(){
return this.name;
}
/**
* 获取.class文件的字节数组
* com.xxx.xxxx.Demo -> F:/temp/com/xxx/xxx/Demo.class
* @param name
* @return
*/
private byte[] readClassFileToByteArray(String name){
InputStream is = null;
byte[] returnData = null;
name = name.replaceAll("\." , "/");
String filePath = this.path + name + ".class";
File file = new File(filePath);
ByteArrayOutputStream os = new ByteArrayOutputStream();
try{
is = new FileInputStream(file);
int tmp = 0;
while((tmp =is.read())!= -1){
os.write(tmp);
}
returnData = os.toByteArray();
}catch(Exception e){
e.printStackkTrace();
}finally{
try{
//关闭流,需要判断是否为空,这里不展示,直接关闭
is.close();
os.close();
} catch(Exception e2){
}
}
return returnData;
}
}
/**
* 测试
*/
public class TestDemo{
public static void main(String[] args){
MyClassLoader loader = new MyClassLoader("ZhangsFei","D:/tmp/");
/**
* 命名两个同名的类,Demo,将一个类放在classpath下,另一个放在自定义目录下D:/tmp/,
* 此时会启用双亲委派机制,先去classpath中的包路径下去找,如果找找到Demo这个类,则加载进来,
* 如果没找到,再去自定义目录 D:/tmp/ 下去查找这个类
*/
Class<?> c = loader.loadClass("com.xxx.xxxx.Demo");
C.newInstance();
}
}