JVM
Java编译器
JVM解释器
JVM结构
类加载器子系统:类加载器子系统用于将编译好的.Class文件加载到JVM中(后面有详细介绍)
执行引擎和本地方法接口:
执行引擎包括即时编译器和垃圾回收器,即时编译器用于将Java字节码编译成具体的机器码,垃圾回收器用于回收在运行过程中不再使用的对象;本地接口库用于调用操作系统的本地方法库完成具体的指令操作
运行时数据区:
运行时数据区用于存储在JVM运行过程中产生的数据,包括程序计数器、方法区、本地方法区、虚拟机栈和虚拟机堆;
- 方法区:存储已被虚拟机加载的类元数据信息(元空间)
- 堆:存放对象实例,几乎所有的对象实例都在这里分配内存
- 虚拟机栈:虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息
- 程序计数器:当前线程所执行的字节码的行号指示器
- 本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务。
JVM的生命周期
虚拟机的启动
java虚拟机通过引导类加载器(bootstrap ClassLoader)创建一个初始类(initial class)来完成,这个类是由虚拟机的具体实现指定的
虚拟机的执行
一个运行中的java虚拟机有着一个清晰的任务:执行java程序
执行一个java程序的时候,真真正正在执行的是一个叫做java虚拟机的进程
虚拟机的退出
有如下几种退出的情况
1.程序正常执行结束
2.程序在运行的过程中遇到了异常或错误而异常终止
3.由于操作系统出现错误而导致java虚拟机进程终止
4.某线程调用Runtime类或System类的exit方法或Runtime类的halt方法
类加载
ClassLoader
类加载器子系统ClassLoader负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
类加载器分为四种:前三种为虚拟机自带的加载器。
启动类加载器(Bootstrap)C++
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
扩展类加载器(Extension)Java
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
应用程序类加载器(AppClassLoader)Java
也叫系统类加载器,负责加载classpath(java.class.path)中指定的jar包及目录中class
用户自定义加载器 Java.lang.ClassLoader的子类,用户可以定制类的加载方式
public class LockDemo { public static void main(String[] args) { //LockDemo 是用户的自定义类 由应用类加载器加载 它的父类加载器是扩展类加载 扩展类的父类加载器是启动类加载器 //AppClassLoader System.out.println(LockDemo.class.getClassLoader()); //ExtClassLoader System.out.println(LockDemo.class.getClassLoader().getParent()); //启动类加载器是C++实现的 java中无法获取 System.out.println(LockDemo.class.getClassLoader().getParent().getParent()); //String是rt包中的类 System.out.println("==========================="); System.out.println(String.class.getClassLoader()); System.out.println(String.class.getClassLoader().getParent()); System.out.println(String.class.getClassLoader().getParent().getParent()); } }
控制台运行效果:符合预期
打印控制台中的sun.misc.Launcher,是一个java虚拟机的入口应用
各种类加载器所加载的文件
public static void main(String[] args) {
//应用类加载器会加载 当前项目编译后的classess目录下的所有的文件
System.out.println("AppClassLoader加载的文件: ");
System.out.println(System.getProperty("java.class.path"));
System.out.println("ExtClassLoader加载的文件: ");
System.out.println(System.getProperty("java.ext.dirs"));
//启动类加载器加载路径
System.out.println("BootstrapClassLoader加载的文件: ");
for (URL url : Launcher.getBootstrapClassPath().getURLs()) {
System.out.println(url);
}
}
双亲委派模型
classloader的双亲委托机制是指多个类加载器之间存在父子关系的时候,某个class类具体由哪个加载器进行加载的问题。
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上
工作过程:
1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载
5、如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException
捕获式加载
双亲委派模型好处
安全,可避免用户自己编写的类动态替换Java的核心类,如java.lang.String ,防止内存中出现多份同样的字节码(安全性角度)
通过这种层次模型,可以避免类的重复加载,也可以避免核心类被不同的类加载器加载到内存中造成冲突和混乱,从而保证了Java核心库的安全。
类加载过程
ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
阶段一:加载阶段
阶段一主要就是生成了Class对象
那么加载的字节码文件来源于哪里?
阶段二:链接
验证
字节码文件在文件开头有特定的文件标识,CA FE BA BE
===================
准备
====================
解析
阶段三:初始化
clinit方法,有类变量的赋值会自动帮我们生成该方法
clinit方法注意点
也就是说:多线程并发情况下一个类只会被加载一次
===============
一个小测试,非法的前向引用
================
init方法
构造器对应字节码文件中 init方法。
任何一个类声明以后,内部至少存在一个类的构造器
PC寄存器
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,即 将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
JVM参数设置
堆空间的常用参数设置
Java堆区在JVM启动的时候即被创建,其空间大小也就确定。是JVM管理的最大一块内存空间。堆内存的大小是可以调节的。
怎么对jvm进行调优?通过参数配
官网: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
java代码查看jvm堆的默认值大小:
//堆的最大值,默认是内存的1/4
System.out.println("max : "+Runtime.getRuntime().maxMemory()*1.0/1024/1024+" MB ");
//堆的当前总大小,默认是内存的1/64
System.out.println("init : "+Runtime.getRuntime().totalMemory()*1.0/1024/1024+" MB ");
在哪里设置JVM参数
idea运行时设置方式如下:
不加分号,中间有空格。
-Xms10m -Xmx30m
重新测试这段代码:
System.out.println("max : " + Runtime.getRuntime().maxMemory() * 1.0 / 1024 / 1024 + " MB "); System.out.println("init : " + Runtime.getRuntime().totalMemory() * 1.0 / 1024 / 1024 + " MB ");
控制台打印如下: