一、JVM介绍,JVM运行流程
1、什么是JVM
Java Virtual Machine:Java程序的运行环境 (java二进制字节码的运行环境)
好处:
- 一次编写,到处运行
- 自动内存管理,垃圾回收机制
2、JVM运行流程
二、什么是程序计数器?
程序计数器:线程私有的,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。
对于每个方法,都会将代码转换为字节码,字节码中的行号就是代码的执行顺序,而每个线程根据自己的程序计数器来确定执行的行号
如上图,对于线程1,从第1行开始执行,执行到第10行之后切换到了线程2。线程2从第1行开始执行,执行到了第9行,然后又切换到了线程1,但是此刻线程1不是从第1行开始执行的,而是根据自己的程序计数器来确定继续从第10行往下执行。
1、概括
什么是程序计数器?
- 线程私有的,每个线程一份,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。
三、详细介绍一下堆
1、介绍
Java堆是一个线程共享的区域:主要用来保存对象实例,数组等,当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。
如上图是Java7和Java8的内存结构
首先说Java8,Java8中有年轻代和老年代。年轻代分为伊甸园(Eden)和两个幸存区(Survivor )。新产生的对象就会存放在伊甸园区,当进行Minor GC的时候就会将伊甸园中的部分垃圾回收,存留下来的会保存到幸存区。当幸存区的对象年龄超过15次时候没被垃圾回收就会进入老年代。详细步骤后面会进行讲解。
方法区保存的是Class类。Java8将方法区/永久代取消了,转到了本地内存。因为Java7中的方法区/永久代如果空间设置少了,就会造成堆空间内存溢出出现OOM,空间设置大了,就会造成堆空间浪费。所以移动到了本地内存。
2、概括
你能给我详细的介绍Java堆吗?
- 线程共享的区域:主要用来保存对象实例,数组等,内存不够则抛出OutOfMemoryError异常。
- 组成:年轻代+老年代
- 年轻代被划分为三部分,Eden区和两个大小严格相同的Survivor区
- 老年代主要保存生命周期长的对象,一般是一些老的对象
Jdk1.7和1.8的区别
- 1.7中有有一个永久代,存储的是类信息、静态变量、常量、编译后的代码。
- 1.8移除了永久代,把数据存储到了本地内存的元空间中,防止内存溢出。
四、什么是虚拟机栈 ?
Java Virtual machine Stacks (java 虚拟机栈)
- 每个线程运行时所需要的内存,称为虚拟机栈,先进后出。
- 每个栈由多个栈帧(frame) 组成,对应着每次方法调用时所占用的内存,栈帧保存的是当前方法的参数、局部变量、返回地址等。
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
1、垃圾回收是否涉及栈内存?
垃圾回收主要指就是堆内存,当栈帧弹栈以后,内存就会释放
2、栈内存分配越大越好吗?
未必,默认的栈内存通常为1024k栈帧过大会导致线程数变少,例如,机器总内存为512m,目前能活动的线程数则为512个,如果把栈内存改为2048k,那么能活动的栈帧就会减半
3、方法内的局部变量是否线程安全?
- 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的。
- 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全。
如上图:
- 对于方法m1,它是线程安全的,因为方法没有局部变量和返回值,所以不存在被其他线程修改的情况。
- 对于方法m2,它是线程不安全的,因为方法有局部变量,存在着被其他线程修改的情况。
- 对于方法m3,它是线程不安全的,因为方法有返回值,这个返回值可能存在被其他线程修改的情况。
4、栈内存溢出情况
- 栈帧过多导致栈内存溢出,典型问题:递归调用
- 栈帧过大导致栈内存溢出
5、概括
什么是虚拟机栈?
- 每个线程运行时所需要的内存,称为虚拟机栈
- 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
垃圾回收是否涉及栈内存?
- 垃圾回收主要指就是堆内存,当栈帧弹栈以后,内存就会释放
栈内存分配越大越好吗?
- 未必,默认的栈内存通常为1024k,栈帧过大会导致线程数变少
方法内的局部变量是否线程安全?
- 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的
- 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
什么情况下会导致栈内存溢出?
- 栈帧过多导致栈内存溢出,典型问题:递归调用
- 栈帧过大导致栈内存溢出
堆栈的区别是什么?
- 栈内存一般会用来存储局部变量方法调用,但堆内存是用来存储Java对象和数组的的。堆会GC垃圾回收,而栈不会
- 栈内存是线程私有的,而堆内存是线程共有的
- 两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常
- 栈空间不足: java.lang.StackOverFlowError。
- 堆空间不足:java.lang.OutOfMemoryError。
五、请解释下方法区
1、介绍方法区
- 方法区(Method Area)是各个线程共享的内存区域。
- 主要存储类的信息、运行时常量池。
- 虚拟机启动的时候创建,关闭虚拟机时释放。
- 如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError: Metaspace。
JDK1.8中将方法区移动到了直接内存的元空间中
2、 常量池
可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息。
首先如左图,里面有方法的机器指令,当执行行号为0的字节码指令的时候,就会根据该行字节码#2然后到常量池表中找到#2所在的位置,然后进行翻译,这样才能执行该行的指令,知道该行指令的作用。
3、运行时常量池
常量池是*.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
如上图,运行时常量池将符号改为内存地址。因为只有获得真正的内存地址才能执行代码指令
4、概括
能不能解释一下方法区?
- 方法区(Method Area)是各个线程共享的内存区域
- 主要存储类的信息、运行时常量池
- 虚拟机启动的时候创建,关闭虚拟机时释放
- 如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError:Metaspace
介绍一下运行时常量池
- 常量池:可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名方法名、参数类型、字面量等信息
- 当类被加载,T它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
六、堆,虚拟机栈,方法区分别存储的是什么?
1、堆
堆存储的是使用new关键字创建的类(对象或者类的实例)或者数组的(含成员变量)
2、栈
栈存放的是基本类型的变量数据和对象的引用。
对象的声明引用解释:
对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中),在Java中,当我们使用new关键字创建一个对象时,这个对象会被存储在堆中,并返回一个引用,这个引用会被存储在栈中。这个引用指向了堆中的对象,我们可以通过这个引用来访问和操作这个对象 。
public class StackExample {
public static void main(String[] args) {
// 声明一个int类型的变量num,存储在栈中
int num = 10;
// 声明一个String类型的变量name,存储在栈中
String name = "Bing";
// 输出num和name的值
System.out.println(num + " " + name);
}
}
3、方法区
Java方法区(Method Area)用于存储已被虚拟机加载的类信息、常量、静态变量、动态生成的类等数据。方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。
七、请解释直接内存
直接内存:并不属于JM中的内存结构,不由JVM进行管理。是虚拟机的系统内存,常见于 NIO 操作时,用于数据缓冲区,它分配回收成本较高,但读写性能高。
Java代码完成文件拷贝
1、常规IO数据拷贝流程
java本身不具备磁盘的读写功能,如果调用磁盘读写,必须调用操作系统所提供的函数。
当调用操作系统所提供的IO方法的时候,就会设计CPU的状态切换,首先从用户态切换到内核态,当切换到内核态的时候,就通过系统提供的函数读取磁盘中的文件,读取的文件会分批次保存在操作系统内存划出的一片系统缓存区。但是系统缓存区java无法去获取,所以java会在堆中分配一片内存名为java缓冲区,将系统缓存区读到java缓冲区中。
读到java缓冲区后CPU切换到用户态,通过输出流写出操作,反复读取,然后将文件读到目标位置。
但是有个问题就是两个缓冲区会对性能产生巨大的影响
2、NIO数据拷贝过程
NIO不在使用两个缓冲区,而是直接使用直接内存来代替缓冲区,因此性能大幅提高
3、概括
你听过直接内存吗?
- 并不属于JVM中的内存结构,不由JVM进行管理。是虚拟机的系统内存
- 常见于 NIO操作时,用于数据缓冲区,分配回收成本较高,但读写性能高,不受JVM内存回收管理