一 Java虚拟机运行时数据区 :
共享:
堆:内存中最大的一个区域,对象实例和数组内部会划分出多个线程私有分配缓冲区;
方法区:存储已经被虚拟机加载的类信息敞常量,静态变量 即使编辑器后的代码等数据;
私有:
虚拟机栈:局部变量表 操作数栈 方法出口;
本地方法栈:Native方法;
程序计数器:虚拟机字节码指令的地址或者UNdefined;
3. 稍微具体的结构图:
编译好class文件后才能交给JVM区执行,之后 class文件需要通过类加载机制把类加载到方法区 ,元数据区,然后需要交给执行引擎 将类中的指令解析出来去执行,
在执行的过程当中 ,再根据类去创建对象, 将对象创建到堆中,然后具体的执行某些逻辑就是在线程里面了,(线程时最小的执行单位);
栈帧:
jvm. 给每一个线程分配一个虚拟机栈 这个栈是一个先进后出的结构, 这个栈会记录这个线程运行时候所需要的数据;在栈帧中会保留对堆中对象的引用。
在每个栈帧中包含的部分:
(1)局部变量表
(2)操作数栈。 【这两个是执行Java程序所需要的做基础的数据结构】
动态连结库:主要是指向方法区的某一个方法;
返回地址:方法执行完毕返回到的位置;
附加信息:JVM在具某些具体实现需要;
程序计数器:记录程序运行到了哪一步?【因为CPU是在线程之间不停轮换的,下一个CPU是怎么知道 我线程执行到哪里的了呢,这里就需要提到程序计数器的用处】
插一句嘴:class文件可以说是JVM 的一个规范,文件中可以有版本号来版本兼容,执行的JVM虚拟机版本号必须>这个文件版本号;
二 虚拟机的本质以及整体流程:
(1)本质:
将我们的class文件转化成操作系统具体的指令,class文件通过JDK的类加载模块将文件加载到内存当中去。
(2)流程
1. 下面是我在整理到JVM 整个流程:
这个是网上流传的流程图 ,但是这里面我进行了补充下面相续说一下整个连接的过程 分成三部分。
第一部分 :验证阶段 ,这里面我进行了一个补充 ,验证是在三个地方进行的:对文件格式验证发生在加载阶段,如果通过可以顺利加载,加载后方方法区就在存在一个静态结构,堆中也存在了该class类型的对象,但是程序如果想用这个类,就一定要进行连接!
对元数据字节码验证: 堆class静态结构惊醒语法语义上的分析,保证不会产生危害虚拟机行为的,解析阶段可以在初始化之前和之后进行的,验证包含了很多步骤,分散在不同阶段。
第二部分 准备过程:
这里注意 : 方法区是抽象概念,永久带和元空间是具体实现!!要分清这个概念
第三部分 解析过程 :
问题一: A如何找到B呢?
此时在A 的class文件中将使用一个字符串代表B的地址。如果A发生了类加载 那么到了B就会触发B的类加载此时符号引用会被替换成B的实际地址。
如果不考虑异常的话,那么 JVM 虚拟机执行代码的逻辑就应该是这样:
do{
从程序计数器中读取 PC 寄存器的值 + 1;
根据 PC 寄存器指示的位置,从字节码流中读取一个字节的操作码;
if(字节码存在操作数) 从字节码流中读取对应字节的操作数;
执行操作码所定义的操作;
}while(字节码流⻓度>0)
三 类加载中双亲委派机制:
1. 双亲委派概念:
如果一个类加载器 收到了类加载请求 他首先不会自己去尝试加载这个类 而是把请求委托给父加载器完成 依次而上,因此所有的类加载请求都应该被传递到顶层的启动类加载其中 只有当夫类加载器在他的搜索范围中 没有找到所需的类的时候,子加载器才会尝试自己去加载该类。
2. 双亲委派机制:
默认情况下 一个限定名的类 只会被一个类加载起加载并解析使用 这样在程序中他就是唯一的 不会产生歧义。
注意⚠️。里面所说的父加载器 子加载器 并非继承关系 而是逻辑关系。
3.双亲委派是怎么开始的?
首先检查该类是否已经被加载 如果已经加载 就直接读取缓存,如果没加载 就开启加载流程
双亲委派的作用?
保证Java 内置的一些类 无法被覆盖 无法被修改。比如Java.lang.object为了保证父类的安全——————————还有一个补充机制:沙箱保护机制
双亲委派就是向上委托查找 向下委托加载;(这里面的查找是从缓存中照 找到就可以直接返回 没找到就是父加载器中找)
关于类加载模块,以 JDK8 为例,最为重要的内容我总结为三点:
每个类加载器对加载过的类保持一个缓存。
双亲委派机制,即向上委托查找,向下委托加载。
沙箱保护机制。
JDK8中的类加载器都继承于一个统一的抽象类:
//类加载器的核心方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 每个类加载起对他加载过的类都有一个缓存,先去缓存中查看有没有加载过
Class<?> c = findLoadedClass(name);
if (c == null) {】
//没有加载过,就走双亲委派,找父类加载器进行加载。
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
// 父类加载起没有加载过,就自行解析class文件加载。
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
//这一段就是加载过程中的链接Linking部分,分为验证、准备,解析三个部分。
// 运行时加载类,默认是无法进行链接步骤的。
if (resolve) {
resolveClass(c);
}
return c;
}
}
这个方法里,就是最为核心的双亲委派机制。并且,这个方法是protected声明的,意味着,是可以被子类覆盖的,所以,双亲委派机制也是可以被打破的。
那么问题来了。为什么要打破双亲委派呢?想想Tomcat要如何加载webapps目录下的多个不同的应用?而关于类加载机制的所有有趣的玩法,也都在这个核心方法里。比如class文件加密加载,热加载等。
4. 沙箱委派机制:
双亲委派机制有一个最大的作用就是要保护JDK内部的核心类不会被应用覆盖。而为了保护JDK内部的核心类,
JAVA在双亲委派的基础上,还加了一层保险。就是ClassLoader中的下面这个方法。
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// 不允许加载核心类
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}
5. 类和对象有什么关系
四、解释执⾏与编译执⾏
Java文件通过javac编译成class文件 ,这种中间码被称为“字节码”,然后有jvm加载字节码(类加载)。运行时 解释器将字节码解释为一行行的机器码来执行,在程序运行期间,即时编译器会针对热点代码将该部分字节码编译成机器码,来获得更高的执行效率。
在整个运行时解析器和即时编译器的相互配合,使Java程序几乎能够达到和编译型语言一样的执行速度。

典型面试题;
(一) 基础类形装箱:
下面代码输出结果是什么 ,为什么会有这样的输出结果:
答案: Integer里面有规定 如果传入的(int)是在-128~127 期间 那么就会返回数组中的对象,如果在-127~128 之外 就会返回一个新的对象。
(二)Java中的静态方法可以重载嘛?
完美回答: 不能,因为在JVM中 调用方法提供了集中不同的字节码指令:
invokcvitual 是来调用对象的虚方法(也就是可重载的这些方法)
invokespecial 根据编译时类型来调用实例方法(比如静态代码 (通常对应字节码层面的init方法),比如构造方法(通常对应字节码层面的init方法));
invokestatic 调用类(静态方法);
invokeinterface 调用接口方法;
所以看得出来 静态方法和重载方法他们调用指令都不一样,那肯定无法重载静态方法的;