分享下我整理的JVM八股笔记
0.常问问题
1.new一个对象到销毁的过程/java对象创建过程?
在Java中,从创建(new)一个对象到销毁的过程大致可以分为以下几个步骤:
- 检查类是否加载:对象的实际创建是通过new关键字实现的。当JVM遇到new关键字时,会检查类是否已经加载到运行时数据区的方法区,如果没有,则先进行类的加载和初始化。
- 分配内存空间:当使用new关键字创建一个对象时,JVM会在运行时数据区的堆中为该对象分配相应的内存空间。
- 初始化对象:JVM会调用相应的构造方法来初始化对象,给对象的成员变量赋予初始值。
- 使用对象:在程序运行过程中,可以通过对象引用来访问和操作对象的成员变量和方法。
- 对象不再被引用:当对象不再被任何变量或数据结构引用时,它就成为垃圾对象,等待垃圾回收器进行回收。
- 垃圾回收:JVM的垃圾回收器会定期检查堆内存中的垃圾对象,并自动回收它们所占用的内存空间。垃圾回收的具体时机和策略取决于JVM的垃圾回收算法。
- 内存空间释放:当垃圾对象被回收后,它们占用的内存空间会被释放,以便重新分配给新创建的对象。
需要注意的是,JVM的垃圾回收机制会自动处理对象的销毁和内存空间的释放,程序员无需手动进行这些操作。但是,为了提高程序性能和避免内存泄漏,程序员应该养成良好的编程习惯,及时释放不再使用的对象引用。
2.java程序运行流程是什么?(编译+运行)
运行流程:
(-1) 编写java代码,文件后缀名为.java
(0)通过java编译器(如javac)将java源代码编译成.class字节码文件
(1)类加载器(ClassLoader)将 class 字节码文件加载到内存中(运行时数据区),但是字节码文件是JVM定义的一套指令集规范,并不能直接交给底层操作系统去执行
(2)特定的命令解释器(执行引擎)将class字节码翻译成特定的操作系统指令集交给 CPU 去执行
(3)此时可能需要调用其他语言的本地库接口(Native Method Library)来实现整个程序的功能
3.类加载执行过程
Class 字节码文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢?
类加载的全过程,即加载、验证、准备、解析和初始化这五个阶段。
- 加载:查找和导入Class字节码文件通过一个类的全限定名来获取定义此类的二进制字节流。将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
- 验证:保证加载类的准确性这一阶段的目的是确保Class文件的字节流包含的信息符合《Java虚拟机规范》的所有约束要求,从而保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
- 准备:为类变量(即静态变量)分配内存并设置类变量初始值需要注意的是,这时候进行内存分配的仅包括类变量,而不包括实例变量
- 解析:把类中的符号引用转换为直接引用解析阶段主要是将常量池内的符号引用转换为直接引用。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载。符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式 的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目 标并不一定是已经加载到虚拟机内存当中的内容。各种虚拟机实现的内存布局可以各不相同,但是它们 能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在《Java虚拟机规范》的Class 文件格式中。直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接 定位到目标的句柄。直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实 例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在虚拟机的内存中 存在。
- 初始化:执行类的构造器方法初始化阶段是执行类的构造器方法的过程。这个方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并而来的。若该类具有父类,JVM会保证父类的先执行,然后才执行子类的。
4.说下java的编译和运行2阶段
在编译阶段中,Java源代码通过前端编译器转换成字节码文件,即.class文件。而在运行阶段,这些字节码文件会被Java虚拟机(JVM)加载并执行。
编译时类型和运行时类型
- 编译时类型(Compile-time Type)
编译时类型是指在编译阶段确定的对象类型。这是由代码中的声明决定的,例如变量声明、方法参数或返回类型的声明。编译器使用这些类型来执行类型检查,确保代码符合Java语言的语法规则和类型系统规则。如果代码违反了这些规则,编译器将生成错误,并且程序无法编译成功。
- 运行时类型(Run-time Type)
运行时类型是指对象在程序实际执行时的实际类型。在运行时,对象的真实类型可能与编译时类型不同,尤其是在使用继承和多态的情况下。例如,如果你有一个父类类型的引用指向一个子类实例,那么该引用的编译时类型是父类,但其运行时类型是子类。
Animal animal = new Dog(); // 或者 new Cat()
在这个例子中:
animal
的编译时类型是Animal
,因为这就是它在代码中被声明的类型。animal
的运行时类型可能是Dog
或者Cat
,具体取决于创建时传入的是哪个子类的实例。
1.JVM组成
1.JVM是什么(实现java跨平台)
JVM(Java虚拟机)是Java跨平台的关键。在程序运行前,Java源代码(.java)需要经过编译器编译成字节码(.class)。在程序运行时,JVM负责将字节码翻译成特定平台下的机器码并运行,也就是说,只要在不同的平台上安装对应的JVM,就可以运行字节码文件。同一份Java源代码在不同的平台上运行,它不需要做任何的改变,并且只需要编译一次。而编译好的字节码,是通过JVM这个中间的“桥梁”实现跨平台的,JVM是与平台相关的软件,它能将统一的字节码翻译成该平台的机器码
好处:
- 一次编写,到处运行
- 自动内存管理,垃圾回收机制
JVM怎么实现一次编写,到处运行?
JVM(Java虚拟机)通过实现一次编写,到处运行的机制,使得Java程序可以在不同平台上运行。具体实现方式如下:
- Java源代码:首先,将Java源代码编译成字节码文件(.class文件)。字节码是一种中间代码,介于源代码和机器码之间,具有平台无关性。
- 字节码文件:字节码文件可以在任何安装了JVM的平台上运行。JVM负责将字节码文件解释执行或者即时编译成本地机器码。
- JVM:JVM是Java程序的运行环境,它负责加载字节码文件、解释执行字节码或者将字节码即时编译成本地机器码。不同的操作系统和硬件平台上有不同的JVM实现,如Windows、Linux、macOS等。
- 跨平台支持:由于JVM的存在,Java程序可以在不同的操作系统和硬件平台上运行,实现了一次编写,到处运行的目标。
什么是Class字节码文件?
Class字节码文件是被Java编译器编译后生成的二进制文件,包含了Java程序运行所需的全部信息。
Java字节码是Java编译器和JVM(Java虚拟机)之间的桥梁。在程序员编写完Java源代码后,通过编译器将.java文件编译成.class文件,也就是Class字节码文件。这个过程中,源代码中的语句被转换成了一连串的平台无关的指令集,这些指令集可以被符合Java虚拟机规范的任何系统执行。
2.JVM由哪些部分组成
- ClassLoader(类加载器):负责加载字节码文件(即 class 文件)到运行时数据区,class 文件在文件开头有特定的文件标示,并且ClassLoader 只负责class 文件的加载,至于它是否可以运行,则由 Execution Engine 决定。
- Runtime Data Area(运行时数据区,即java内存):是存放java内存相关数据的,分为五部分:Stack(虚拟机栈),Heap(堆),MethodArea(方法区),PC Register(程序计数器),Native Method Stack(本地方法栈)。几乎所有的关于 Java 内存方面的问题,都是集中在这块。
- Execution Engine(执行引擎):Class 文件被加载后,会把指令和数据信息放入内存中,Execution Engine 则负责把这些命令解释给操作系统,即将 JVM 指令集翻译为操作系统指令集。
- Native Method Library(本地库接口):负责调用本地接口的。他的作用是调用不同语言的本地接口给 JAVA 用
2.运行时数据区(JVM内存)
-1.JVM内存模型
JVM(Java虚拟机)内存模型是Java程序在运行时的内存管理机制,它定义了程序运行时的数据存储结构。JVM内存模型主要可以分为以下几个部分:
- 程序计数器(Program Counter Register):程序计数器是一块较小的内存空间,它的作用可以看作是指向下一条指令的地址,即即将要执行的指令代码。每个线程都有一个独立的程序计数器,这是因为JVM允许多线程并发执行,而每个线程需要知道自己下一条要执行的指令在哪里。
- Java虚拟机栈(Java Virtual Machine Stacks):每个线程在创建时都会创建一个虚拟机栈,栈中保存着一个个栈帧(Frame),每个方法调用都会产生一个对应的栈帧,栈帧中包含局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。虚拟机栈是线程私有的,生命周期与线程相同。
- 本地方法栈(Native Method Stacks):与虚拟机栈类似,本地方法栈服务于Native方法的执行,这些方法通常是用C或C++编写的。在某些JVM实现中,本地方法栈和Java虚拟机栈是同一个栈。
- 堆(Heap):堆是JVM管理的最大一块内存区域,是所有线程共享的内存区域,用于存放对象实例。几乎所有的对象实例以及数组都要在堆上分配。堆是垃圾收集器管理的主要区域,因此也被称作GC堆。从内存回收的角度,由于现代垃圾收集器采用分代收集算法,所以堆还可以细分为新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
- 方法区(Method Area):方法区也是所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在JDK 1.8之前的版本中,方法区也被称为“永久代”(Permanent Generation),但在JDK 1.8中,永久代已经被移除,取而代之的是元空间(Metaspace),元空间使用的是本地内存。
- 运行时常量池(Runtime Constant Pool):运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
以上就是JVM内存模型的基本组成部分。了解这些基础知识有助于更好地理解和优化Java应用程序的性能。
0.运行时数据区包含了哪几个部分?
运行时数据区包含了堆、方法区、虚拟机栈、本地方法栈、程序计数器这几部分,每个功能作用不一样
- 堆:Java堆是线程共享的区域,主要用于存储new出来的对象实例(包括Class对象:每个类在加载到JVM的方法区时都会产生一个相应的Class对象)。在这里分配对象实例的内存空间,它是垃圾收集器管理的主要区域,通过-Xmx和-Xms参数可以调整堆的大小。堆内存的合理分配和释放对于Java程序的性能至关重要。堆是Java虚拟机所管理的内存中最大的一块,用于存放所有类实例和数组对象。Java堆可以细分为新生代(Young Generation)和老年代(Old Generation),其中新生代又可以进一步细分为Eden空间和两个Survivor空间(S0, S1)。空间大小比eden:survivor:survivor=8:1:1对象的创建几乎都在堆上进行,而垃圾回收也主要是在堆上进行。
- 方法区:方法区同样是线程共享的区域,用于存储已被虚拟机加载的类的元数据信息。方法区存储的信息较为持久,通常不会被频繁地创建和销毁。方法区也称为非堆区,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 虚拟机栈和本地方法栈:都是线程私有的,用于存储方法调用的相关信息(用于存储局部变量表、操作数栈、动态链接、方法出口等信息。),前者服务于Java方法,后者服务于本地方法虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
- **程序计数器(PC寄存器):**每个线程私有, 用于存储当前线程执行的字节码指令的行号。JVM工作时就是通过改变这个计数器的值来选取下一个需要执行的字节码指令。每个线程拥有一个独立的程序计数器,用于指示当前线程所执行的字节码指令的位置。如果执行的是本地(Native)方法,那么程序计数器的值为空(Undefined)。
什么是Class对象?作用是什么?
类的对象和Class对象不一样!
- Class对象包含了什么?Class对象包含了它所表示的类的元数据信息,,包括类名、超类、接口、字段、方法、构造器以及类的修饰符等
- Class对象怎么来的?放在哪?在类被加载到JVM的方法区时,每个类都会在创建一个Class对象,存储到堆里。这意味着,当你通过反射或者类的.class语法来获取某个类的Class对象时,你得到的是一个存储在堆上的实际对象实例。该Class对象保存在同名.class的文件中(即编译后得到的字节码文件)。。编译后的字节码文件保存的就是Class对象。
- java类和他的CLass对象的关系对于手动编写的每个java类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。
- Class对象有什么用呢?实例化:当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。也就是说,Class对象对于类的实例化具有非常重要的意义。没它就没法new新对象和引用静态成员变量。动态代理:Class对象在实现动态代理时起到关键作用。动态代理允许我们在运行时创建一个实现了一组接口的代理对象,这个代理对象可以在调用方法前后执行一些额外的操作。反射:Class对象是实现Java反射机制的基础。反射允许程序在运行时动态地创建对象、调用方法和访问字段,这对于编写通用代码和框架非常有用。封装:Class对象封装了类的结构信息,包括类的字段、方法、构造器等。通过Class对象,我们可以访问这些信息,从而了解和使用类的属性和方法。
1.什么是程序计数器(线程私有)?有什么用?
**程序计数器(PC寄存器):**每个线程私有, 用于存储当前线程执行的字节码指令的行号。
作用:
**java虚拟机对于多线程是通过线程轮流切换并且分配线程执行时间。**在任何的一个时间点上,一个处理器只会处理执行一个线程,如果当前被执行的这个线程它所分配的执行时间用完了【挂起】。处理器会切换到另外的一个线程上来进行执行。并且这个线程的执行时间用完了,接着处理器就会又来执行被挂起的这个线程。
那么现在有一个问题就是,当前处理器如何能够知道,对于这个被挂起的线程,它上一次执行到了哪里?那么这时就需要从程序计数器中来回去到当前的这个线程他上一次执行的行号,然后接着继续向下执行。
程序计数器是JVM规范中唯一一个没有规定出现OOM的区域,所以这个空间也不会进行GC。
2.Java堆(线程共享)
0.什么是java堆?
堆:Java堆是线程共享的区域,主要用于存储new出来的对象实例(包括Class对象:每个类在加载到JVM的方法区时都会产生一个相应的Class对象)。在这里分配对象实例的内存空间,它是垃圾收集器管理的主要区域,通过-Xmx和-Xms参数可以调整堆的大小。堆内存的合理分配和释放对于Java程序的性能至关重要。
- 堆是Java虚拟机所管理的内存中最大的一块,用于存放所有类实例和数组对象。
- Java堆可以细分为新生代(Young Generation)和老年代(Old Generation),方法区,其中新生代又可以进一步细分为Eden空间和两个Survivor空间(S0, S1)。
- 对象的创建几乎都在堆上进行,而垃圾回收也主要是在堆上进行。
在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:
- 新生代
- 老生代
- 永久代
JDK 8 版本之后 永久代已被 Metaspace(元空间) 取代,元空间使用的是本地内存。
1.讲一下新生代、老年代、永久代的区别?
- 新生代主要用来存放新生的对象。新生代又被进一步划分为 Eden区 和 Survivor区,Survivor 区由 From Survivor 和 To Survivor 组成。新生代通常采用年轻代垃圾回收算法,如复制算法,能够高效地回收生命周期短的对象这里主要存放新创建的对象,以及那些经过几次垃圾回收后仍然存活的对象。年轻代的垃圾回收频率较高,因为大部分对象在这里很快就被回收。新生代内又分三个区:一个Eden区,两个Survivor区(S0、S1,又称From Survivor、To Survivor),大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足晋升到老年代条件的对象将被复制到另外一个Survivor区。对象每经历一次复制,年龄加1,达到晋升年龄阈值后,转移到老年代
- 老年代主要存放应用中生命周期长的内存对象。老年代通常采用标记-清除或标记-整理算法,适合回收生命周期较长的对象。经过多次年轻代垃圾回收后仍然存活的对象会被提升到老年代。老年代的垃圾回收频率较低,但每次回收可能需要更长时间。
- 永久代指的是永久保存区域。主要存放类的元数据,包括类的定义信息、运行时常量池、字段和方法的数据等。。在Java8中,永久代已经被移除,取而代之的是一个称之为“元数据区”(元空间)的区域。元空间和永久代类似,不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存的限制。
为什么要设置两个Survivor区域?
设置两个 Survivor 区最大的好处就是解决内存碎片化。我们先假设一下,Survivor 只有一个区域会怎样。Minor GC 执行后,Eden 区被清空了,存活的对象放到了 Survivor 区,而之前 Survivor 区中的对象,可能也有一些是需要被清除的。问题来了,这时候我们怎么清除它们?在这种场景下,我们只能标记清除,而我们知道标记清除最大的问题就是内存碎片,在新生代这种经常会消亡的区域,采用标记清除必然会让内存产生严重的碎片化。因为 Survivor有 2 个区域,所以每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次Minor GC 时,From 与 To 职责兑换,这时候会将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。这种机制最大的好处就是,整个过程中,永远有一个 Survivor space 是空的,另一个非空的 Survivorspace 是无碎片的。那么,Survivor 为什么不分更多块呢?比方说分成三个、四个、五个?显然,如果Survivor 区再细分下去,每一块的空间就会比较小,容易导致 Survivor 区满,两块 Survivor 区可能是经过权衡之后的最佳方案。5.25 说一说你对GC算法的了解。参考答案标记-清除算法:最早出现也是最基础的垃圾收集算法是“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。它的主要缺点有两个:第一个是执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;第二个是内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。标记-清除算法的执行过程如下图所示。
2.Java对象如何从新生代转移到老年代?
Java对象从新生代晋升到老年代的四种方式是:
- 年龄晋升:每个对象都有一个年龄计数器,当对象经过一次Minor GC后仍然存活,则该对象的年龄加一。一旦对象的年龄达到预设的阈值(默认通常是15),它就会被移动到老年代。 虚拟机给每个对象定义了一个对象年龄(Age)计数器,存储在对象头中。对象通常在Eden区里诞生,如果经过第一次MinorGC(当 Eden 区空间不够时,发起 Minor GC。)后仍然存活,并且能被Survivor容纳的话,该对象会被移动到Survivor FROM空间中,并且将其对象年龄设为1岁。每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次Minor GC 时,From 与 To 职责对换**对象在Survivor区中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。
- 大对象直接进入老年代:如果一个对象的大小超过新生代的一半,那么它会被直接分配到老年代。
- 永久代对象晋升到老年代:永久代中的对象在垃圾回收时可能会被晋升到老年代。
- 动态对象年龄判定:如果Survivor区中某个年龄段的对象总大小超过了Survivor区的一半(可通过
-XX:TargetSurvivorRatio
调整比例),那么年龄大于或等于该年龄段的所有对象下次想进入survivor区都将直接晋升至老年代。 - survivor区空间不够:如果在进行了一次Minor GC后,Survivor区不足以容纳上一次GC后存活下来的对象,那么这些对象也会被移动到老年代。
3.为什么要分新生代和永久代?
将堆内存分为新生代和老年代的主要目的是为了优化垃圾回收的效率和减少应用程序的停顿时间,从而提高系统的性能和稳定性。
以下是一些原因为什么要分新生代和老年代:
- 对象生命周期不同:大部分对象的生命周期很短,它们很快就会变成垃圾。将这些短命对象分配到新生代,可以利用新生代的垃圾回收算法(如复制算法)来快速回收这些对象,减少垃圾回收的开销。
- 避免全堆垃圾回收:将堆内存分为新生代和老年代可以避免Full GC全堆垃圾回收,这样可以减少应用程序的停顿时间。只需要在新生代进行频繁的垃圾回收,而老年代的垃圾回收发生相对较少,可以减少应用程序的停顿时间。
- 优化垃圾回收算法:新生代通常采用年轻代垃圾回收算法,如复制算法,能够高效地回收生命周期短的对象;而老年代通常采用标记-清除或标记-整理算法,适合回收生命周期较长的对象。
- 提高内存利用效率:通过将堆内存分为不同区域,可以根据不同区域的特点采用不同的垃圾回收策略和参数设置,以达到更好的性能和内存利用效率。
因此,将堆内存分为新生代和老年代有利于提高内存管理的效率和性能,减少应用程序的停顿时间,提高系统的吞吐量。
3. 什么是虚拟机栈和本地方法栈
虚拟机栈和本地方法栈:都是线程私有的,用于存储方法调用的相关信息(用于存储局部变量表、操作数栈、动态链接、方法出口等信息。),前者服务于Java方法,后者服务于本地方法
- 虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
特点:
- 每个线程运行时所需要的内存,称为虚拟机栈,先进后出
- 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
- 垃圾回收是否涉及栈内存?垃圾回收主要指就是堆内存,当栈帧弹栈以后,内存就会释放
- 栈内存分配越大越好吗?未必,默认的栈内存通常为1024k栈帧过大会导致线程数变少,例如,机器总内存为512m,目前能活动的线程数则为512个,如果把栈内存改为2048k,那么能活动的栈帧就会减半
- 方法内的局部变量是否线程安全?如果方法内局部变量没有逃离方法的作用范围,它是线程安全的
- 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
什么是java方法和本地方法?
以下是对它们的详细介绍:
- Java方法:Java方法是使用Java语言编写的,经过编译后变成字节码存储在class文件中。它们由JVM(Java虚拟机)解释执行,具有可移植性特征,即同一份Java代码可以在不同的操作系统上运行而无需修改。
- 本地方法:本地方法是在Java中声明的方法,但是其实现是由其他编程语言(如C或C++)完成的。本地方法主要用于调用底层功能的场合,例如直接与操作系统交互或者进行硬件操作等。
5.方法区(线程共有)
- 方法区(Method Area)是各个线程共享的内存区域
- 主要存储类的信息、运行时常量池
- 虚拟机启动的时候创建,关闭虚拟机时释放
- 如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError: Metaspace
运行时常量池
运行时常量池是方法区的一部分。
Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。
除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()
6.直接内存
它又叫做堆外内存,线程共享的区域,在 Java 8 之前有个永久代的概念,实际上指的是 HotSpot 虚拟机上的永久代,它用永久代实现了 JVM 规范定义的方法区功能,主要存储类的信息,常量,静态变量,即时编译器编译后代码等,这部分由于是在堆中实现的,受 GC 的管理,不过由于永久代有 -XX:MaxPermSize 的上限,所以如果大量动态生成类(将类信息放入永久代),很容易造成 OOM,有人说可以把永久代设置得足够大,但很难确定一个合适的大小,受类数量,常量数量的多少影响很大。
所以在 Java 8 中就把方法区的实现移到了本地内存中的元空间中,这样方法区就不受 JVM 的控制了,也就不会进行 GC,也因此提升了性能。
6. 堆栈的区别是什么?
1、栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的的。堆会GC垃圾回收,而栈不会。
2、栈内存是线程私有的,而堆内存是线程共有的。
3,、两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常。
栈空间不足:java.lang.StackOverFlowError。
堆空间不足:java.lang.OutOfMemoryError。
7.JVM中对象在堆中的生命周期?
- 在 JVM 内存模型的堆中,堆被划分为新生代和老年代 新生代又被进一步划分为 Eden区 和 Survivor区,Survivor 区由 From Survivor 和 To Survivor 组成
- 当创建一个对象时,对象会被优先分配到新生代的 Eden 区 此时 JVM 会给对象定义一个对象年轻计数器(-XX:MaxTenuringThreshold)
- 当 Eden 空间不足时,JVM 将执行新生代的垃圾回收(Minor GC) JVM 会把存活的对象转移到 Survivor 中,并且对象年龄 +1对象在 Survivor 中同样也会经历 Minor GC,每经历一次 Minor GC,对象年龄都会+1
- 如果分配的对象超过了
-XX:PetenureSizeThreshold
,对象会直接被分配到老年代
8.JVM中对象的分配过程?
为对象分配内存是一件非常严谨和复杂的任务,JVM 的设计者们不仅需要
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
我的笔记专栏,内有自己整理的八股知识笔记和算法刷题笔记,我会不断通过他人和自己的面经来更新和完善自己的八股笔记。专栏每增加一篇文章费用就会上涨一点,如果你喜欢的话建议你尽早订阅。内有超详细苍穹外卖话术!后续还会更新其他项目和我的实习经历的话术!敬请期待!