【Java面试】二十、JVM篇(上):JVM结构

文章目录

  • 1、JVM
  • 2、程序计数器
  • 3、堆
  • 4、栈
    • [4.1 垃圾回收是否涉及栈内存](#4.1 垃圾回收是否涉及栈内存)
    • [4.2 栈内存分配越大越好吗](#4.2 栈内存分配越大越好吗)
    • [4.3 方法内的局部变量是否线程安全吗](#4.3 方法内的局部变量是否线程安全吗)
    • [4.4 栈内存溢出的情况](#4.4 栈内存溢出的情况)
    • [4.5 堆和栈的区别是什么](#4.5 堆和栈的区别是什么)
  • 5、方法区
    • [5.1 常量池](#5.1 常量池)
    • [5.2 运行时常量池](#5.2 运行时常量池)
  • 6、直接内存

1、JVM

Java源码编译成class字节码后,JVM负责class字节码的处理。不同操作系统下的JVM,将字节码处理成对应操作系统下的二进制文件,从而实现一次编译,到处运行。能到处运行,是因为不同操作系统有不同的JVM,也即编译后的class字节码和操作系统之间,隔着一个JVM在干活儿。

简单的理解,JVM就是Java二进制字节码的运行环境,是JDK包下包含的一些代码,像一个虚拟的计算机一样处理着class字节码。

JVM有两个最大的亮点:

  • 处理class字节码,实现一次编译,到处运行
  • 自动内存管理,垃圾回收机制,创建的对象,用完后不用手动回收

JVM的组成:

2、程序计数器

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

如上,线程1 执行字节码从第0行到第10行后,CPU时间片用完,线程1的程序计数器记录下当前行号。CPU的时间片分给了线程2 ,线程2执行到第9行后,CPU时间片用完。线程1再次抢到时间片,继续执行,此时,根据线程1的程序计数器,CPU就知道该从第10行继续往下执行。

3、堆

  • 堆区,线程共享
  • 保存着创建出来的对象
  • use、total、max,use == max 后,无法再分配空间,堆内存溢出

堆中有年轻代和老年代。年轻代有三部分,伊甸园区和两块大小相同的幸存者区。根据JVM策略,新创建的对象在伊甸园区,伊甸园区满了以后,触发Young GC,GC后或者的对象,复制到幸存者区,后面每GC一次,活着的对象年龄加一(对象头里存着年龄),对象GC年龄到达阈值(如15)后,晋升到老年代。

元空间里存类的信息、静态变量、常量、编译后的字节码信息(InstanceKlass对象(c++))。对JDK7和8,其位置变化:

  • JDK7及以前,方法区在堆区的永久代空间里
  • JDK8及以后,永久代被移除,用元空间代替,方法区在元空间,而元空间在操作系统的直接内存里,理论上可以一直分配

因为永久代/方法区或者说后来的元空间,存储的主要是一些类信息和常量,需求开发,加载的类越来越多,这个空间不可控,移除永久代,在本地内存放个元空间,可以防止OOM

4、栈

栈,即每个线程运行时需要的内存空间,保存着该线程方法调用的基本数据,先进后出,其中,每一个调用的方法用一个叫栈帧的东西存。

4.1 垃圾回收是否涉及栈内存

垃圾回收处理的主要是堆内存,对于栈内存,栈帧弹栈后,内存就会释放

4.2 栈内存分配越大越好吗

默认1024k,栈帧过大会导致线程数变少,机器总内存为512m,目前能活动的线程数则为512个,如果把内存改为2048k,则可活动的线程数上限在栈内存方面就会减半

4.3 方法内的局部变量是否线程安全吗

在方法的作用范围之内,是线程安全的。出了方法,被怎么使用就不一定了。如下:

每个线程过来,都会在自己的栈里创建一个m1方法对应的栈帧,栈帧里存着局部变量sb,因此线程安全。

对m2方法来说,局部变量sb是传过来的,那可能就有线程安全问题,如上面main线程中在操作sb,新开的一个线程也在操作sb。同理,m3方法,将局部变量sb return,后面可能被一个成员变量接收,但后面本质上也不关局部变量的事了

4.4 栈内存溢出的情况

  • 栈帧过多导致栈内存溢出,如递归调用
java 复制代码
public static void m1() {
	m1();
}
  • 栈帧过大导致栈内存溢出

4.5 堆和栈的区别是什么

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

5、方法区

  • 方法区和堆一样,各个线程共享
  • 主要存储类的信息(InstanceKclass对象)、运行时常量池
  • 虚拟机启动的时候创建,虚拟机关闭的时候释放
  • 方法区的内存空间不够时,抛异常OutOfMemoryError:Metaspace
  • 方法区是一个概念,不同版本的JDK有不同的实现,对JDK7来说,永久代是其对方法区的落地实现(且此时永久代在堆区),对JDK8来说,则给方法区换了一种实现:元空间(元空间在本地内存)

5.1 常量池

javap查看一个类是字节码的结构信息:可以看到类的基本信息、常量池、方法的定义:


常量池可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息

5.2 运行时常量池

上面提到,常量池是class文件中的,当这个类被加载,它的常量池信息就会放入到运行时常量池,并发里面的符号地址变为真实地址

常量池和运行时常量池的区别:

【区别】

6、直接内存

直接内存(操作系统分给JVM进程的内存之外的内存),不属于JVM内存,不由JVM进行管理。在进行NIO操作时,用于数据缓冲区,其分配回收成本高,但读写性能好。常规IO复制文件流程:

NIO复制文件流程:这块直接内存系统和Java代码都可以直接访问,少了一次缓冲区的复制操作

直接内存主要通过 java.nio 包下的 ByteBuffer 类来进行分配和使用。

java 复制代码
ByteBuffer.allocateDirect(int capacity)

直接内存可以减少数据在 Java 堆和操作系统内存之间的拷贝次数,从而提高 I/O 的效率,常用于文件读写

【相关】

相关推荐
点点滴滴的记录7 分钟前
Java的CompletableFuture实现原理
java·开发语言·javascript
xiaolingting8 分钟前
Java 引用是4个字节还是8个字节?
java·jvm·引用·指针压缩
一只傻小白,13 分钟前
JAVA项目中freemarker静态模板技术
java·开发语言
袁庭新13 分钟前
Spring Boot项目接收前端参数的11种方式
java·springboot·袁庭新·如何接收前端数据·boot接收数据
机跃15 分钟前
递归算法常见问题(Java)
java·开发语言·算法
程序员-小李35 分钟前
餐厅下单助手系统(Java+MySQL)
java·开发语言·mysql
开心工作室_kaic39 分钟前
springboot496基于java手机销售网站设计和实现(论文+源码)_kaic
java·开发语言·智能手机
像少年啦飞驰点、41 分钟前
SpringBoot + HttpSession 自定义生成sessionId
java·开发语言
珊珊来吃1 小时前
EXCEL中给某一列数据加上双引号
java·前端·excel
DogDaoDao1 小时前
leetcode 面试经典 150 题:矩阵置零
数据结构·c++·leetcode·面试·矩阵·二维数组·矩阵置零