9.2JVM 虚拟机架构原理
1.JVM虚拟机组成架构
Java是一种跨平台的语言,JVM屏蔽了底层系统的不同,为Java字节码文件构成了一个统一的环境。
java主程序: java org.apache.catalina.startup.Bootstrap "$@" start ------tomcat启动类
命令解析: 1. java:java可执行程序,启动JVM进程
2. org.apache.catalina.startup.Bootstrap:Class类文件。二进制.class文件。
3."$@" start :作为参数提交给class类的main函数
JVM执行解析:
1.启动JVM进程: java命令
2.加载类文件:通过类加载器加载类文件。比如:org.apache.catalina.startup.Bootstrap
3.加载到运行期数据区:将类文件加载到方法区(线程共享区)。
4.JVM创建主线程:JVM创建主线程,并为主线程分配JAVA栈和程序计数寄存器。
5.程序计数寄存器:记录方法区可执行字节码指令的位置。
6.执行引擎获取可执行字节码指令:java class的字节码指令,将字节码指令解释或者编译为本地操作系统的指令,因为JVM是跨平台的,可以再不同的操作系统上,不同的CPU硬件上去执行。同样的.class文件可以再不同操作系统,不同硬件上执行,就是要靠执行引擎,把.class字节码指令转换成本地的执行指令。
7.创建对象: 在字节码执行过程中,需要创建一个对象。创建的对象放在堆里面。new指令,存放在方法区中的一行字节码指令。 指令由线程执行,创建完成的对象存放在堆中。而对象的变量引用存放在线程栈上。
2.JVM字节码文件
Java如何实现在不同操作系统上,不同硬件平台上,都可以不用修改代码就能顺畅的执行?
计算机领域的任何问题都可以通过增加个中间层(虚拟层)来解决
Java所有的指令有200个左右,一个字节(8位)可以存储256种不同的指令信息,一个这样的字节成为字节码(Bytecode)。
在代码的执行过程中,JVM将字节码解释执行,屏蔽对低层操作的依赖,JVM也可以将字节码编译执行,如果是热点代码,
会通过JIT动态的编译为机器码,提高执行效率。
3.字节码执行流程
4.Java字节码文件编译过程
4.类加载器的双亲委派模型
低层次的当前类加载器,不能覆盖更高层次类加载器已经加载的类。
如果低层次的类加载器想加载一个未知类,需要上级类加载器确认,只有当上级类加载器没有加载过这个类,也允许加载的时候,
才让当前类加载器加载这个位置类。
5.自定义类加载器
隔离加载类:同一个JVM中不同组件加载同一个类的不同版本。Tomcat加载多个Web应用。
扩展加载源:从网络,数据库等处加载字节码。
字节码加密:加载自定义的加密字节码,在ClassLoader中解密。
public class CustomClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name)throws ClassNotFoundException{
try{
byte[] result=getClassFromCustomPath(name);
if(result==null){
throw new FileNotFoundException();
}else{
return defineClass(name,result,0,result.length);
}
}catch(Exception e){
e.printStackTrace();
}
throw new ClassNotFoundException(name);
}
private byte[] getClassFromCustomPath(String name){
//从自定义路径中加载指定类
}
}
public static void main(String[] args){
CustomClassLoader customClassLoader=new CustomClassLoader();
try{
Class<?> clazz=Class.forName("one",true,customClassLoader);
Object obj=class.newInstance();
System.out.println(obj.getClass().getClassLoader());
}catch(Exception e){
e.printStackTrace();
}
}
6.堆 & 栈
堆:每个JVM实例唯一对应一个堆,应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所拥有的所有线程共享。
堆栈:JVM为每个新创建的线程都分配一个堆栈。也就是说,对于每个JAVA线程来说,它的运行就是通过对堆栈的操作来完成的。
Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配。也就是说在建立一个对象时从两个地方分配内存,
在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的引用而已。
7.方法区 & 程序计数器
方法区:主要存放从磁盘加载进来的类字节码,而在程序运行过程中创建的类实例则存放在堆里。程序运行的时候,实际上是以线程为单位运行的。
当JVM进入启动类的main方法的时候,就会为应用程序创建主线程,main方法里的代码就会被这个主线程执行。每个线程有自己的Java栈,栈里存放着
方法运行期的局部变量。而当前线程执行到哪一行字节码指令,这个信息责备存放在程序计数寄存器。
public class Demo01{
public static void main(String[] args){
A a=new A();
System.out.println(a.width);
}
}
class A{
public static int width=100;
static {
System.out.println("静态初始化类A");
width=300;
}
public A(){
System.out.println("创建A类的对象");
}
}
8.Java(线程)栈
所有在方法内定义的基本类型变量,都会被每个运行这个方法的线程放入自己的栈中,线程的栈彼此隔离,所以这些变量一定是线程安全的。
9.线程工作内存 & volatile
Java内存模型规定在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内存拷贝主内存变量副本来进行。
一个共享变量(类的成员变量,类的静态成员变量)被volatile修饰之后,保证了不同线程对这个变量进行操作时的可见性,
即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
10.Java运行环境
评论