JVM 虚拟机 面试题

6 篇文章 0 订阅
类加载机制的步骤是什么,static和final修改的成员变量的加载时机

final是在准备阶段时就赋值了,static准备阶段时数据是零值,在初始化阶段才会赋值。
https://blog.csdn.net/zxd8080666/article/details/78087646

jps命令:查看正在运行的Java进程

jps是 java process status 的缩写,翻译过来是"Java的进程的状态"

试题

(试题)Java类加载器有哪几种?
Java类加载器按照层次,从顶层到底层分为三种:启动类加载器BootstrapClassLoader、扩展类加载器ExtensionClassLoader、应用程序类加载器ApplicationClassLoader

(试题)java代码编译执行过程
 1.源码编译:通过Java编译器将源代码编译成JVM字节码(.class文件)
 2.类加载:通过ClassLoader及其子类来完成JVM的类加载
 3.类执行:字节码被装入内存,进入JVM虚拟机,被解释器解释执行

什么时候需要对类进行初始化?

(面)JVM的内存结构

JVM由三个子系统构成:类加载器、运行时数据区、执行引擎
一、类加载器

类加载器分为3种:
1.启动类加载器 BootStrapClassLoader 是Java类加载层次中最顶层的类加载器,负责加载JAVA_HOME\jre\lib目录下JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等。该加载器不是ClassLoader的子类,由C/C++语言实现其功能。
2.ExtensionClassLoader 扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME\jre\lib\ext目下的所有jar。它是ClassLoader的子类,由Java语言实现。
3.AppClassLoader:称为应用程序类加载器,负责加载当前应用程序目录下的所有jar和class文件以及环境变量CLASSPATH指定的jar(即JAVA_HOME/lib/dt.jar和JAVA_HOME/lib/tools.jar)和第三方jar。AppClassLoader是ClassLoader的子类,由Java语言实现。
.
双亲委派模型:类加载器收到类加载请求,自己不加载,向上委托给父类加载,父类加载不了,再自己加载(除了顶层的启动类加载器外)。好处:避免Java核心API篡改;

JVM类加载分为5个过程:加载,验证,连接(准备,解析,初始化),使用,卸载
1、装载:查找和导入Class文件
2、链接:(a)检查:检查载入的class文件数据的正确性(b)准备:给类的静态变量分配存储空间(c)解析:将符号引用转成直接引用
3、初始化:对静态变量,静态代码块执行初始化工作

【二、运行时数据区】通常包括5个部分:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。

【三、执行引擎】1.解释器 2.JIT编译器 3.垃圾收集器

(面)JVM运行时数据区

程序计数器: 是当前线程程序执行的字节码所在行号指示器。
字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。

虚拟机栈 VMStack、本地方法栈
编译时已知的数据类型、对象引用类型、方法返回地址;
栈帧——局部变量表 操作栈 动态链接 方法返回地址;
栈的生命期是跟随线程的生命期,线程创建时创建,线程结束栈内存也就释放,是线程私有的。
都会有 StackOverflowError 和 OutOfMemoryError异常。
通过-Xss参数设置栈大小。
HotSpot不区分虚拟机栈和本地方法栈,作为一个整体

方法区 Metaspace 元空间
存储运行时常量池,已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据等;
亦称PermenentGeneration(永久代)
通过-XX:permSize和-XX:MaxPermSize设置该空间

堆 Heep: 存放对象实例
新生代:Yong(Eden s1 s2)老年代Old
通过-Xmx和-Xms设置堆空间大小

(面)堆内存该设置多大?OutOfMemoryError异常到底是怎么引起的?如何进行JVM调优?JVM的垃圾回收是如何?甚至创建一个String对象,JVM都做了些什么?

JVM内存模型和JMM(Java内存模型)的异同

=======================================================================================================================================================================================================================================================================

JVM

1 什么是JVM,JVM优点有哪些?
2 JVM跨平台及原理
3 JVM的组成
4 JVM的位置
5 JVM的体系结构
6 Java代码的执行流程
7 JVM的架构模型
8 JVM的生命周期

JDK的组成

JDK=JRE+JVM+其它;JDK是整个JAVA的核心,包括了Java运行环境JRE、Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。
JRE目录中有两个文件夹bin和lib,可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库。

JDK JRE JVM三者之间的关系

JDK(Java Development Kit)是Java开发工具包,JDK中包含JRE(在JDK的安装目录下有一个名为jre的目录,里面有两个文件夹bin和lib,可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib和起来就称为jre)和一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。

JRE(Java Runtime Environment)是运行基于Java语言编写的程序所不可缺少的运行环境。也是通过它,Java的开发者才得以将自己开发的程序发布到用户手中,让用户使用。JRE中包含了Java virtual machine(JVM),runtime class libraries和Java application launcher,这些是运行Java程序的必要组件。

JVM(Java virtual machine)Java虚拟机是整个java实现跨平台的最核心的部分,所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行。

JAVA环境变量
PATH与CLASSPATH的区别?

PATH是你要使用编译器的命令时,去寻找的路径;path指示java命令的bin路径,像javac、java、javaw等。
CLASSPATH是你要编译时,对编译文件的操作时找被编译文件的路径。
classpath是javac编译器的一个环境变量,它的作用与import、package关键字有关

JVM

什么是JVM?JVM优点?

JVM(Java Virtual Machine)是Java虚拟机的缩写。简单来说JVM是用来解析和运行Java程序的。
跨平台:由Java编写的程序可以在不同的操作系统上运行:一次编写,多处运行。
原理:编译之后的字节码文件和平台无关

JVM是java实现跨平台的核心的部分,所有的java程序首先被编译为.class的类文件,该文件可以在虚拟机上执行。class并不直接与机器的操作系统相对应,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。
.
只有JVM还不能成class的执行,因为在解释class的时候JVM需要调用解释所需要的类库lib,而jre包含lib类库。JVM屏蔽了与操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多平台上运行。
.
Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多平台上运行。虚拟机之所以存在的原因–屏蔽底层不同操作系统并且减少基于原生语言开发的复杂性,使java这门语言能够跨各种平台。

JVM的位置

JVM就是运行在操作系统之上的一个软件。JVM位于操作系统上层,但是位于应用程序下层。
(应用程序 > JVM > 操作系统 > 硬件体系)

JVM体系结构

JVM的组成
JVM的架构分为三个主要子系统:类加载器子系统(加载文件)、运行时内存区(装入内存)、执行引擎(解释执行)

一 类加载器子系统

在这里插入图片描述
类的加载方式:隐式加载-关键字new;显示加载:LoadClass,Class.forName()等

1 类加载器 Class Loader
类加载器种类

1、Java类加载器按照层次,从顶层到底层,分为以下三种:
(1)启动类加载器(BootstrapClassLoader)是最顶层的类加载器,负责加载JAVA_HOME\jre\lib目录下JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等。启动类加载器无法被Java程序直接引用,该加载器不是ClassLoader的子类,由C/C++语言实现其功能。
(2)扩展类加载器(ExtensionClassLoader)负责加载JAVA_HOME/lib/ext目录中的类库,它是ClassLoader的子类,由Java语言实现,开发者可以直接使用扩展类加载器。
(3)应用程序类加载器(ApplicationClassLoader)一般称系统类加载器。它是程序中默认的类加载器。负责加载当前应用程序目录下的所有jar和class文件以及环境变量CLASSPATH指定的jar(即JAVA_HOME/lib/dt.jar和JAVA_HOME/lib/tools.jar)和第三方jar。AppClassLoader是ClassLoader的子类,由Java语言实现。

类加载机制*

类的生命周期分为5个过程:加载,验证,连接(准备,解析,初始化),使用,卸载
在这里插入图片描述

1)加载

加载主要是将.class文件中的二进制字节流读入到JVM中。在加载阶段,JVM需要完成3件事:
1)通过类的全限定名获取该类的二进制字节流;
2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

2)连接

1)验证,字节码验证器将验证生成的字节码是否正确,如果验证失败,我们将得到验证错误。
2)准备,内存将为所有静态变量分配默认值。
3)解析,所有符号内存引用将被来自方法区域的原始引用所替换。

验证内容:
<1>解析和与填充符号表的过程
a. 词法,语法分析
b.符号表
<2>插入式注解处理器的注解处理过程
a.注解处理器
<3>语义分析以及字节码的生成
1.标注检查
2.数据及控制流分析
3.解语法糖

3)初始化

这是类加载的最后阶段;在这里,所有静态变量都将被赋初始值,并且静态块也会被执行。
https://my.oschina.net/u/1458864/blog/2004785

二 运行时数据区(JVM内存结构)

《Java虚拟机规范》中的规定,运行时数据区(RunTime Data Area)通常包括5个部分:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。
在这里插入图片描述

1)程序计数器

内存空间小,线程私有。它是唯一没有OutOfMemoryError异常的区域。
程序计数器的作用可以看做是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变计数器的值来选取下一条字节码指令。其中,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器来完成。

2)Java虚拟机栈

栈的生命期是跟随线程的生命期,线程创建时创建,线程结束栈内存也就释放,是线程私有的。
Java虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

栈有一个很重要的特殊性,就是存在栈中的数据可以共享。

3)本地方法栈

本地方法栈则为虚拟机使用到的 Native 方法服务。也会抛出 StackOverflowError 和 OutOfMemoryError 异常。
区别在于虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈是为虚拟机使用到的Native方法服务。

4)方法区

方法区属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
JDK8之前方法区的实现是被称为一种“永久代”的区域,这部分区域使用JVM内存,但是JDK8的时候便移除了“永久代(Per Gen)”,转而使用“元空间(MetaSpace)”的实现,而且很大的不同就是元空间不在共用JVM内存,而是使用的系统内存。
在JDK1.8中把存放元数据中的永久内存从堆内存中移到了本地内存中

方法区的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是回收确实是有必要的。

5)堆

堆内存是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。

堆是由垃圾回收器来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,java的垃圾回收器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

问题:什么是运行时常量池?
简单的来讲class文件在编译后除了存储一些类的版本、字段、方法、接口等元数据信息外,还有一部分信息是常量池,这个常量池我们称之为“静态常量池”,只是作为一种持久化数据存储在硬盘上,代表编译期生成的各种字面量和符号引用(最常见的就是字符串常量),那么这类信息被加载到内存中就会以运行时常量池的形式存在内存中,JDK7/JDK8都已经移到了堆区。这类数据变量的好处简单来说就是如果堆区中已经存在一个数据变量,即使再创建一个这样的变量,那么JVM将会直接指向已经创建好的数据,而不会再分配内存区域,这样一方面加快数据的创建,另一方面节省内存空间!但是实际上的机制要复杂一些!

三 执行引擎

被分配给运行时数据区的字节码将由执行引擎执行。执行引擎读取字节码并逐个执行。

解释执行也包括两种情况: 1,源码解释执行 2,字节码解释执行 解释和编译执行的区别是:是否产生中间本地机器码。
即时编译生成机器相关的中间码,可重复执行缓存效率高。解释执行直接执行字节码,重复执行需要重复解释。

1.解释器

解释器更快地解释字节码,但执行速度很慢。解释器的缺点是,当一个方法被多次调用时,每次都需要一个新的解释。

2.JIT编译器

JIT编译整个字节码并将其更改为本机代码。此本机代码将直接用于重复的方法调用,从而提高系统的性能。

3.垃圾收集器

垃圾收集器收集和删除未引用的对象。可以通过调用 System.gc()触发垃圾收集,但不能保证执行。JVM的垃圾收集收集创建的对象。

Java代码编译和执行过程

Java代码的编译是由Java源码编译器来完成,流程图如下所示:
在这里插入图片描述

HelloWorld的具体执行过程

(面)简述Java源文件编译与执行过程

编译过程:
1.先通过编译器【编译】,找到主类HelloWorld里面有多少个被public修饰的类,就编译成多少个.class字节码文件。
2.然后虚拟机把HelloWorld.class作为初始类装载到内存,进行解析并初始化。虚拟机中的类加载器加载系统中的class文件,包括jdk中的基础类(如String Object等)。
3.最后由虚拟机【解释】含有主函数的.class文件,获得main方法程序的入口。把字节码指令翻译成本机cpu能够识别的指令,最后【执行】该程序。

HelloWorld的具体执行过程:
1.虚拟机执行HelloWorld.java文件,将源文件生成HelloWorld.class字节码文件;

2.虚拟机将HelloWorld.class类文件加载到内存中(即方法区的类代码区中);

3.虚拟机通过类找到HelloWorld的主方法(程序的入口方法),访问权限为public(公有可用),虚拟机传递String[](字符串数组对象:空数组)类型参数的地址到主方法的args中去,并在栈区为args开辟内存空间,返回一个void的返回值;
4.定义一个String(标准类库中的)类型的变量(在栈区开辟空间)s,s的值暂未确定;
5.s = “Hello World!”,对象“Hello World!”在方法区的常量数据区开辟空间,属性为:Hello World!,方法即为:toString(),变量s存放对象“Hello World!”的地址;
6.虚拟机找到标准类库中的System.class类并加载到内存中(即方法区的类代码区中),System.out为标准字节输出流对象(),并调用println()方法将变量s的值打印到屏幕上。

PS: 虚拟机调用主方法时会创建三个默认对象:System.out(标准字节输出流对象)、System.in(标准字节输入流对象)和System.error(标准字节出错流对象).

以上共涉及:
1个java文件:HelloWorld.java
4个class类:HelloWorld.class、String[].class、String.class、System.class
5个对象:“Hello World!”、String[]、System.out、System.in、System.error
2个变量:args、s
3个方法:main()、toString()、println()

HelloWorld解析

用 javac 将源文件进行编译为 HelloWorld.class 字节码文件。 使用“Java Hello World”命令启动Java虚拟机运行程序,Java虚拟机首先将编译好的字节码文件由类加载器加载到内存区,然后虚拟机对加载到内存中的Java类进行解释执行。

1、用 javac 把源文件进行编译为 HelloWorld.class 字节码文件。
2、 类的加载阶段,加载至JVM内存中,Main方法就是程序的入口,然后由JVM虚拟机来解释执行,在一些虚拟机的实现中,还会被JVM利用字节码解释器特定系统的机器码执行,从而提高执行效率。
3、虚拟机通过类找到HelloWorld的主方法(程序的入口方法),访问权限为public(公有可用),虚拟机传递String[](字符串数组对象:空数组)类型参数的地址到主方法的args中去,并在栈区为args开辟内存空间,返回一个void的返回值;
4、定义一个String(标准类库中的)类型的变量(在栈区开辟空间)s,s的值不确定(垃圾值,编译无法通过);
5、s = “Hello World!”,对象“Hello World!”在方法区的常量数据区开辟空间,属性即为:Hello World!,方法即为:toString(),变量s存放对象“Hello World!”的地址;
6、虚拟机找到标准类库中的System.class类并加载到内存中(即方法区的类代码区中),System.out为标准字节输出流对象(),并调用println()方法将变量s的值打印到屏幕上。

双亲委派模型

类加载器之间的这种层次关系叫做双亲委派模型。

双亲委派机制是什么?有什么好处?
类加载器收到类加载请求,自己不加载,向上委托给父类加载,父类加载不了,再自己加载。
好处:避免Java核心API篡改;

2、 类加载的双亲委派模型
双亲委派模型要求除了顶层的启动类加载器外,其他的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承关系来实现,而是都使用组合关系来复用父加载器的代码;

工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,
而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,
因此所有的加载请求最终都应该传递到顶层的启动类加载器中,
只有当父类加载器反馈自己无法完成这个请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

好处:
Java类随着它的类加载器一起具备了一种带有优先级的层次关系。
例如类Object,它放在rt.jar中,无论哪一个类加载器要加载这个类,
最终都是委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
判断两个类是否相同是通过classloader.class这种方式进行的,所以哪怕是同一个class文件如果被两个classloader加载,那么他们也是不同的类。

实现自己的加载器:
只需要继承ClassLoader,并覆盖findClass方法。
在调用loadClass方法时,会先根据双亲委派模型在父加载器中加载,如果加载失败,则会调用自己的findClass方法来完成加载。

双亲委派模型

类加载器收到类加载请求,自己不加载,向上委托给父类加载,父类加载不了,再自己加载(除了顶层的启动类加载器外)。

Java垃圾回收机制

自动垃圾回收是一种在堆内存中找出哪些对象在被使用,还有哪些对象没被使用,并且将后者删掉的机制。
所谓使用中的对象,指的是程序中有指针指向的对象;而未使用中的对象,则没有被任何指针给指向,因此占用的内存也可以被回收掉。

1、标记,垃圾回收的第一步是标记。垃圾回收器此时会找出哪些内存在使用中,还有哪些不是。
垃圾回收器要检查完所有的对象,才能知道哪些有被引用,哪些没。如果系统里所有的对象都要检查,那这一步可能会相当耗时间。
2、清除,删掉标记出的未引用对象。内存分配器会保留指向可用内存的引用,以供分配新对象。
3、压缩,为了提升性能,删除了未引用对象后,还可以将剩下的已引用对象放在一起(压缩),这样就能更简单快捷地分配新对象了。
为什么需要分代垃圾收集?
之前说过,逐一标记和压缩 Java 虚拟机里的所有对象非常低效:分配的对象越多,垃圾回收需时就越久。不过,根据统计,大部分的对象,其实用没多久就不用了。
存活(没被释放)的对象随运行时间越来越少。而图中左侧的那些峰值,也表明了大部分对象其实都挺短命的。

JVM 分代
根据之前的规律,就可以用来提升 JVM 的效率了。方法是把堆分成几个部分(就是所谓的分代),分别是新生代、老年代,以及永生代。

新对象会被分配在新生代内存。一旦新生代内存满了,就会开始对死掉的对象,进行所谓的小型垃圾回收过程。一片新生代内存里,死掉的越多,回收过程就越快;至于那些还活着的对象,此时就会老化,并最终老到进入老年代内存。

老年代用来保存长时间存活的对象。通常,设置一个阈值,当达到该年龄时,年轻代对象会被移动到老年代。最终老年代也会被回收。这个事件成为 Major GC。

Major GC 也会触发STW(Stop the World)。通常,Major GC会慢很多,因为它涉及到所有存活对象。所以,对于响应性的应用程序,应该尽量避免Major GC。还要注意,Major GC的STW的时长受年老代垃圾回收器类型的影响。

永久代包含JVM用于描述应用程序中类和方法的元数据。永久代是由JVM在运行时根据应用程序使用的类来填充的。此外,Java SE类库和方法也存储在这里。

如果JVM发现某些类不再需要,并且其他类可能需要空间,则这些类可能会被回收。

JVM中一次完整的GC流程

JVM内存结构的布局和相应的控制参数
在这里插入图片描述

end

参考资料

什么是Java虚拟机
https://www.cnblogs.com/yixianyixian/p/7680321.html

JVM内存结构详解
https://blog.csdn.net/wo541075754/article/details/102623406

JVM面试题
https://www.jianshu.com/p/3d93cf3335f7

JVM类加载机制面试题
https://my.oschina.net/u/1458864/blog/2004785

Java内存模型
https://mp.weixin.qq.com/s/vqgswbDv_ZsZ0QqAahHEUQ

我们new一个对象的时候,都发生了什么?
https://www.sohu.com/a/289618535_753508





《Java虚拟机规范》中的规定,运行时数据区通常包括5个部分:

程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。

1.程序计数器(Program Counter Register)
内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。

2.Java虚拟机栈(VM Stack)
线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

3.本地方法栈(Native Method Stack)
本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。

4.方法区(Method Area)
方法区属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

5.Java 堆(Heap)
对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。

6.(加)运行时常量池

7.直接内存

JVM基础类

java应用环境中不同的class分别由不同的ClassLoader负责加载。

JVM中默认的ClassLoader有三种,BootstrapClassLoader、ExtensionClassLoader、App ClassLoader,分别各司其职:BootstrapClassLoader负责加载java基础类,主要是 %JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等。
ExtensionClassLoader负责加载java扩展类,主要是 %JRE_HOME/lib/ext 目录下的jar和class AppClassLoader 负责加载当前java应用的classpath中的所有类。

其中Bootstrap ClassLoader是JVM级别的,由C++撰写;Extension ClassLoader、App ClassLoader都是java类,都继承自URLClassLoader超类。
BootstrapClassLoader由JVM启动,然后初始化sun.misc.Launcher,sun.misc.Launcher初始化ExtensionClassLoader、AppClassLoader

JVM由三个子系统构成:类加载器、运行时数据区、执行引擎。

类加载器
1.启动类加载器 BootStrapClassLoader 是Java类加载层次中最顶层的类加载器,负责加载JAVA_HOME\jre\lib目录下JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等。该加载器不是ClassLoader的子类,由C/C++语言实现其功能。

2.ExtensionClassLoader 扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME\jre\lib\ext目下的所有jar。它是ClassLoader的子类,由Java语言实现。

3.AppClassLoader:称为应用程序类加载器,负责加载当前应用程序目录下的所有jar和class文件以及环境变量CLASSPATH指定的jar(即JAVA_HOME/lib/dt.jar和JAVA_HOME/lib/tools.jar)和第三方jar。AppClassLoader是ClassLoader的子类,由Java语言实现。

运行时数据区
运行时数据区,通常包括5个部分:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。

1.程序计数器:内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。

2.Java虚拟机栈:栈的生命期是跟随线程的生命期,线程创建时创建,线程结束栈内存也就释放,是线程私有的。

3.本地方法栈:本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。

4.方法区:属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
5.Java 堆:这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。

【执行引擎】
1.解释器 2.JIT编译器 3.垃圾收集器
内存空间分几部分:代码段、数据段,栈,堆

栈:只保存基础数据类型的对象和自定义对象的引用
堆:用来保存对象的实例(new 创建的对象和数组)。堆内存由程序员控制。
静态区(方法区):包含所有的class和static变量(全局变量和静态变量)。被所有的线程共享。
介绍JVM中7个区域,然后把每个区域可能造成内存的溢出的情况说明
https://www.jianshu.com/p/0e208d2c002c

JVM内存模型和JMM(Java内存模型)

1、JVM内存模型和JMM(Java内存模型)不是一回事,JMM来源于JSR-133:memory_model-1_0-pfd-spec.pdf.

JMM的目的是为了解决Java多线程对共享数据的读写一致性问题,通过Happens-Before语义定义了Java程序对数据的访问规则,修正之前由于读写冲突导致的Cache数据不一致的问题。具体到Hotspot VM的实现,主要是由OrderAccess类定义的一些列的读写屏障来实现JMM的语义。
JVM内存模型则是指JVM的内存分区。

2、Java内存模型和操作系统内存模型的关系
3、JVM的内存结构
4、对于JMM和JVM本身的内存模型,这两者本身没有关系。如果一定要勉强对应,则从变量,主内存,工作内存的定义来看,主内存主要是对应于Java堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。从更低层次上说,主内存就是物理内存,而为了获取更好的执行速度,虚拟机(甚至是硬件系统本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存中,因为运行时主要访问–读写的是工作内存。

原文链接:https://blog.csdn.net/libafei/article/details/80385208

其他

什么是Java虚拟机?

Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。虚拟机之所以存在的原因–屏蔽底层操作系统平台的不同并且减少基于原生语言开发的复杂性,使java这门语言能够跨各种平台。

什么是Java虚拟机?https://www.cnblogs.com/yixianyixian/p/7680321.

Java内存模型 https://mp.weixin.qq.com/s/vqgswbDv_ZsZ0QqAahHEUQ

解释内存中的栈、堆和静态存储区的用法

栈 只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
堆 用来保存对象的实例(new 创建的对象和数组)
方法区 包含所有的class和static变量。

Java内存溢出的十个场景

1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.
2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
3. 堆:存放所有new出来的对象。
4. 静态域:存放静态成员(static定义的)
5. 常量池:存放字符串常量和基本类型常量(public static final)。
6. 非RAM存储:硬盘等永久存储空间

栈溢出的原因与排查方法

1.是否App中的类中和引用变量过多使用了Static修饰 如public staitc Student s。
2.是否多重嵌套循环、死循环。
3.是否使用了向数据库查询所有记录的方法。
4.检查是否有数组,List,Map中存放的是对象的引用而不是对象,因为这些引用会让对应的对象不能被释放。会大量存储在内存中。
5.检查是否使用了“非字面量字符串进行+”的操作。因为String类的内容是不可变的,每次运行"+"就会产生新的对象,如果过多会造成新String对象过多,从而导致JVM没有及时回收而出现内存溢出。

Java 垃圾回收机制

Java 垃圾回收机制https://blog.csdn.net/zl1zl2zl3/article/details/90904088

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值