java 对象头

在 Java 虚拟机中,每个 Java 对象都有一个对象头(object header),这个由标记字段和类型指针所构成。其中,标记字段用以存储 Java 虚拟机有关该对象的运行数据,如哈希码、GC 信息以及锁信息,而类型指针则指向该对象的类。

在 64 位的 Java 虚拟机中,对象头的标记字段占 64 位,而类型指针又占了 64 位。也就是说,每一个 Java 对象在内存中的额外开销就是 16 个字节。以 Integer 类为例,它仅有一个 int 类型的私有字段,占 4 个字节。因此,每一个 Integer 对象的额外内存开销至少是 400%。这也是为什么 Java 要引入基本类型的原因之一

如果虚拟机开启了压缩指针, 可以把类型指针 从 8 个字节压缩到 4 个字节, 从而对象头总共占用 12 字节

Java 虚拟机会让不同的 @Contended 字段处于独立的缓存行中,因此你会看到大量的空间被浪费掉。

link

GC

那么什么是 GC Roots 呢?我们可以暂时理解为由堆外指向堆内的引用,一般而言,GC Roots 包括(但不限于)如下几种:

  • Java 方法栈桢中的局部变量;
  • 已加载类的静态变量;
  • JNI handles;
  • 已启动且未停止的 Java 线程

jvm 链接

G1垃圾回收器

卡表和Rset

Java中继承类的初始化顺序可以总结为以下几点:

父类静态代码块的初始化。父类的静态代码块在类被加载时执行,且仅执行一次。

子类静态代码块的初始化。子类的静态代码块在类被加载时执行,且仅执行一次。

父类实例变量的初始化。在父类构造函数被调用之前,父类的实例变量被初始化。

父类构造函数的初始化。在父类实例变量被初始化之后,父类的构造函数被调用。

子类实例变量的初始化。在子类构造函数被调用之前,子类的实例变量被初始化。

子类构造函数的初始化。在子类实例变量被初始化之后,子类的构造函数被调用。

需要注意的是,当创建子类对象时,以上顺序逐步执行,以确保父类的初始化顺序在子类之前完成。同时,如果子类没有显示地调用父类构造函数,则默认调用父类的无参构造函数。

自己模拟虚拟机溢出场景

https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Java%20%E8%99%9A%E6%8B%9F%E6%9C%BA-%E5%AE%8C/11%20%E7%AC%AC10%E8%AE%B2%EF%BC%9A%E5%8A%A8%E6%89%8B%E5%AE%9E%E8%B7%B5%EF%BC%9A%E8%87%AA%E5%B7%B1%E6%A8%A1%E6%8B%9F%20JVM%20%E5%86%85%E5%AD%98%E6%BA%A2%E5%87%BA%E5%9C%BA%E6%99%AF.md

jvm 工具

  • gceasy
  • MAT
  • GCViewer
  • GCViewer
1
2
3
4
5
6
7
8
9
grep -n real gc.log | awk -F"=| " '{ if($8>0.1){ print }}'

jstat -gcutil $pid 1000

iostat -x 1

jstat -gcutil -t 90542 1000 | awk 'BEGIN{pre=0}{if(NR>1) {print $0 "\t" ($12-pre) "\t" $12*100/$1 ; pre=$12 } else { print $0 "\tGCT_INC\tRate"} }' 

ps -p 75 -o rss,vsz

查看进程的内存分布情况

1
pmap -x 2154  | sort -n -k3

垃圾收集器

Shenandoah 和 ZGC

双亲委派机制及其打破情况

tomcat / spi

jmm 和 jvm