JVM内存管理
JVM是一种规范。JVM的跨平台性。JVM的语言无关性。
常见的JVM实现:Hotspot(Oracle公司)、Zing(C4垃圾回收算法)、毕昇(华为公司)
JVM运行时数据区域
运行时数据区域分为线程共享区和线程私有区
线程共享区包含:方法区、堆。运行时常量池在方法区中
线程私有区包含:虚拟机栈、本地方法栈、程序计数器
方法区(存储类加载Class类、静态常量(基本数据类型)、常量(基本数据类型)、static静态代码块、运行时常量池)
成员变量指向(对象)在类加载的时候不会执行,运行时成员变量会存储在堆中,
堆(存储对象实例)
栈(存储局部变量、引用对象)先进后出
运行时数据区外还存在一个直接内存,又称为堆外内存。
虚拟机栈 (先进后出)
虚拟机栈存放的方法的栈帧
虚拟机栈存储当前线程运行java方法所需的数据、指令、返回地址
-Xss 256K 限制大小
栈溢出一般是出现了死递归
栈帧
栈帧包含:局部变量表、操作数栈(不会被回收,会被复用)、动态连接、完成出口(返回地址)
程序计数器
程序计数器记录的是字节码运行的行号,记录的是偏移量
直接内存
Java中直接内存(堆外内存)有哪些:使用Unsafe操作本地内存、JNI或者JNA程序操作了本地内存
//直接分配128的直接内存
ByteBuffer buffer=ByteBuffer.allocateDiret(128*1024*1024);
直接内存的申请绕过了JVM的垃圾回收。好处是速度会稍微快点。缺点就是容易忘记回收造成内存泄漏。多线程申请内存会造成覆盖。
JVM内存处理全流程
- JVM申请内存
- 初始化运行数据区
- 类加载 会把类,静态变量和常量以及加了static的东西放入方法区
- 执行方法 运行方法创建栈,创建虚拟机栈。执行main方法的栈帧
- 创建对象
JVM编译:
-
.java文件经过javac指令编译成.class文件
-
类加载把.class文件加载到方法区交由JVM执行引擎去执行
-
jvm把字节码翻译成机器码
堆空间分代划分
分为老生代、新生代(Eden、From、To)。老生代存放的是经过多次GC垃圾回收没有回收掉的对象。
新生代经过15次GC后会晋升为老生代。对象经历一次垃圾回收,没有被回收掉。age+1。age达到15 晋级老生代。底层记录对象的年龄是用4位二进制,最大值位1111也就是15。
JHSDB工具查看内存。
JVM哪些区域会发生内存溢出:堆、虚拟机栈、方法区
对象的分配
JVM中对象的创建过程
类加载----》
检查加载(JVM遇到一条字节码new指令)-----》
分配内存(划分内存的方式:指针碰撞、空闲列表。解决并发安全:CAS加失败重试、本地线程分配缓冲)----》
内存空间初始化(“零”值)—》
设置(对象头)----》
对象初始化(构造方法)
使用指针碰撞内存必须是规整的。如果内存不规整,或者存在内存碎片就需要使用空闲列表方式。
CAS:是CPU的一个指令。多线程使用时使用CAS比较和交换。
本地线程分配缓冲:预先给每个线程划分一块内存区域(java8以后使用此种方式,禁止以后使用的就是CAS的方式)
对象分为:
1、对象头(Mark Word存储对象自身的运行时数据(哈希码、GC分代年龄、锁状态标识、线程持有的锁、偏向线程ID、偏向时间戳)、类型指针、若为对象数组,还应有记录数组长度的数据)
2、实例数据
3、对象填充(非必须)
对象的访问定位:使用句柄或者直接指针的方式。目前主流的JVM使用的是直接指针方式
垃圾回收
垃圾回收GC,需要判断对象的存货
判断对象的存活 :
-
引用计数算法 没法解决循环引用的关系
-
可达性分析(根可达)
-
经过判断对象存活后还会有一个Finalize 缓冲期。Finalize是类的自我拯救。因为是另起了一个线程,所有优先级比较低 ,不推荐使用。
GC roots:
从根找可达 根有 静态变量----》线程栈变量-----》常量池----》JNI(指针)—》内部引用(class对象、异常对象Exception。类加载器)—》同步锁----》内部对象----》临时对象(跨代引用)
class对象要回收的条件。比较苛刻,要满足所有条件:
-
class new 出的所有对象都要回收
-
对应的类加载器 也要回收
-
类 Java.lang.class对象,
-
任何地方没有引用,并且无法通过反射调用这个类的方法
-
参数控制
各种引用
- 强引用 = 不会被回收
- 软引用 SoftReference 发生垃圾回收,可能会回收
- 弱引用 WeakReference 只要垃圾回收,就会被回收
- 虚引用 PhantomReference 随时都会被垃圾回收回收
对象的分配策略
几乎所有的对象都在堆中进行分配,对象触发逃逸分析会把对象会分配到栈中(java 10000次)
对象的分配原则
-
对象优先在Eden分配 (常用)
-
空间分配担保
-
大对象直接进入老年代
-
长期存活的对象进入老年代 (常用)
-
动态对象年龄判定
虚拟机的优化技术
-
逃逸分析+触发JIT(热点数据) 对象会分配到栈中
-
本地线程分配缓冲
垃圾回收算法
- 复制算法 加强版(Appel式的复制回收算法)用于新生代垃圾回收
新生代内存分配规则:Eden:form:to=8:1:1
特点:实现简单、运行高效。没有内存碎片。空间利用率只有一半
- 标记-清除算法 用于老生代
根据可达性分析标记引用的对象,然后清除没有引用的对象
特点:位置不连续,产生碎片。可以不暂停。
- 标记-整理算法
根据可达性分析标记引用的对象,先标记,后整理内存空间。 最后整体清理
特点:没有内存碎片。指针需要移动。
垃圾回收器
- 单线程垃圾回收器
- 多线程并行垃圾回收器
- 并发垃圾垃圾回收器
举例常见的垃圾回收器
-
Serial 回收新生代 复制算法 回收器类型属于单线程
-
Serical Old 回收老年代 标记整理算法 回收器类型属于单线程
-
Paraller Scavenge 回收新生代 复制算法 回收器类型属于多线程并行垃圾回收器
-
Paraller Old 回收老年代 标记整理算法 回收器类型属于多线程并行垃圾回收器
-
PaeNew 回收新生代 复制算法 回收器类型属于多线程并行垃圾回收器
-
CMS 回收老年代 标记清除算法 回收器类型属于并发的多线程回收器
-
G1 跨新生代和老年代 标记整理+化整为零算法 回收器类型属于并发的多线程回收器
CMS的标记清除算法的过程减少了暂停卡顿:初始标记 并发标记 重新标记 并发清除
CMS中的问题:CPU敏感 浮动垃圾 内存碎片
面试题
常量池(方法区):
-
Class常量池(静态常量池)
-
运行时常量池
-
字符串常量池