题目 01 请你用自己的语言介绍 Java 运行时数据区(内存区域)
01.堆、虚拟机栈、本地方法栈、方法区(永久代、元空间)、运行时常量池(字符串常量池)、直接内存
a.堆:
占用区域最大(占地盘的王者)
垃圾回收的主要内存区域(垃圾收集器的主要治理对象)
JDK各版本不断迭代区域划分,只为了更好更快地回收管理此区域
各线程公用空间(对比虚拟机栈)
b.虚拟机栈:
线程私有空间(从线程出生到死亡一直相伴)——生命周期相同
其大小决定了方法调用深度,报错的时候打的e.printstack就能看到层层调用。
StackOverFlow——标识栈内存设置小了,或者死循环了
那虚拟机栈里面在入栈出栈存的是什么东西呢——栈帧(
栈帧:1.调用新方法,栈帧随之创建(方法在虚拟机帧中出入的通关文牒)
2.存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等(方法执行时所有的必备信息)
3.当前栈帧(当前方法——当前类)(执行引擎的操作对象)
c.本地方法栈:
与虚拟机栈的唯一区别就是服务对象不同
服务对象:虚拟机栈————为java服务(字节码服务)做服务
本地方法栈————为native方法(比如c++方法)做服务
d.方法区
方法区是一个概念区,其具体实现有:永久代,元空间。(类比于降温这个概念的具体实现有:吹风扇和吹空调)
方法区要存储:1.class 2.运行时常量池 3.JIT编译器编译之后的代码缓存
| 永久代 | 元空间 | 比较 |
|---|
| 应用时间 | JDK1.8以前 | JDK1.8以后 | 元空间逐步替代永久代 |
| 存储区域 | 存储区域是JVM内存所占用区域 | 物理内存区域 | 占用jvm内存管理空间就得让jvm操心内存移除和垃圾回收的事情 |
| 存储内容 | 方法区需要存的都存 | 只存类的元信息,静态变量和运行时常量池交给堆来存 | |
e.运行时常量池(字符串常量池)
| class常量池 | 运行时常量池 | 字符串常量池 |
|---|
| 范围情况 | 一个class文件只有一个class常量池 | 一个class对象有一个运行时常量池 | 全局只有一个字符串常量池 |
| 存储内容 | 字面量和符号引用 | 字面量和符号引用 | 双引号引起来的字符串 |
一个叫dog的class文件有一个自己的**class常量池**。
如果实例化了两个对象:dog1和dog2,则dog1和dog2各有自己的**运行时常量池**;
但是不管有多少个对象和文件,全局也是只有一个**字符串常量池**。
f.程序计数器
从别的线程切换回来,程序计数器告诉你上回执行到哪了。(指路小天才)
g.直接内存
| 直接内存 | 堆内存 |
|---|
| 申请空间耗费性能 | 耗费更高的性能 | 性能耗费更低 |
| IO读写性能 | 更优 | 对比直接内存差一些 |
| 使用场景 | 有很大的数据需要存储(生命周期长);频繁的IO操作(网络并发场景) | 数据不是很大 |
01.为什么堆内存要分年轻代和老年代?
就像是收拾街道的清洁工师傅,会把所负责的区域分成两块;
一块区域(年轻代)经常清理,然后把瓶子和纸盒收起来放到另一块区域(老年代);
然后年轻代经常清理,老年代隔一段时间清理一次(把瓶子和纸盒卖了)。
题目 02 描述一个 Java 对象的生命周期
解释一个对象的创建过程
new指令->>想准备买一口新锅,来专门来炒肉吃
常量池检查->>看看自己家里是不是买过这样的锅
是否已加载->>不过是新锅还是旧锅都要拿到厨放这个空间来(已经拿过来就不用拿了)
分配内存空间->>确保厨放有空间能放下这口锅
内存空间初始化为零值->>把灶台清理出来准备放锅
必要信息设置->>在锅盖上标记一下这个锅的用途
执行init方法->>对这个锅进行涂油开锅
解释一个对象的内存分配
就像是一个人(对象),来到旅店,要住宿,住进一个房间(内存分配)。
A旅店(Serial,ParNew)的房间是从头到尾进行分配的(内存空间连续),接着上个旅客入住的房子安排(指针碰撞)。
B旅店(CMS收集器和Mark-Sweep收集器)分配房间内存不连续,会维持一个空闲列表,来从中给旅客来分配空间(空闲列表)
解释一个对象的销毁过程
类比于黑帮清理门户,如果能和帮派中的常驻长老(类静态属性引用的对象)建立引用联系的(能为他们所用的),就不能当做清理对象,否则会被当做清理对象。当然在清理之前,会垃圾一个机会,如果垃圾能通过调用finlize方法,亮出免死腰牌证明自己是长老的人,则可以免除一死。否则第二次机会,还是被标记为垃圾,必死无疑。
Step1:先确定垃圾
一个对象,如果对于其他人没有用处(引用法)会被认定为垃圾。
一个对象,如果没有在Gcroots对象对象的可触达链路内,则会被认定为垃圾。
可做GcRoots的对象:
1.虚拟机栈中,栈帧的本地变量表引用的对象
2.方法区中,类静态属性引用的对象
3.方法区中,常量引用的对象
4.本地方法栈中,JNI引用的对象
Step2:给垃圾机会
第一次被标记为垃圾之后,如果此对象有必要执行finalize方法.
在执行finalize方法后,还没有与引用链建立联系的会被第二次标记
Step3:第二次被标记的对象就是真的要回收
对象的 2 种访问方式是什么?
类似于作坊(JAVA栈,本地变量表)通过中介找劳工
通过句柄访问对象:
劳工的类型信息和具体劳工的地址信息由中介(句柄)来做保存。不管劳工怎么移动,作坊只要寻找句柄就可以了。
通过直接指针访问对象:
类似于作坊(JAVA栈,本地变量表)自己维护劳工的地址信息,劳工维护自己的类型信息。劳工移动,作坊的引用信息也要做更新。
为什么需要内存担保?
当年轻代通过自身的minoreGc,移动survivor区域都无法有足够空间来放入要加入的新对象时。
老年代会挺身而出,收留那希望在gc之后还存活的年轻代对象,以留出足够大的年轻代空间来收留新的对象。
垃圾收集算法有哪些?垃圾收集器有哪些?他们的特点是什么?
| 新生代使用算法 | 老年代使用算法 | 特点 |
|---|
| ParNew 收集器 | 并行复制算法,暂停所有用户线程 | 串行标记整理算法,暂停所有用户线程 | 存在线程交互开销(单cpu性能不如Serial) |
| ParallelScavenge 收集器 | 并行复制算法,暂停所有用户线程 | 串行标记整理算法,暂停所有用户线程 | 吞吐量优先 |
| ParallelOld 收集器 | 并行复制算法,暂停所有用户线程 | 并行标记整理算法,暂停所有用户线程 | ParallelScavenge的老年代版本 |
| CMS 收集器 | 无(CMS是专门的老年代收集器) | 并发回收,标记清楚 | 低延迟,会产生内存碎片,对CPU资源敏感 |
| G1收集器 | 取消老年代年轻代的物理划分 | 取消老年代年轻代的物理划分 | 空间整合:标记整理,局部使用复制算法,但不会产生内存碎片。可预测的停顿 |