【JVM】
【问】JVM的总体结构?
JVM四大组成:类装载器+运行时数据区+执行引擎+本地接口。
【问】运行时数据区(java内存区域)的内存图?五个部分的详细介绍?
(1)
java8以前:
java8以后:
(2)
a、堆
存放:对象实例。
更新速度慢,二级缓存。程序员分配释放或GC机制。
java7及以前:
java8:
永久代(方法区)被从JVM中移除,用元空间替代,位于直接内存中。
b、方法区(java8移动到元空间)
java7方法区存放:常量/常量池+静态变量+类信息+即时编译后代码。
java8:抛弃方法区,常量+静态变量 存入jvm堆,其余元数据移动到元空间存放。
方法区和永久代的关系?
永久代是HotSpot虚拟机对方法区的⼀种实现方式,把方法区用来当作永久代。
常用参数?
java7:调节方法区大小:
java8:调节元空间大小:
为什么要把永久代替换为元空间?
元空间在直接内存中,不受jvm内存大小限制,溢出概率更小。
c、程序计数器(PC寄存器)
作用:
(1)字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
(2)在多线程的情况下,程序计数器⽤于记录当前线程执行的位置,从⽽当线程被切换回来的时
候能够知道该线程上次运⾏到哪儿了。
注意:
唯一一个不会出现OOM问题的内存区域。
d、JVM栈
存放:对象引用和基本数据类型。
更新速度快,一级缓存。变量生命周期结束自动释放。
线程私有也是线程安全,一个线程对应一个虚拟机栈区域,一个方法的调用对应一个栈帧。(栈的内部由一个个的栈帧组成)(return或报错则弹栈)
e、本地方法栈(native)
调用本地方法(native方法)的区域,连接其他语言。(C/C++)
f、直接内存(不属于JVM,但也被频繁使用)
【问】JVM堆、栈的区别?
栈内存 | 堆内存 | |
存储对象 | 对象引用和基本数据类型 | 对象实例 |
更新速度 | 快,一级缓存 | 慢,二级缓存 |
内存释放 | 变量生命周期结束自动释放 | 程序员分配释放、GC机制 |
线程 | 线程私有 | 线程公有 |
【问】java程序new一个对象后发生了什么,比如:A a=new A(); ?
编译A类>>类加载流程>>对象创建流程>>对象访问定位
【问】类的加载过程?类的生命周期?
(1)加载(双亲委派机制)
JVM读取Class文件,创建Class对象的过程。
(2)连接
a、验证
确保Class文件符合当前虚拟机的要求。
b、准备
在方法区中为类变量分配内存空间并设置默认值。(加了final修饰的直接准备)
c、解析
将常量池中的符号引用替换为直接引用。
(3)初始化
为类的静态变量赋初始值和收集静态代码块。
【问】类加载机制(双亲委派机制)?作用?
(1)
三个等级的加载器:
(1)根加载器
(2)扩展类加载器
(3)应用程序类加载器(负责加载我们自己写的类)
第一步:
由下往上走的逻辑,加载过的话不用加载;如果都没有加载过,就一直走到最上边(也就是根加载器);
第二步:
假如都没有加载过,走到了根,就采取第二步加载逻辑,由上往下走的逻辑。每到一个加载器判定自己能否加载,自己能加载就自己加载,加载不了就给子加载器加载。一直到最下面的应用程序类加载器(有时还有自定义类加载器,需要继承java.lang.ClassLoader,重写findclass方法)。若还是加载不了,抛出ClassNotFound异常。
(2)
a、第一步的逻辑是为了节省时间,提升性能。
b、第二步的逻辑是为了当修改下层加载器,不影响上层加载器,保证安全性。
【问】反射:
定义:
JAVA反射机制是在运行状态中,通过class类对象动态的获取类的信息或动态调用对象的方法,这种机制称为java的反射机制。
特点:
反射可以实现动态创建对象和编译,体现出很大的灵活性,是Java被视为动态语言的关键。但是会影响一些性能。
【问】Java对象的创建过程?(5步)(运行时数据区里进行)
(1)类加载检查:
虚拟机遇到⼀条 new 指令时,检查是否完成了完整的类加载过程,如果没有,必须执行类加载过程。(加载、连接、初始化)
(2)内存分配:
为新生对象分配堆内存。
内存分配方式?
分配方式有 “指针碰撞” 和 “空闲列表” 两种方式。选择哪种分配方式取决于java堆是否有内存碎片(取决于垃圾收集器)
解决内存分配并发问题?
(1)乐观锁CAS+失败重试: CAS 是乐观锁的⼀种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采⽤ CAS配上失败重试的方式保证更新操作的原子性。
(2)独立内存TLAB: 为每⼀个线程预先在 Eden 区分配⼀块独立内存TLAB,JVM 在给线程中的对象分配内存时,首先在TLAB 分配,当对象大于TLAB 中的剩余内存或 TLAB 的内存已⽤尽时,再采⽤上述的CAS进行内存分配.
(3)对象的初始化零值:
将分配到的内存空间都初始化为零值(不包括对象头),保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使⽤,程序能访问到这些字段的数据类型所对应的零值。
(4)设置对象头:
设置对象头信息:这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC 分代年龄等。
(5)执行init构造方法:
接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样⼀个真正可用的对象才算完全产生出来。(字段不在是默认值,而是程序员设定的值)
【问】对象的访问定位有哪两种方式?
我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有①使用句柄和②直接指针两种:
1. 句柄池:
Java堆中将会划分出⼀块内存来作为句柄池,reference 中存储的就是对象的句柄地址,⽽句柄中包含了对象实例数据与类型数据各⾃的具体地址信息;
2. 直接指针:
如果使⽤直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。
这两种对象访问方式各有优势:
(1)句柄池的好处:
对象移动时地址修改方便。引用中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,⽽不需要更改引用。
(2)直接指针的好处:
就是访问对象速度快。它节省了⼀次指针定位的时间开销。