@(Java)
- new过程:JVM遇到一条字节码new指令时,检查指令的参数是否能在常量池中定位到一个类的符号引用,检查符号引用代表的类是否已经被加载、解析和初始化过。如果没有,必须先执行类加载。
- 类加载检查通过后,JVM为新生对象分配内存。所需内存的大小在类加载完成后便可完全确定。(堆内存规整:使用“指针碰撞”,不规整则“空闲列表”)
对象创建保证原子性:CAS失败重试、TLAB本地线程分配缓冲(每个线程在Java堆中预先分配一小块内存)
如何判断对象已死?
- 引用计数法:在对象中添加一个引用计数器,每当有一个地方引用,计数器就+1;当引用失效时,计数器值就-1;计数器为0时,对象不可能再被使用。
- 可达性分析算法:通过“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,从而得出引用链,如果某个对象到“GC Roots”间没有任何引用链项链,或者说从GC Roots到这个对象不可达,证明对象不可能再被使用。
GC Roots:虚拟机栈(栈帧中的本地变量表)中引用的对象;方法区中类静态属性引用的对象;本地方法栈中JNI引用的对象;Java虚拟机内部的引用,Class对象、异常对象;所有Synchronized关键字持有的对象;JMXBean、JVMTI中注册的回调、本地代码缓存。
四种引用类型
- 强引用:Object obj = new Object(),垃圾回收器不会回收。
- 软引用:系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,回收后还没有足够内存,才会抛出OOM。
- 弱引用:弱引用关联的对象只能生存到下一次GC发生未知。
- 虚引用:无法通过虚引用取得对象实例,只是在对象回收时收到一个系统通知。
对象被“判死刑”的条件
- 1、可达性分析算法判定为不可达的对象,2、对象是否有必要执行
finalize()
方法。
“没必要执行”:对象没有重写
finalize()
,或者finalize()
已经被虚拟机调用过(只会被调用一次)。
- 优先级很低的
finalizer
线程执行finalize()
方法,避免由于某个对象的finalize()
方法执行缓慢,导致死循环,导致整个内存回收子系统的崩溃。
GC的种类
- Minor GC/ Young GC:指目标只是新生代的垃圾收集。
- Major GC/ Old GC:指目标只是老年代的垃圾收集。(只有CMS收集器)
- Mixed GC:只目标是收集整个新生代以及部分老年代的垃圾收集。(只有G1)
- Full GC:收集真个Java堆和方法区的垃圾收集。
垃圾收集算法
- 标记-清除
- 标记-复制
- 标记-整理
OopMap的作用
- HotSpot中使用:根节点分析,需要“Stop The World”,用户线程停顿下来之后,其实并不需要一个不漏地检查完所有执行上下文和全局的引用位置,OopMap用来存放栈里和寄存器里哪些位置是引用。
安全点、安全区域
-
引用关系变化导致OopMap内容变化指令非常多,需要大量的额外存储空间。(特定的位置记录OopMap,即安全点)
-
垃圾收集发生时如何让所有的线程都跑到最近的安全点
1、抢占式终端(几乎没有JVM实现):直接操纵线程 2、主动式终端:线程轮询标志位,到达安全点中断
记忆集
-
一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。
-
跨代引用:进行一次Minor GC,新生代的对象呗老年代引用,找出该区域中的存活对象,不得不在固定的GC Roots之外,再额外遍历整个老年代中所有对象来确保可达性分析的正确性。(极个别现象)
-
新生代上建立一个“记忆集”,把老年代划分成若干小块,标识出老年代的哪一块内存存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会加入到GC Roots进行扫描。
-
每个记录精确到一块内存区域,该区域内有对象含有跨代指针。(卡精度,“卡表”)
-
写屏障可以看做在虚拟机层面对“引用类型字段赋值”的AOP切面。应用写屏障后,虚拟机就会为所有赋值操作生成的相应指令。
-
“伪共享”:中央处理器的缓存系统中是以缓存行(Cache Line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量恰好共享同一个缓存行,就会彼此影响而导致性能降低。
解决方案:先检查卡表标记,只有当该卡表元素未被标记过时才将其标记为变脏。(+UseCondCardMark)