深入理解运行时数据区
先看如下代码
package com.nl;
public class JVMObj {
public static final String WHITE_TYPE = "WHITE";//常量
public static String BLACK_TYPE = "BLACK";//静态变量
public static void main(String[] args) throws InterruptedException {
Car car1 = new Car();
car1.setName("xiaoyao");
car1.setColorType(WHITE_TYPE);
car1.setMoney(100000000);
Car car2 = new Car();
Car car2 = new Car();
car2.setName("xiaonie");
car2.setColorType(BLACK_TYPE);
car2.setMoney(1);
Thread.sleep(Integer.MAX_VALUE);
}
static class Car {
String colorType;
String name;
int money;
public String getColorType() {
return colorType;
}
public void setColorType(String colorType) {
this.colorType = colorType;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
}
1.从代码上面分析JVM 向操作系统申请内存:
JVM 第一步就是通过配置参数或者默认配置参数向操作系统申请内存空间,根据内存大小找到具体的内存分配表,然后把内存段的起始地址和终止地 址分配给 JVM,接下来 JVM 就进行内部分配。
2…JVM 获得内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小-Xms30m -Xmx30m -Xss1m -XX:MaxMetaspaceSize=30m
3. 类加载:
这里主要是把 class 放入方法区、还有 class 中的静态变量和常量也要放入方法区
4.执行方法及创建对象:
启动 main 线程,执行 main 方法,开始执行第一行代码。此时堆内存中会创建一个 student 对象,对象引用 Car就存放在栈中。 后续代码中遇到 new 关键字,会再创建一个 Car对象,对象引用 Car就存放在栈中。
JVM 在操作系统上启动,申请内存,先进行运行时数据区的初始化,然后把类加载到方法区,最后执行方法。 方法的执行和退出过程在内存上的体现上就是虚拟机栈中栈帧的入栈和出栈。 同时在方法的执行过程中创建的对象一般情况下都是放在堆中,最后堆中的对象也是需要进行垃圾回收清理的。
堆空间分代划分
GC 概念
GC- Garbage Collection 垃圾回收,在 JVM 中是自动化的垃圾回收机制,我们一般不用去关注,在 JVM 中 GC 的重要区域是堆空间。 我们也可以通过一些额外方式主动发起它,比如 System.gc(),主动发起。
JHSDB 工具
给大家推荐一个工具JHSDB是一款基于服务性代理实现的进程外调试工具。
现在改造下代码:
package com.nl;
public class JVMObj {
public static final String WHITE_TYPE = "WHITE";//常量
public static String BLACK_TYPE = "BLACK";//静态变量
public static void main(String[] args) throws InterruptedException {
Car car1 = new Car();
car1.setName("xiaoyao");
car1.setColorType(WHITE_TYPE);
car1.setMoney(100000000);
for (int i = 0; i < 30; i++) {
System.gc();//进行三十次辣鸡回收
}
Car car2 = new Car();
car2.setName("xiaonie");
car2.setColorType(WHITE_TYPE);
car2.setMoney(1);
Thread.sleep(Integer.MAX_VALUE);
}
static class Car {
String colorType;
String name;
int money;
public String getColorType() {
return colorType;
}
public void setColorType(String colorType) {
this.colorType = colorType;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
}
通过jps命令就能查到当前java的一个进程。
然后打开工具,输入ID
查看堆的参数
上图中可以看到实际 JVM 启动过程中堆中参数的对照,可以看到,在不启动内存压缩的情况下。堆空间里面的分代划分都是连续的。
再来查看对象:
双击出现这个 Car类的对象,两个,就是 car1和 car2 对象。
最后再对比一下堆中分代划分可以得出为什么 car1 在 Eden,car2 在老年代
JHSDB 中查看栈
当我们通过 Java 运行以上代码时,JVM 的整个处理过程如下:
- JVM 向操作系统申请内存,JVM 第一步就是通过配置参数或者默认配置参数向操作系统申请内存空间。
- JVM 获得内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小。
- 完成上一个步骤后, JVM 首先会执行构造器,编译器会在.java 文件被编译成.class 文件时,收集所有类的初始化代码,包括静态变量赋值语句、 静态代码块、静态方法,静态变量和常量放入方法区
- 执行方法。启动 main 线程,执行 main 方法,开始执行第一行代码。此时堆内存中会创建一个 Car 对象,对象引用 car 就存放在栈中。 执行其他方法时,具体的操作:栈帧执行对内存区域的影响。
使用 JHSDB 工具查看栈空间一样可以看到。后续这个篇章也会继续分享。