Java 对象在 JVM 中的内存布局超详细解说
作者:new?Handsome() 发布时间:2023-05-19 14:10:23
一、new 对象的几种说法
初学 Java 面向对象的时候,实例化对象的说法有很多种,我老是被这些说法给弄晕。
public class Test {
public static void main(String[] args) {
// 创建一个 ProgramLanguage 对象, 对象名是 java
ProgramLanguage java = new ProgramLanguage();
// 实例化一个 ProgramLanguage 对象, 对象名是 c
ProgramLanguage c = new ProgramLanguage();
// 把 ProgramLanguage 类实例化, 实例化后的对象的对象名是 python
ProgramLanguage python = new ProgramLanguage();
}
}
class ProgramLanguage {
private Integer id;
private String name;
}
下面的三种说法的操作都是实例化对象,只是说法不一样而已
① 创建一个 xxx 对象
② 实例化一个 xxx 对象
③ 把 xxx 类实例化
二、Java 对象在内存中的存在形式
这里先简单看一看 Java 对象在内存中的存在形式和几个内存相关的概念,后面还会详细介绍的。先看下面的几个知识点:
1. 栈帧(Frame)
① 方法被调用则栈帧创建,方法执行结束则栈帧销毁
② 栈帧中存储了方法的局部变量信息
③ 栈帧是分配给方法的一段栈空间
main 方法作为程序的入口肯定是第一个被调用的方法,所以会先创建 main 方法的栈帧
在 main 方法中调用了 test1 方法,并传入【55】作为参数给 test1 方法的局部变量 v,所以第二个创建的是 test1 方法的栈帧
test1 方法中的代码很快就执行完了,所以 test1 的栈帧很快会被销毁(方法执行结束后该方法的栈帧销毁)
在 main 方法中调用了 test2 方法,并传入【88】作为参数给 test2 方法的局部变量 v,所以第三个创建的是 test2 方法的栈帧
在 test2 方法中调用了 test3 方法,并传入【666】作为参数给 test3 方法的局部变量 v,所以第四个创建的是 test3 方法的栈帧
当 test3 方法执行完毕后,test3 方法的栈帧被销毁
test3 方法的结束也正是 test2 方法的结束,所以 test2 方法的栈帧也被销毁
test2 方法的结束表示 main 方法的结束,所以 main 方法的栈帧会被销毁
2. 对象在内存中的存在形式 ①
Java 中的所有对象都是通过
new
关键字创建出来的(new 关键字:实例化一个对象;向堆空间申请一段内存,用于存放刚刚实例化的对象)所有的对象都存储在堆空间
所有保存对象的变量都是引用类型
局部变量是放在栈空间
Java 运行时环境中有个垃圾回收器(garbage collector),会自动回收没有被使用的(堆空间的)内存
当一个对象没有被任何引用指向的时候,会被 GC 回收掉内存
分析下面的代码的内存布局:
public class DogDemo {
public static void main(String[] args) {
Dog doggy = new Dog();
doggy.age = 6;
doggy.weight = 3.14;
}
}
main 方法被调用,会在栈空间创建 main 方法的栈帧,main 方法的栈帧中会存放 main 方法中的局部变量信息(包括 args 和 main 方法中对象的引用 doggy)
在 main 方法中,通过
new
关键字实例化了 Dog 对象,Dog 对象存储在堆空间堆空间中有一段内存用于存储类的对象的数据,这段内存中存放了 Dog 对象的属性信息(如 age、weight)
栈空间中的 doggy 变量代表堆空间中的对象的地址(通过地址可以访问对象)
分析下面的代码的内存布局(稍微复杂)
public class Dog {
public int price;
}
public class Person {
public int age;
public Dog dog;
}
public class Test {
public static void main(String[] args) {
Dog doggy = new Dog();
doggy.price = 255;
Person yeTing = new Person();
yeTing.age = 20;
yeTing.dog = doggy;
}
}
main 方法被调用,会在栈空间创建 main 方法的栈帧,main 方法的栈帧中会存放 main 方法中的局部变量信息(包括 args、main 方法中对象的引用 doggy、对象的引用 yeTing)
在 main 方法中,通过
new
关键字实例化了 Dog 对象,Dog 对象存储在堆空间。堆空间中有一段内存用于存储 Dog 对象的属性信息(如 price = 255)在 main 方法中,通过
new
关键字实例化了 Person 对象,Person 对象存储在堆空间。堆空间中有一段内存用于存储 Person 对象的属性信息(如 age = 20),堆空间中,Person 对象的属性 dog 是 Dog 对象的引用,所以它指向的是堆空间中的 Dog 对象(dog 指向的是栈空间中的 doggy 引用的堆空间的 Dog 对象。doggy 和 yeTing 指向的 Person 对象中的 dog 属性指向的是同一个对象)引用变量不一定是在栈空间(也可能是在堆空间,如上图中 yeTing 指向的 Person 对象中 dog,这个 dog 就是引用变量。但是,它是在堆空间。)
引用变量指向对象实际上保存的是对象在堆空间中的地址值(如:doggy 保存的是 Dog 对象在堆空间的地址值、yeTing 保存的是 Person 对象在堆空间的地址值)
3. 对象中的方法存储在那儿?
看下面的代码,思考对象中的方法存储在那儿?
public class Dog {
public int price;
public void run() {
System.out.println(price + "_" + "run()");
}
public void eat() {
System.out.println(price + "_" + "eat()");
}
}
public class Test {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.price = 255;
dog1.run();
dog1.eat();
Dog dog2 = new Dog();
dog2.price = 552;
dog2.run();
dog2.eat();
}
}
Java 虚拟机执行 Java 程序时会把内存划分为若干个不同的数据区域,主要包括:
① PC 寄存器(Program Counter Register):存储 Java 虚拟机正在执行的字节码指令的地址
② Java 虚拟机栈(Java Virtual Machine Stack):存储 Java 方法的栈帧(① 方法被调用的时候会在栈空间创建该方法的栈帧,该方法执行完毕后,该方法对应的栈帧会销毁;② 栈帧中会存放方法中的局部变量信息)【栈空间】
③ 堆空间(Heap):存储被 GC(垃圾回收器) 所管理的各种对象(GC 管理的是通过 new 关键字创建的对象)
④ 方法区(Method Area):存储每个类的结构信息(如:字段和方法信息、构造方法和普通方法的字节码信息)
⑤ 本地方法栈(Native Method Stack):用来支持 native 方法的调用(如:用 C 语言编写的方法)
4. Java 对象在内存中的存在形式 ②
String:
是字符串,在 Java 编程中被频繁地使用,但它是引用类型
Java 中双引号包裹的内容默认就是字符串类型
Java 中被双引号包裹的内容叫做字符串常量
字符串常量存储在字符串常量池中(String Constant Pool)
jdk1.7 之前,字符串常量池在方法区;后来被移动到了堆空间。所以,jdk1.8的字符串常量存储在堆空间的字符串常量池中
后面学习 String 的时候还会细说
分析下面代码的内存布局:
public class Dog {
String name;
int age;
String color;
}
public class DogDemo {
public static void main(String[] args) {
Dog doggy = new Dog();
doggy.name = "笑天";
doggy.age = 6;
doggy.color = "黑";
}
}
三、类中属性详细说明
现实世界中的对象有状态(State)和行为(Behavior),面向编程中的对象有属性(Field)和方法(Method)。
类是创建单个对象的蓝图(模板)
下面详细说明一下类中【属性】这个概念。其实上篇文章已经能够很好理解,这里只是再补充一下而已。
属性、成员变量、字段(field)指的是同一个东西(即一个类的状态)
习惯上把现实世界的对象的状态(State)和编程中的属性联系在一起,便于理解
属性可以是基本数据类型或引用类型(自定义类,接口,数组 …)
定义属性的语法:访问修饰符 + 属性类型(eg: String、int、Dog、Bicycle) + 属性名
访问修饰符(控制属性被访问的范围)有四种:public、protected、默认(不写)、private【后面会详细说】
/**
* 访问修饰符有四种:public、protected、默认(不写)、private
*/
public class Dog {
public String name;
protected int age;
String color;
private double weight;
}
如果不给对象的属性赋值,属性会有初始值
/**
* 测试若不给对象的属性赋初始值, 它们的默认初始值
*/
public class FiledInitialValueTest {
private int score;
private short age;
private byte month;
private long salary;
private float height;
private double pi;
private char firstName;
private boolean isTrueLove;
private Person person;
public static void main(String[] args) {
FiledInitialValueTest test = new FiledInitialValueTest();
System.out.println("\n若不给对象的属性赋值, 初始值如下所示:");
System.out.println("score【int】 = " + test.score);
System.out.println("age【short】 = " + test.age);
System.out.println("month【byte】 = " + test.month);
System.out.println("salary【long】 = " + test.salary);
System.out.println("height【float】 = " + test.height);
System.out.println("pi【double】 = " + test.pi);
// 字符类型的属性的初始值是空串(在控制台无法看到)
System.out.println("firstName【char】 = " + test.firstName);
// 字符类型的属性的初始值强制类型转换为 int 后是【0】
System.out.println("firstName【(int)char】 = " + (int) test.firstName);
System.out.println("isTrueLove【boolean】 = " + test.isTrueLove);
System.out.println("person【person】 = " + test.person);
}
}
四、细小知识点
1. 如何创建对象
必须先有类(模板)才能创建对象
通过【new】关键字创建类的对象。【new】:向堆空间申请一块内存存储对象数据
public class TestCreateObject {
public static void main(String[] args) {
// (1) 先声明再创建
Dog dog; // 声明
dog = new Dog(); // 通过 new 关键字创建对象
// (2) 声明并创建对象
Dog doggy = new Dog();
}
}
2. 如何访问属性
可通过【.】号访问属性或调用方法
可把 . 看做【的】、【の】
五、Exercise
看代码,画图:
public class Person {
private int age;
private String name;
public static void main(String[] args) {
Person yangJiaLi = new Person();
yangJiaLi.age = 17;
yangJiaLi.name = "杨嘉立";
// 下面的一行代码有2种说法:
// 1. 把 yangJiaLi 赋值给 yjl
// 2. yjl 指向 yangJiaLi
Person yjl = yangJiaLi;
System.out.println(yjl.age); // 17
}
}
六、总结
本篇文章的重点是第二节【Java 对象在内存中的存在形式】
需重点知道:
来源:https://blog.csdn.net/m0_54189068/article/details/126620737


猜你喜欢
- 基于JavaFX开发桌面程序注:我也是JAVA FX的初学者之一,自己在学习的时候踩了许多的坑,中文英文的资料查了不少,但是觉得FX技术和其
- 使用javafx更新UIJavaFx如果在子线程中更新UI,不论是task还是runable都会报错java.lang.IllegalSta
- 本文实例为大家分享了C#实现订单管理程序的具体代码,供大家参考,具体内容如下订单管理的控制台程序,能够实现添加订单、删除订单、修改订单、查询
- 最近做一个小玩意需要识别验证码,之前从来没有接触过识别验证码这块,这可难倒了我。所以,在网上搜索如何识别验证码,许多前辈写的博文教会了我。怕
- 1.super介绍我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。用于访问父类的属性,方法,构造器2.super
- 前言 对于服务端,达到高性能、高扩展离不开异步。对于客户端,函数执行时间是1毫秒还是100毫秒
- SpringBoot如何快速配置数据源;有如下两种方式:通过spring-boot-starter-jdbc快速配置数据源自定义数据源Dat
- Android EditText的光标的显示和隐藏一、java代码1.编辑框的光标 显示的方法:editText.setCursorVisi
- 压缩包制作也是很多项目中需要用到的功能。比如有大量的文件(假设有10000个)需要上传,1个1个的上传似乎不太靠谱(靠,那得传到什么时候啊?
- 实践过程效果代码public partial class Form1 : Form{ public Form1()
- 黑白棋介绍黑白棋,又叫苹果棋,最早流行于西方国家。游戏通过相互翻转对方的棋子,最后以棋盘上谁的棋子多来判断胜负。黑白棋非常易于上手,但精通则
- 前言这两天面试了一个物联网公司高级研发,面试题是下面这样子公司领导,部门主管,小组组长,组成员4级,假如有个 疫情预警,先通知组人员(对个人
- 本文主要探讨以下几个问题:嵌套滑动设计目的嵌套滑动的实现嵌套滑动与事件分发机制嵌套滑动设计目的不知道大家有没有注意过淘宝APP首页的二级联动
- spring针对Bean之间的循环依赖,有自己的处理方案。关键点就是 * 缓存。当然这种方案不能解决所有的问题,他只能解决Bean单例模式下非
- 数据导出到Excel几乎是所有客户都会提出的一个需求。下面我就分享一下我的代码。首先需要引入的jar包:然后就是正式代码了。package
- 本文实例讲述了Java自定义注解用法。分享给大家供大家参考,具体如下:一 自定义注解语法[public] @interface Annota
- java arrayList遍历的四种方法及Java中ArrayList类的用法package com.test;import java.u
- JSON.toJSONString格式化成json字符串时保留null属性使用阿里的com.alibaba.fastjson.JSON格式化
- 使用@Tolerate实现冲突兼容使用Lombok能够减少程序员的重复工作提高工作效率,而Lombok的注解基本是基于标准的(如,标准的Bu
- 最近在学习AngularJS的知识,收获不少,不过因为自己平时工作时开发都是用的freemarker+springmvc来做的页面数据交互,