【 大厂必考之 JVM】01,kafka 原理和面试笔试题目
public class A {
public static void main(String[] args) {
System.out.println("abc");
}
}
命令
javac x.java
java x
更多参考:[https://blog.csdn.net/u011531425/article/details/80961628](
)
[](
)二、JVM 的体系架构
=======================================================================
JVM 体系结构如下图所示,将按照从上到下的顺序讲解
1.类装载器 classloader
负责将 class 文件的字节码内容加载到内存中并将这些内容转换成方法区中的运行时数据结构,class 文件在文件开头有特定标识(cafe babe:Java 图标——咖啡和橡树)。
通俗来讲:classloader 相当于快递员的作用,只负责加载,至于是否能运行,由 Execution Engine 决定
[](
)1.1 有几个类装载器?
准确说有四个,三个虚拟机自带的:
启动类加载器:(Bootstrap c++ )负责加载存放在 JDK\jre\lib(JDK 代表 JDK 的安装目录,下同)下,或被-Xbootclasspath 参数指定的路径中的,并且能被虚拟机识别的类库(如 rt.jar,所有的 java.*开头的类均被 Bootstrap ClassLoader 加载)。启动类加载器是无法被 Java 程序直接引用的。
扩展类加载器:(Extension?Java )它负责加载 DK\jre\lib\ext 目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如 javax.*开头的类),开发者可以直接使用扩展类加载器。
应用程序类加载器:(AppClassLoader) 它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
一个用户自定义的:Java.lang.ClassLoader
1.2?双亲委派机制
当一个类收到类加载请求,他会先把这个请求交给他的父类,只有父类无法完成这个请求时,子加载器才会尝试自己去加载。
双亲委派的好处是保护 Java 核心类,比如加载位于 rt.jar 中的 java.lang.Object,不管是哪个加载器加载的,最终都会交给启动类加载器,这样就保证了不同的类加载器得到的都是同一个 Object 对象。
代码举例:查看类是被那个加载器加载的
/**
@Author: 一条 IT
@Date: 2020/12/3 21:28
*/
public class Test {
public static void main(String[] args) {
System.out.println(Object.class.getClassLoader());
System.out.println(Test.class.getClassLoader().getParent().getParent());
System.out.println(Test.class.getClassLoader().getParent());
System.out.println(Test.class.getClassLoader());
}
}
输出
null
null
sun.misc.Launcher$ExtClassLoader@1b6d3586
sun.misc.Launcher$AppClassLoader@14dad5dc
因为 Object 是 jdk 自带的,所以在加载的时候是走 Bootstrap 启动类加载器,而 Bootstrap 加载器是 C++语言写的,所以在查的时候是 null,报了 NullPointException();Test 类自己写的,走 AppClassLoder,他的父类是扩展加载器,再父类是启动类加载器,也输出 Null
1.3.沙箱安全机制
**主要是防止恶意代码污染 java 源代码,**比如定义了一个类名为 String 所在包为 java.lang,因为这个类本来是属于 jdk 的,如果没有沙箱安全机制的话,这个类将会污染到我所有的 String,但是由于沙箱安全机制,所以就委托顶层的 bootstrap 加载器查找这个类,如果没有的话就委托 extsion,extsion 没有就到 appclassloader,但是由于 String 就是 jdk 的源代码,所以在 bootstrap 那里就加载到了,先找到先使用,所以就使用 bootstrap 里面的 String,后面的一概不能使用,这就保证了不被恶意代码污染。
2.运行时数据区
========
运行时数据区:主要包括:方法区,堆,Java 栈,PC 寄存器,本地方法栈
方法区和堆由所有线程共享;Java 栈,本地方法栈和 PC 寄存器由线程独享。
2.1java 栈
主要存储**8 种基本数据类型+对象的引用变量+实例方法。**当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。栈的大小与 JVM 的实现有关,通常在 256k-756k 之间。当栈被压满,会报 StackOverflowError 错误 ,不是异常,最简单的实现是递归死循环。
栈内存在线程创建时创建,跟随线程的生命周期,不存在垃圾回收,是私有的。
虚拟机只会直接对 Javastack 执行两种操作:以帧为单位的压栈或出栈,按照后进先出的顺序。
每个栈帧代表一个方法,Java 方法有两种返回方式,return 和抛出异常,两种方式都会导致该方法对应的帧出栈和释放内存。
栈帧的组成:
本地变量:输入参数,输出参数以及方法内的变量。
栈操作:记录出栈,入栈的操作。
栈帧数据:类文件,方法等。
2.2 本地方法栈(native)
保存 native 方法进入区域的地址,如某个 JVM 实现的本地方法不是用 Java 编写,比如某线程在调用本地方法时,就进入了一个不受 JVM 限制的领域,该方法被 native 关键字修饰。
new 线程类
Thread t=new Thread();
t.start();
进入 start()方法
public synchronized void start() {
/**
This method is not invoked for the main method thread or "system"
group threads created/set up by the VM. Any new functionality added
to this method in the future may have to also be added to the VM.
A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
so that it can be added to the group's list of threads
and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwa
ble ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
再找到 start0()方法,看到被 native 修饰,说明 java 已经无能为力。
private native void start0();
2.3 程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,存储指向下一条指令的地址,它的作用可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。?由于 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Natvie 方法,这个计数器值则为 Undefined。此内存区域是唯一一个在 Java 虚拟机不会发生内存溢出错误的区域。
简单来说,相当于班级内的之日排班表。
2.4 方法区
**存储每一个类的结构信息,**例如运行时常量池,字段和方法数据,构造函数和普通方法的字节码内容。简单来说就时类的实例化模板。
例如王者荣耀的每个英雄出生时都有生命,攻击力等数值,都有走路,攻击等方法。
2.5 堆
Java 堆(Java Heap)是 Java 虚拟机所管理的内存中最大的一块,大小可调节。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以 Java 堆中还可以细分为:新生代和老年代;再细致一点的有 Eden 空间、From Survivor 空间、To Survivor 空间等。根据 Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx 和-Xms 控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError 异常。
评论