Java面试 (4) :JVM(基本组成、垃圾回收…)

Java面试——JVM:基本组成、垃圾回收…

1 JVM基本组成

1.1 主要组成部分与运行流程

在这里插入图片描述

JVM的主要组成部分:

  • ClassLoader:类加载器。
  • Runtime Data Area:运行时数据区(内存分区)
  • Execution Engine:执行引擎
  • Native Method Library:本地库接口

运行流程:

  1. 类加载器(ClassLoader)将Java代码转换为字节码
  2. 运行时数据区(Runtime Data Area)加载字节码至内存中。字节码文件只是JVM的一套指令集规范,并不能直接交给底层系统去执行,而是由执行引擎运行
  3. 执行引擎(Execution Engine)将字节码翻译为底层系统指令,再交由CPU执行去执行,此时需要调用其他语言的本地库接口(Native Method Library)来实现整个程序的功能

1.2 运行时数据区

组成部分:堆、方法区、栈、本地方法栈、程序计数器

  1. 用于解决对象实例存储的问题,为垃圾回收器管理的主要区域。
  2. 方法区在Java 7中可认为是堆的一部分,用于存储已被虚拟机加载的信息,常量、静态变量、即时编译器编译后的代码。
  3. 用于解决程序运行的问题,存储栈帧,栈帧中存储局部变量表、操作数栈、动态链接、方法出口等信息。
  4. 本地方法栈与栈功能相同,执行的是本地方法,为Java调用非Java代码的接口。
  5. 程序计数器(PC寄存器)存放当前线程所执行的字节码的行数。JVM工作时通过改变这个计数器的值来选取下一个需要执行的字节码指令。

1.2.1 程序计数器

线程私有的、内部保存的字节码的行号,用于记录正在执行的字节码指令的地址。

javap -verbose xx.class:打印堆栈大小,局部变量的数量和方法的参数。

在这里插入图片描述

java虚拟机对于多线程采用线程轮流切换并分配线程执行时间的方法。在任一时间点上,一个处理器只会处理一个线程,如果当前被执行线程的所分配执行时间用尽(被挂起),处理器会切换执行另一个线程。当该线程的执行时间用尽时,处理器会切换至刚才被挂起的线程,根据程序计数器获取上一次执行的行号,继续向下执行。

程序计数器是JVM规范中唯一一个没有规定出现OOM的区域,所以该空间不会进行GC。

1.2.2 堆

线程共享的区域,主要用于保存对象实例、数组等。当堆中没有内存空间可分配给实例且无法再扩展时,会抛出OutOfMemoryError。

在这里插入图片描述

堆中存在年轻代老年代方法区/永久区(Java 7)或永久代(Java 8)。

  • Young区(年轻代)被划分为三部分——Eden区和两个大小严格相同的Survivor区,其中在Survivor区间中,某一时刻只有其中一个被使用,另一个留做垃圾收集时复制对象用。Eden区满时, GC会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,仍存活于Survivor的对象将被移动到Tenured区间。
  • Tenured区(老年代)主要保存生命周期长的对象(老的对象)。当对象在Young复制转移一定次数后就会被转移到Tenured区。
  • Perm代(永久代,≈方法区)主要保存的是保存的类信息、静态变量、常量、编译后的代码。在Java 7中,方法区在堆上,会受到GC的管理,且存在大小限制,大量动态生成类放入方法区很容易造成OOM。
    为了避免方法区出现OOM,Java 8中将堆上的方法区/永久代移动到本地内存上重新开辟了一块空间,称为元空间。元空间并不在虚拟机中,因此默认情况下元空间的大小仅受本地内存限制。

1.2.3 方法区

方法区类似于传统语言的编译代码存储区,其存储每个类的结构,如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化及接口初始化中使用的特殊方法。

方法区在虚拟机启动时创建。尽管方法区在逻辑上是堆的一部分,但简单的实现常选择不进行垃圾收集或压缩它。本规范不要求方法区的位置或用于管理已编译代码的策略。可以是固定大小,也可以根据计算需要扩大,若不需要更大的方法区可以缩小。方法区的内存不需要是连续的。

Java 虚拟机为程序员或用户提供对方法区初始大小的控制,以及在方法区域大小可变的情况下,对最大和最小方法区域大小的控制。

以下异常情况与方法区相关:

  • 如果方法区域中的内存无法满足分配请求,Java 虚拟机将抛出OutOfMemoryError。

1.2.4 虚拟机栈

栈描述方法执行时的内存模型,是线程私有的,生命周期与线程相同。每个方法被执行的同时会创建栈桢,保存执行方法时的局部变量、动态连接信息、方法返回地址信息等等。方法开始执行的时候会进栈,方法执行完会出栈(相当于清空数据),所以栈不需要进行GC

堆、栈的区别:

  1. 栈内存一般用来存储局部变量和方法调用;堆内存用来存储Java对象和数组。堆会GC垃圾回收;栈不会。
  2. 栈内存是线程私有的,堆内存是线程共有的。
  3. 两者异常错误不同,若栈内存或者堆内存不足都会抛出异常。
    • 栈空间不足:java.lang.StackOverFlowError
    • 堆空间不足:java.lang.OutOfMemoryError

1.3 直接内存

不受JVM内存回收管理,是虚拟机的系统内存。常见于NIO操作时用于数据缓冲区,分配回收成本较高,但读写性能高。

【例】在本地电脑中的一个较大的文件(超过100m)从一个磁盘挪到另外一个磁盘。代码如下

/**
 * 演示 ByteBuffer 作用
 */
public class Demo1_9 {
   
    static final String FROM = "E:\\download\\rbsp-2\\hyperplasma.mp4";
    static final String TO = "E:\\new.mp4";
    static final int _1Mb = 1024 * 1024;

    public static void main(String[] args) {
   
        io(); // io用时:1535.586957 1766.963399 1359.240226
        directBuffer(); // directBuffer用时:479.295165 702.291454 562.56592
    }

    private static void directBuffer() {
   
        long start = System.nanoTime();
        try (FileChannel from = new FileInputStream(FROM).getChannel();
             FileChannel to = new FileOutputStream(TO).getChannel();
        ) {
   
            ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
            while (true) {
   
                int len = from.read(bb);
                if (len == -1) {
   
                    break;
                }
                bb.flip();
                to.write(bb);
                bb.clear();
            }
        } catch (IOException e) {
   
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("directBuffer用时:" + (end - start) / 1000_000.0);
    }

    private static void io() {
   
        long start = System.nanoTime();
        try (FileInputStream from = new FileInputStream(FROM);
             FileOutputStream to = new FileOutputStream(TO);
        ) {
   
            byte[] buf = new byte[_1Mb];
            while (true) {
   
                int len = from.read(buf);
                if (len == -1) {
   
                    break;
                }
                to.write(buf, 0, len);
            }
        } catch (IOException e) {
   
            e.printStackTrace();
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Akira37

💰unneeded

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值