1、详解虚拟机栈
栈帧内部结构:
局部变量表:用于存放我们的局部变量(方法中的变量)。首先它是 一个32位的 长度,主要存放我们的java的八大基础数据类型,一般32位就可以存放下。如果是64位的就使用高低位占用俩个也可以存放下,如果局部是一些对象,比如我们的object对象,我们只需要存放他的一个引用地址即可。
操作数栈:存放java方法执行的操作数,他就是一个栈,先进后出的站结构,操作数栈就是用来操作的。操作的元素可以是任意的java数据类型,所以我们知道方法开始的时候,操作数栈是空的。
动态连接:java语言特性多态。
返回地址(完成出口):正常返回(调用程序计数器中的地址作为返回)、异常 返回的话(通过异常处理器表来确定)
/**
* 栈帧执行对内存区域的影响
*/
public class Person {
public int work(){
int x = 1;
int y = 2;
int z = (x+y) * 10;
return z;
}
public static void main(String[] args){
//person 的字符串引用地址放在栈中的局部变量表中(对象引用), new person 对象的具体实例数据是在堆中(对象实例)
Person person = new Person();
System.out.println(person.work());
System.out.println(person.hashCode());
}
}
执行流程:线程运行java方法、main方法执行、work方法执行、栈帧入栈、字节码的执行细节、work方法执行完、栈帧出栈
对代码进行反编译
指令集详解链接:https://cloud.tencent.com/developer/article/1333540
反汇编代码如下:
Compiled from “Person.java”
public class ex1.Person {
public ex1.Person();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object.""😦)V
4: return
public int work();
Code:
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 10
9: imul
10: istore_3
11: iload_3
12: ireturn
public static void main(java.lang.String[]);
Code:
0: new #2 // class ex1/Person
3: dup
4: invokespecial #3 // Method “”😦)V
7: astore_1
8: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_1
12: invokevirtual #5 // Method work:()I
15: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
18: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_1
22: invokevirtual #7 // Method java/lang/Object.hashCode:()I
25: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
28: return
}
代码解释:
0: iconst_1
1: istore_1 // int x = 1;
1、将常量1从常量池中取出,加载到操作数栈(类似于计算机运算器中数据寄存器)
2、将操作数栈中的数据存储到局部变量表地址为1的地方
3、 2: iconst_2
3: istore_2 //int y =2的执行步骤和第一步一样,先将y=2从常量池中取出,加载到操作数栈,然后在存储在局部变量表中
4、 4: iload_1 //取出局部变量表中地址为1的数据,并压入操作数栈
5: iload_2 //取出局部变量表中地址为2的数据,并压入操作数栈
6: iadd //将操作数栈中的数据取出进入执行引擎(类似于运算器)做加法运算 X+Y,并将计算结果保存在操作数栈
7: bipush 10 //将常量10压入操作数栈
9: imul //乘法运算 (x+y) * 10;将数据出栈进入执行引擎计算,并将30返回操作数栈
10: istore_3 //将计算结果30保存至局部变量表
11: iload_3 // int z = (x+y) * 10; 将局部变量表中地址为30的数据加载到操作数栈
12:ireturn //返回操作数栈的结果进入执行引擎进行下一个(栈帧)方法的运算
程序计数器
类似于运算器中pc(Programma Count)程序计数器:记录指令执行的步骤地址。
本地方法栈
和虚拟机栈执行过程一样,运行的是本地方法代码。
2、详解JVM内存区域
/**
* VM参数
* -Xms30m -Xmx30m 堆最小30兆最大30兆
* -XX:MaxMetaspaceSize=30m 方法区最大值
* -XX:+UseConcMarkSweepGc 使用cmgc回收方式
* -XX:-UseCompressedOops 禁止内存信息压缩,便于查看具体的信息
*/
public class JVMObject {
public final static String MAN_TYPE = "man";//常量
public static String WOMAN_TYPE = "woman";//静态变量
public static void main(String[] args) throws Exception {
Teacher t1 = new Teacher();
t1.setName("zhangsan");
t1.setSexType(MAN_TYPE);
t1.setAge(20);
for(int i =0 ;i<15 ;i++){
System.gc();//主动触发GC 垃圾回收 15次--- T1存活
}
Teacher T2 = new Teacher();
T2.setName("lisi");
T2.setSexType(MAN_TYPE);
T2.setAge(18);
Thread.sleep(Integer.MAX_VALUE);//线程休眠
}
}
class Teacher {
String name;
String sexType;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSexType() {
return sexType;
}
public void setSexType(String sexType) {
this.sexType = sexType;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
代码运行时数据区步骤:
第一步:JVM申请内存
JVM第一步就是通过配置参数或者默认参数配置向操作系统申请物理内存空间,根据内存大小找到具体的内存分配表,然后把物理内存段的起始地址和终止地址分配给JVM,接下来JVM就会进行内存分配。
第二步:初始化运行时数据区
JVM获得内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小。
第三步:类加载
先加载JVMObject.class、Teacher.class类文件,然后加载MAV_TYPE、WOMAN_TYPE变量和常量池放入方法区
第四步:执行方法、创建对象
启动main线程,创建虚拟机栈。执行main方法,压入main栈帧,创建teacher对象,并把对象的应用放入栈帧中的局部变量表,把对象的实例数据放入堆内存中。
第五步:对象数据回收
栈内存随着方法的执行,自动回收;堆中的对象需要垃圾回收器进行专门的回收
3、JHSDB工具查看对象、栈
JHSDB工具使用
JHSDB是一款基于服务型代理实现的进程外调试工具。服务性代理是HotSpot虚拟机中一组用于映射Java虚拟机运行信息的,主要基于java语言实现的API集合。
JDK1.8的开启方式
开启HSDB工具
Jdk1.8启动JHSDB的时候必须将sawindbg.dll复制到对应目录的jre下
然后进入目录进度款jdk1.8.0_102\lib下,执行java –cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
JDK1.9及以后的开启方式
进入jdk的bin目录下,我们可以在命令行中使用jhsdb hsdb来启动它
因为JVM启动有一个进程,需要借助一个命令jps查找到对应的程序的进程
查看堆
1、查看堆参数
上面中可以看到实际JVM启动过程中堆中参数的对照,在不启动内存压缩的情况下,堆空间的里面的分代划分都是连续的。
堆地址分析:
Eden地址:1300000—13800000
From地址:13800000 --13900000
To地址:13900000 – 13a0000
Tenured、old地址:13a00000—14e00000
2、查看对象
这里可以看到JVM中的所有对象,都是基于class的对象
双击出现这个Teacher类的对象,就是T1和T2对象
T2对象lisi:
T2对象1300000 在eden区
T1对象zhangsan
T1对象地址13ac1d08 在old区
查看栈
先执行虚拟机本地方法栈,然后执行虚拟机栈
4、堆和栈的区别
功能上:
以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char等)以及对象的引用变量。其内存分配在栈上。变量出了作用域就会自动释放,随着线程一起生死。
而堆内存用来存储java中的对象。无论是成员变量、局部变量、还是类变量,他们指向的对象都存储在堆内存中。
线程独享还是共享:
栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在器所属线程中可见,及栈内存可以理解成线程的私有内存。
堆内存中的对象是所有线程都可见的。堆内存中的对象可以被所有线程访问。
空间大小:
栈的内存要远小于堆内存。垃圾回收器回收的主要是堆内存空间,内存优化也主要就是优化的堆内存空间。