JVM初探

2020/10/21 posted in  JVM

JVM运行时数据区

  • 程序计数器

    • 是一块很小的内存空间,可以看做是当前线程执行的行号指示器。字节码解释器工作时就是通过改变这个计数器的数值来选取下一条需要执行的字节码指令
    • 线程私有
    • 不会发生OOM
  • Java虚拟机栈

    • 线程私有
    • 为执行Java方法服务
    • 其工作原理类似于数据结构中的栈--FIFO。每个方法在执行的过程中会在虚拟机栈中创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
    • 会发生OOM、StackOverflowError
  • 本地方法栈

    • 线程私有
    • 与虚拟机栈功能类似
    • 为Native方法服务
    • 会发生OOM、StackOverflowError
  • Java堆

    • 线程共享

    • 几乎所有的对象实例都在堆中分配。

    • 逃逸分析、标量替换-->栈上分配

    • 新生代:老年代 = 1:2

    • Eden:S0:S1 = 8:1:1

  • 方法区

    • 线程共享
    • 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码
    • 运行时常量池:用于存放编译期生成的各种字面量和符号引用
    • 会出现OOM

辣鸡回收算法与辣鸡回收器

辣鸡回收需要做的3件事情

  • 哪些内存需要回收
  • 什么时候回收
  • 如何回收

上面JVM运行时数据区中线程私有变量(本地方法栈、JVM虚拟机栈、程序计数器)会随着方法执行的完成而自动释放内存,所以这部分的内存不需要考虑回收。需要考虑回收的内存主要就是堆内存与方法区,只有在程序运行的时候才知道会创建哪些对象,哪些需要回收。

--
判断对象是否存活

  1. 程序计数器

    给对象添加一个引用计数器,每当有一个地方引用它时,计数器的值就+1,当引用失效时,计数器的值-1.

    但是主流的Java虚拟机里面并没有使用这种方式,最主要的原因是它很难解决对象之间相互循环引用的问题。

  2. 可达性分析(GC Roots)

    使用最多的是这种方式。
    这种算法的思路就是,选择一个根节点,从这个节点往下搜索,搜索过程中走过的路径称为引用链,当一个对象到根节点没有引用链存在时,也就是不可达,这种对象就是辣鸡对象。
    哪些对象可以作为GC Roots

    • 虚拟机栈中引用的对象
    • 方法区中静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中引用的对象
  3. 引用的类型

    • 强引用:

      • new出来的对象都是强引用对象,在没有指定该对象=null,不会被回收。
      Object obj =new Object();  // 强引用
      

    obj = null;//这时候为垃圾回收器回收这个对象,至于什么时候回收,取决于垃圾回收器的算法

         
     - 软引用:
         - SoftReference
         - 当内存不够用的时候会回收。
         - 适合做缓存。
    
         
         ```
         String value = new String(“sy”);
    

SoftReference sfRefer = new SoftReference (value );
sfRefer .get();//可以获得引用对象值
```
- 弱引用
- WeakReference
- 只要发生GC就会回收。
- 适合加载的时候初始化用。
- ThreadLocal中有使用。

    ```
    String value = new String(“sy”);
    WeakReference weakRefer = new WeakReference(value );
    System.gc();
    weakRefer.get();//null
    ```
    
- 虚引用
    - 作用:管理堆外内存。
    - NIO中 DirectByreBuffer
  1. 对象的自我拯救-二次标记

    • 当一个对象被标记为辣鸡后并不会立马被回收掉。
    • 第一次经过可达性分析后发现该对象是辣鸡对象,会被第一次标记并且进行一次筛选,筛选的条件是此对象时候有必要执行finalize方法,
    • 如果该对象需要执行finallize方法,那么这个对象会被放置在一个F-Queue的队列中,稍后GC将会对队列中的对象信心小规模的二次标记。
    • 如果对象在执行finalize过程中,与GC Roots建立联系了,那么这个对象就不会被回收,反之就会被回收了。
  2. 回收方法区
    永久代主要回收两类对象:废弃常量和无用的类。
    废弃常量好理解,没有任何对象引用常量池中的某个变量,这个变量就是废弃常量。
    如何判断一个类是无用的类

    • 该类的所有实例都已经被回收掉
    • 加载该类的ClassLoader已经被回收
    • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

辣鸡回收算法

  1. 标记-清除算法
    当对象被标记为辣鸡后,直接清理掉。
    效率不高、容易产生内存碎片

  2. 标记-整理算法
    被标记为辣鸡的对象回收之后,整理内存空间。

  3. 复制算法
    将内存分为大小相等的两块,每次内存分配的时候只使用其中的一块,当这块内存满了只有,将存活对象复制到另一块内存空间中,然后整块清理。
    实现简单、运行高效
    但是浪费一半的内存。

  4. 分代收集算法
    目前使用这种算法。

新生代中发生GC -- minor gc

minor GC test

public class HeapTest {

	private byte[] bytes = new byte[1024*100];

	public static void main(String[] args) throws InterruptedException {
		List<HeapTest> list = new ArrayList<>();
		while (true) {
			System.out.println(111);
			list.add(new HeapTest());
			Thread.sleep(10);
		}
	}
}

minor-g
minor-g

这图是上面的代码运行时的gc情况。

从图中可以得出结论,新生成的对象都是在Eden区,当Eden区满了后,会执行gc回收,将Eden区与S0区中的存活对象拷贝到S1,并清空Eden、S0,存活对象年龄+1;
当新生对象又一次将Eden区填满后,会将Eden区、S1中的存活对象拷贝到S0,并清空S1,存活对象年龄+1;
当存活对象的年龄达到15后,会被认为是老不死对象,挪到老年代内存中。
新生代中的GC 叫做minor GC。
老年代内存占满后进行的GC就是FULL GC。
minor GC正常情况下可以回收70--95%的空间。

--
对象进入老年代的情况:

  • 长期存活的对象进入老年代
    • 对象年龄达到15后
    • markword中2字节表示
  • 内存担保机制
    • 当Eden区的内存不足以容纳新生成的对象的时候,新生成的对象会直接进入老年代
  • 动态年龄判断(大对象直接进入老年代)
    • 新生成的对象的内存大于Eden区内存的50%会直接进入老年代

JVM调优的目的:减少FULL GC

--

辣鸡收集器

  • Serial
  • ParNew
  • Parallel Scavenge
  • Serial Old
  • Parallel Old
  • CMS
  • G1

-XX:+UseParallelGC

--
TODO
JVM调优参数
JDK工具
安全点与安全区域
STW
各种垃圾收集器
逃逸分析与标量替换