1.堆空间分代划分
堆被划分为新生代和老年代(Tenured),新生代又被进一步划分为 Eden 和 Survivor 区,最后 Survivor 由 From Survivor 和 To Survivor 组成。
2.GC 概念
GC- Garbage Collection 垃圾回收,在 JVM 中是自动化的垃圾回收机制,我们一般不用去关注,在 JVM 中 GC 的重要区域是堆空间。 我们也可以通过一些额外方式主动发起它,比如 System.gc(),主动发起。(项目中不需要主动调用,只是在这里分析使用)
3.JHSDB 工具
JHSDB 是一款基于服务性代理实现的进程外调试工具。服务性代理是 HotSpot 虚拟机中一组用于映射 Java 虚拟机运行信息的,主要基于 Java 语言实现的 API 集合。
JDK1.8 的开启方式
开启 HSDB 工具: Jdk1.8 启动 JHSDB 的时候必须将 sawindbg.dll(一般会在 JDK 的目录下)复制到对应目录的 jre 下(注意在 win 上安装了 JDK1.8 后往往同级目录下有一个 jre 的目录)
复制后
然后到目录:C:\Program Files\Java\jdk1.8.0_101\lib 进入命令行,执行 java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
C:\Program Files\Java\jdk1.8.0_191\lib>java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
对话窗出来了
JDK1.9 及以后的开启方式
进入 JDK 的 bin 目录下,我们可以在命令行中使用 jhsdb hsdb 来启动它
4.代码改造
VM 参数加入:
-XX:+UseConcMarkSweepGC
-XX:-UseCompressedOops
加入以上参数,指定垃圾回收器
-Xms30m -Xmx30m -Xss1m -XX:MaxMetaspaceSize=30m -XX:+UseConcMarkSweepGC -XX:-UseCompressedOops
/**
* @author 公众号:IT三明治
* @date 2021/3/7
*
* -Xms30m -Xmx30m -Xss1m -XX:MaxMetaspaceSize=30m -XX:+UseConcMarkSweepGC -XX:-UseCompressedOops
*/
public class JVMObject {
public final static String MAN_TYPE = "man";
public final static String WOMAN_TYPE = "woman";
public static void main(String[] args) throws InterruptedException {
Teacher T1 = new Teacher();
T1.setName("Sandwich");
T1.setSexType(MAN_TYPE);
T1.setAge(20);
//进行15次垃圾回收
for (int i = 0; i< 15; i++) {
//主动发起GC
System.gc();
}
Teacher T2 = new Teacher();
T2.setName("Jack");
T2.setSexType(MAN_TYPE);
T2.setAge(18);
Thread.sleep(Integer.MAX_VALUE);
}
}
实例代码启动
因为JVM启动有一个进程,需要一个命令jps查找到对应程序的进程
在JHSDB工具中attach上去
5.JHSDB 中查看对象
查看堆参数:
上图中可以看到实际 JVM 启动过程中堆中参数的对照,可以看到,在不启动内存压缩的情况下。堆空间里面的分代划分都是连续的。
再来查看对象:
这里可以看到 JVM 中所有的对象,都是基于 class 的对象
全路径名搜索
双击出现这个Teacher类的对象,两个,就是T1和T2对象
Heap Parameters:
Gen 0: eden [0x0000000012e00000,0x0000000012e51f18,0x0000000013600000) space capacity = 8388608, 4.001140594482422 used
from [0x0000000013600000,0x0000000013600000,0x0000000013700000) space capacity = 1048576, 0.0 used
to [0x0000000013700000,0x0000000013700000,0x0000000013800000) space capacity = 1048576, 0.0 usedInvocations: 0
Gen 1: concurrent mark-sweep generation
free-list-space[ 0x0000000013800000 , 0x0000000014c00000 ) space capacity = 20971520 used(4%)= 856992 free= 20114528
Invocations: 15
对比以上堆中分代划分
T1的内存地址0x00000000138d1330 在free-list-space[ 0x0000000013800000 , 0x0000000014c00000 )范围内属于老年代
T2的内存地址0x0000000012e00000 在eden [0x0000000012e00000,0x0000000012e51f18,0x0000000013600000)范围内,属于eden
6.JHSDB 中查看栈
从上图中可以验证栈内存,同时也可以验证到虚拟机栈和本地方法栈在 Hotspot 中是合二为一的实现了。
确认Thread.sleep()是本地方法
当我们通过 Java 运行以上代码时,JVM 的整个处理过程如下:
- JVM 向操作系统申请内存,JVM 第一步就是通过配置参数或者默认配置参数向操作系统申请内存空间。
- JVM 获得内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小。
- 完成上一个步骤后, JVM 首先会执行构造器,编译器会在.java 文件被编译成.class 文件时,收集所有类的初始化代码,包括静态变量赋值语句、 静态代码块、静态方法,静态变量和常量放入方法区
- 执行方法。启动 main 线程,执行 main 方法,开始执行第一行代码。此时堆内存中会创建一个 Teacher 对象,对象引用 T 就存放在栈中。 执行其他方法时,具体的操作:栈帧执行对内存区域的影响。栈帧执行对内存区域的影响
7.从底层深入理解运行时数据区(总结)
深入辨析堆和栈
功能
⇒
\Rightarrow
⇒以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char 等)以 及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放;
⇒
\Rightarrow
⇒而堆内存用来存储 Java 中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中;
线程独享还是共享
⇒
\Rightarrow
⇒栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。
⇒
\Rightarrow
⇒堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
空间大小
栈的内存要远远小于堆内存