Java面试核心知识点1——JVM

1 JVM的运行机制

我们知道JVM是用于运行Java字节码的虚拟机,主要包括一套字节码指令集、一组程序计数器、一个虚拟机栈、一个虚拟机堆、一个方法区和一个垃圾回收器。JVM运行在操作系统之上,不与硬件设备直接交互。

Java源文件(.java文件)通过编译器被编译成.class文件(字节码文件),.class文件又被JVM中的解释器编译成机器吗在不同操作系统上运行(Windows,Mac,Linux)。每种操作系统的解释器都是不同的,但是基于解释器的虚拟机是一样的,这也是为什么Java能够跨平台的原因。在一个Java进程开始运行后,虚拟机就开始实例化了,有多个进程启动就会实例化多个虚拟机实例。进程退出或者关闭,则虚拟机实例消亡,在多个虚拟机实例之间不能共享数据。

Java虚拟机包括一个类加载器子系统、运行时数据区、执行引擎和本地接口库。本地接口库通过调用本地方法库与操作系统进行交互。

2.JVM的内存区域

JVM的内存区域分为线程私有区域(程序计数器、虚拟机栈、本地方法区)、线程共享区域(堆,方法区)和直接内存

线程私有区域:生命周期与线程相同,随线程的启动而创建,结束而销毁。该部分主要包括程序计数器、虚拟机栈和本地方法区。

  1.     程序计数器:是一块很小的内存空间,用来存储当前运行的线程所执行的字节码的行号指示器。每个运行中的线程中都有一个独立的程序计数器,在方法执行时,该方法的程序计数器记录的是实时虚拟机字节码指令的地址;如果该方法执行的是native方法,则程序计数器的值为空。这是唯一一个没有内存溢出的区域(out of memory)
  2.     虚拟机栈:描述Java方法的执行过程。生命周期与线程一致,主要描述的是Java方法的执行过程的内存模型,每个方法在执行的时候会创建一个栈桢,这个栈桢会存储局部变量表、操作数栈、动态链接、方法出口等信息。同时它还用来保持部分运行时数据及其数据结构,处理动态链接方法的返回值和异常分派。 栈桢用来记录方法的执行过程,在方法被执行时虚拟机为其创建一个与之对应的栈桢,方法的执行和返回对应栈栈在虚拟机中的入栈和出栈,
  3.    本地方法区(栈):与虚拟机栈类似,区别是虚拟机栈为执行的Java方法服务;本地方法栈为native方法服务。

线程共享区域:随虚拟机的启动而创建,随虚拟机的关闭而销毁。

  1. 堆:它是JVM内存管理中最大的一块。在JVM运行过程中创建的对象和产生的数据都被存储在堆中,堆是被线程共享的内存区域,也是垃圾收集器进行垃圾回收的最主要的内存区域。由于现代JVM采用分代收集算法,因此Java堆从GC(Garbage Collection,垃圾回收)的角度还可以细分为:新生代、老年代和永久代。
  2. 方法区:也被称为永久代,用于存储常量、静态变量、类信息、即时编译器编译后的机器码、运行时常量池等数据。JVM把GC分代收集扩展至方法区,即使用Java堆的永久代来实现方法区,这样JVM的垃圾收集器就可以像管理Java堆一样管理这部分内存。永久带的内存回收主要针对常量池的回收和类的卸载,因此可回收的对象很少。 

          常量被存储在运行时常量池中,是方法区的一部分。静态变量也属于方法区的一部分。在类信息(Class文件)中不但保存了类的版本、字段、方法、接口等描述信息,还保存了常量信息。 

直接内存;堆外内存。

3.JVM的运行时内存(JVM堆)

JVM的运行时内存也叫作JVM堆,从GC的角度可以将JVM堆分为新生代、老年代和永久代。其中新生代默认占1/3堆空间,老年代默认占2/3堆空间,永久代占非常少的堆空间。新生代又分为Eden区、ServivorFrom区和ServivorTo区,Eden区默认占8/10新生代空间,ServivorFrom区和ServivorTo区默认分别占1/10新生代空间

  • 新生代:eden区、servivorTo区和ServivorFrom区。

     JVM新创建的对象(除了大对象外)会被存放在新生代,默认占1/3堆内存空间。由于JVM会频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。新生代又分为Eden区、ServivorTo区和ServivorFrom区,如下所述。

(1)Eden区:Java新创建的对象首先会被存放在Eden区,如果新创建的对象属于大对象,则直接将其分配到老年代。大对象的定义和具体的JVM版本、堆大小和垃圾回收策略有关,一般为2KB~128KB,可通过XX:PretenureSizeThreshold设置其大小。在Eden区的内存空间不足时会触发MinorGC,对新生代进行一次垃圾回收。

(2)ServivorTo区:保留上一次MinorGC时的幸存者。

(3)ServivorFrom区:将上一次MinorGC时的幸存者作为这一次MinorGC的被扫描者。

新生代的GC过程叫作MinorGC,采用复制算法实现,具体过程如下。

(1)把在Eden区和ServivorFrom区中存活的对象复制到ServivorTo区。如果某对象的年龄达到老年代的标准(对象晋升老年代的标准由XX:MaxTenuringThreshold设置,默认为15),则将其复制到老年代,同时把这些对象的年龄加1;如果ServivorTo区的内存空间不够,则也直接将其复制到老年代;如果对象属于大对象(大小为2KB~128KB的对象属于大对象,例如通过XX:PretenureSizeThreshold=2097152设置大对象为2MB,1024×1024×2Byte=2097152Byte=2MB),则也直接将其复制到老年代。

(2)清空Eden区和ServivorFrom区中的对象

(3)将ServivorTo区和ServivorFrom区互换,原来的ServivorTo区成为下一次GC时的ServivorFrom区。

  • 老年代

    老年代主要存放有长生命周期的对象和大对象。老年代的GC过程叫作MajorGC。在老年代,对象比较稳定,MajorGC不会被频繁触发。在进行MajorGC前,JVM会进行一次MinorGC,在MinorGC过后仍然出现老年代空间不足或无法找到足够大的连续空间分配给新创建的大对象时,会触发MajorGC进行垃圾回收,释放JVM的内存空间。

MajorGC采用标记清除算法,该算法首先会扫描所有对象并标记存活的对象,然后回收未被标记的对象,并释放内存空间。

因为要先扫描老年代的所有对象再回收,所以MajorGC的耗时较长。MajorGC的标记清除算法容易产生内存碎片。在老年代没有内存空间可分配时,会抛出Out Of Memory异常。

  • 永久代

    永久代指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。Class在类加载时被放入永久代。永久代和老年代、新生代不同,GC不会在程序运行期间对永久代的内存进行清理,这也导致了永久代的内存会随着加载的Class文件的增加而增加,在加载的Class文件过多时会抛出Out Of Memory异常,比如Tomcat引用Jar文件过多导致JVM内存不足而无法启动。

需要注意的是,在Java 8中永久代已经被元数据区(也叫作元空间)取代。元数据区的作用和永久代类似,二者最大的区别在于:元数据区并没有使用虚拟机的内存,而是直接使用操作系统的本地内存。因此,元空间的大小不受JVM内存的限制,只和操作系统的内存有关。

在Java 8中,JVM将类的元数据放入本地内存(Native Memory)中,将常量池和类的静态变量放入Java堆中,这样JVM能够加载多少元数据信息就不再由JVM的最大可用内存(MaxPermSize)空间决定,而由操作系统的实际可用内存空间决定。

4.垃圾回收与算法

4.1 如何确认垃圾

Java确认对象是否回收有两种方式:引用计数法和可达性分析,其中,引用计数法容易产生循环引用的问题,可达性分析通过根搜索算法(GC Roots Tracing)来实现。

  1. 引用计数法:在Java中如果要操作对象,就必须先获取该对象的引用,因此可以通过引用计数法来判断一个对象是否可以被回收。在为对象添加一个引用时,引用计数加1;在为对象删除一个引用时,引进计数减1;如果一个对象的引用计数为0,则表示此刻该对象没有被引用,可以被回收。容易产生循环引用问题。循环引用指两个对象相互引用,导致它们的引用一直存在,而不能被回收
  2. 可达性分析法:为了解决引用计数法的循环引用问题,Java还采用了可达性分析来判断对象是否可以被回收。具体做法是首先定义一些GC Roots对象,然后以这些GC Roots对象作为起点向下搜索,如果在GC roots和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象要经过至少两次标记才能判定其是否可以被回收,如果在两次标记后该对象仍然是不可达的,则将被垃圾收集器回收。

4.2 Java中常用的垃圾回收算法

Java中常用的垃圾回收算法有标记清除(Mark-Sweep)、复制(Copying)、标记整理(Mark-Compact)和分代收集(Generational Collecting)这4种垃圾回收算法。

  • 标记清除算法

标记清除算法是基础的垃圾回收算法,其过程分为标记和清除两个阶段。在标记阶段标记所有需要回收的对象,在清除阶段清除可回收的对象并释放其所占用的内存空间。

由于标记清除算法在清理对象所占用的内存空间后并没有重新整理可用的内存空间,因此如果内存中可被回收的小对象居多,则会引起内存碎片化的问题,继而引起大对象无法获得连续可用空间的问题。

  • 复制算法

复制算法是为了解决标记清除算法内存碎片化的问题而设计的。复制算法首先将内存划分为两块大小相等的内存区域,即区域1和区域2,新生成的对象都被存放在区域1中,在区域1内的对象存储满后会对区域1进行一次标记,并将标记后仍然存活的对象全部复制到区域2中,这时区域1将不存在任何存活的对象,直接清理整个区域1的内存即可。

复制算法的内存清理效率高且易于实现,但由于同一时刻只有一个内存区域可用,即可用的内存空间被压缩到原来的一半,因此存在大量的内存浪费。同时,在系统中有大量长时间存活的对象时,这些对象将在内存区域1和内存区域2之间来回复制而影响系统的运行效率。因此,该算法只在对象为“朝生夕死”状态时运行效率较高。

  • 标记整理算法

标记整理算法结合了标记清除算法和复制算法的优点,其标记阶段和标记清除算法的标记阶段相同,在标记完成后将存活的对象移到内存的另一端,然后清除该端的对象并释放内存。

  • 分代收集算法

无论是标记清除算法、复制算法还是标记整理算法,都无法对所有类型(长生命周期、短生命周期、大对象、小对象)的对象都进行垃圾回收。因此,针对不同的对象类型,JVM采用了不同的垃圾回收算法,该算法被称为分代收集算法。分代收集算法根据对象的不同类型将内存划分为不同的区域,JVM将堆划分为新生代和老年代。新生代主要存放新生成的对象,其特点是对象数量多但是生命周期短,在每次进行垃圾回收时都有大量的对象被回收;老年代主要存放大对象和生命周期长的对象,因此可回收的对象相对较少。因此,JVM根据不同的区域对象的特点选择了不同的算法。目前,大部分JVM在新生代都采用了复制算法,因为在新生代中每次进行垃圾回收时都有大量的对象被回收,需要复制的对象(存活的对象)较少,不存在大量的对象在内存中被来回复制的问题,因此采用复制算法能安全、高效地回收新生代大量的短生命周期的对象并释放内存。JVM将新生代进一步划分为一块较大的Eden区和两块较小的Servivor区,Servivor区又分为ServivorFrom区和ServivorTo区。JVM在运行过程中主要使用Eden区和ServivorFrom区,进行垃圾回收时会将在Eden区和ServivorFrom区中存活的对象复制到ServivorTo区,然后清理Eden区和ServivorFrom区的内存空间 。

老年代主要存放生命周期较长的对象和大对象,因而每次只有少量非存活的对象被回收,因而在老年代采用标记清除算法。

在JVM中还有一个区域,即方法区的永久代,永久代用来存储Class类、常量、方法描述等。在永久代主要回收废弃的常量和无用的类。JVM内存中的对象主要被分配到新生代的Eden区和ServivorFrom区,在少数情况下会被直接分配到老年代。在新生代的Eden区和ServivorFrom区的内存空间不足时会触发一次GC,该过程被称为MinorGC。在MinorGC后,在Eden区和ServivorFrom区中存活的对象会被复制到ServivorTo区,然后Eden区和ServivorFrom区被清理。如果此时在ServivorTo区无法找到连续的内存空间存储某个对象,则将这个对象直接存储到老年代。若Servivor区的对象经过一次GC后仍然存活,则其年龄加1。在默认情况下,对象在年龄达到15时,将被移到老年代。

5.垃圾收集器

Java堆内存分为新生代和老年代:新生代主要存储短生命周期的对象,适合使用复制算法进行垃圾回收;老年代主要存储长生命周期的对象,适合使用标记整理算法进行垃圾回收。因此,JVM针对新生代和老年代分别提供了多种不同的垃圾收集器,针对新生代提供的垃圾收集器有Serial、ParNew、Parallel Scavenge,针对老年代提供的垃圾收集器有Serial Old、Parallel Old、CMS,还有针对不同区域的G1分区收集算法。

重点说cms和g1垃圾收集器原理:

CMS垃圾回收器:

G1垃圾回收器:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然,我可以为您介绍一些Java基础知识点面试题。以下是一些常见的Java基础知识点面试题及其答案: 1. 什么是JavaJava是一种面向对象的编程语言,具有跨平台特性,可以在不同的操作系统上运行。 2. Java的特点有哪些? Java具有以下特点: - 简单易学:Java语法相对简单,与C++相比更易于学习和使用。 - 面向对象:Java支持面向对象的编程范式,具有封装、继承和多态等特性。 - 跨平台性:Java通过虚拟机(JVM)实现跨平台,一次编写,到处运行。 - 安全性:Java提供了安全机制,如内存管理、异常处理和安全检查等。 - 多线程:Java支持多线程编程,可以实现并发操作。 3. 什么是面向对象编程? 面向对象编程(OOP)是一种编程范式,将数据和操作数据的方法封装在一起,形成对象。对象可以通过定义类来创建,类定义了对象的属性和行为。 4. Java中的基本数据类型有哪些? Java中的基本数据类型包括: - 整数类型:byte、short、int、long - 浮点数类型:float、double - 字符类型:char - 布尔类型:boolean 5. Java中的包是什么? 包(Package)是Java中用于组织类和接口的一种机制。它可以将相关的类和接口放在同一个包中,方便管理和使用。 6. 什么是Java的访问修饰符? Java的访问修饰符用于控制类、方法和变量的访问权限。常用的访问修饰符有public、protected、private和默认(没有修饰符)。 7. Java中的异常处理机制是什么? Java中的异常处理机制通过try-catch-finally语句块来实现。当代码可能抛出异常,可以使用try块来捕获异常,并在catch块中处理异常。finally块中的代码无论是否发生异常都执行。 8. 什么是Java的多线程? 多线程是指在一个程序中同执行多个线程,每个线程都是独立的执行流。Java通过Thread类和Runnable接口来实现多线程编程。 9. Java中的垃圾回收是什么? Java中的垃圾回收是自动内存管理的一种机制,通过垃圾回收器自动释放不再使用的内存。开发人员无需手动释放内存,可以专注于业务逻辑的实现。 10. 什么是Java的反射机制? Java的反射机制是指在运行动态地获取类的信息并操作类的属性和方法。通过反射机制,可以在运行创建对象、调用方法和访问属性等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值