专栏首页北风IT之路Java实例化对象过程中的内存分配

Java实例化对象过程中的内存分配

问题引入

这里先定义一个很不标准的“书”类,这里为了方便演示就不对类的属性进行封装了。

class Book{
    String name;    //书名
    double price;   //价格
    public void getInfo(){
        System.out.println("name:"+name+";price:"+price);
    }
}

在这个类中定义了两个属性和一个方法,当然也是可以定义多和类和多个方法的。 类现在虽然已经定义好了,但是一个类要使用它必须要实例化对象,那么对象的定义格式有一下两种格式:

//声明并实例化对象: 类名称 对象名称 = new 类名称()
Book book = new Book();
//分步完成声明和实例操作: 
// |- 声明对象: 类名称 对象名称 = null;
Book book = null;
// |- 实例化对象: 对象名称 = new 类名称();
book = new Book();

对象属于引用数据类型,其和基本数据类型最大的不同在于引用数据类型需要进行内存分配,而关键字new主要的功能就是开辟内存空间,也就是说只要是使用引用数据类型就必须使用关键字new来开辟空间。有些时候我们需要对对象属性进行操作,那么其中的堆栈内存空间又是如何分配的呢?接下来我们来分析一下其中的过程。

堆内存与栈内存

如果想对对象操作的过程进行内存分析,首先要了解两块内存空间的概念:

  • 堆内存:保存每一个对象的属性内容,堆内存需要用关键字new才能开辟。
  • 栈内存:保存的是一块堆内存的地址。

堆内存很好理解,可能有人会有疑问为什么会有栈内存,举个例子,好比学校有很多教室,每个教室有一个门牌号,教室内放了很多的桌椅等等,这个编号就好比地址,老师叫小明去一个教室拿东西,老师必须把房间号告诉小明才能拿到,也就是为什么地址必须存放在一个地方,而这个地方在计算机中就是栈内存。

对象空属性

我们先实例化一个对象,并对其的属性不设置任何值

public class Test{
    public static void main(String args[]){
         Book book = new Book();
         book.getInfo();
    }
}

运行结果如下:

name:null;price:0.0

其内存变化图如下:

使用关键字new就在栈内存中开辟一个空间存放book对象,并且指向堆内存的一个空间,此时并未对其赋值,所以始终指向默认的堆内存空间。

操作对象属性

我们先声明并实例化Book类,并对实例出的book对象操作其属性内容。

public class Test{
    public static void main(String args[]){
         Book book = new Book();
         book.name = "深入理解JVM";
         book.price = 99.8;
         book.getInfo();
    }
}

编译执行后的结果如下:

name:深入理解JVM;price:99.8

内存变化图如下:

分步实例化对象

示例代码如下:

public class Test{
    public static void main(String args[]){
         Book book = null;  //声明对象
         book = new Book(); //实例化对象
         book.name = "深入理解JVM";
         book.price = 99.8;
         book.getInfo();
    }
}

很明显结果肯定和前面一样

name:深入理解JVM;price:99.8

表面没什么区别,但是内存分配过程却不一样,接下来我们来分析一下

任何情况下只要使用了new就一定要开辟新的堆内存空间,一旦堆内存空间开辟了,里面就一定会所有类中定义的属性内容,此时所有的属性内容都是其对应数据类型的默认值。

直观的说就是栈内存先要指向一个null,然后等待开辟新的栈内存空间后才能指向其属性内容。

NullPointerException的出现

那么如果使用了没有实例化的对象,就会出现最常见也是最让人头疼的一个异常NullPointerException,像下面的代码

public class Test{
    public static void main(String args[]){
         Book book = null;
//         book = new Book();   //实例化的这一步被注释
         book.name = "深入理解JVM";
         book.price = 99.8;
         book.getInfo();
    }
}

在编译的过程是不会出错的,因为只有语法错误才会在编译时中断,而这种逻辑性错误能成功编译,但是执行的时候却会抛出NullPointerException异常。 运行结果:

Exception in thread "main" java.lang.NullPointerException at language.Test.main(Test.java:19)

空指针异常是平时遇到最多的一类异常,只要是引用数据类型都有可能出现它。这种异常的出现也是很容易理解的,犹如你说今天被一只恐龙追着跑,恐龙早就在几个世纪前就灭绝了,现实生活中不可能存在,当然人们就会认为你说的这句话是谎言。在程序中也一样,没有被实例化的对象直接调用其中的属性或者方法,肯定会报错。

引用数据分析

引用是整个java中的核心精髓,引用类似于C++中的指针概念,但是又比指针的概念更加简单。 举个简单的例子,比如李华的小名叫小华,一天李华因为生病向老师请假了,老师问今天谁请假了,说李华请假了和小华请假了都是一个意思,小华是李华的别名,他们两个都是对应一个个体。 如果代码里面声明两个对象,并且使用了关键字new为两个对象分别进行了对象的实例化操作,那么一定是各自占用各自的堆内存空间,并且不会互相影响。

例如:声明两个对象

public class Test{
    public static void main(String args[]){
         Book bookA = new Book();
         Book bookB = new Book();

         bookA.name = "深入理解JVM";
         bookA.price = 99.8;
         bookA.getInfo();

         bookB.name = "Java多线程";
         bookB.price = 69.8;
         bookB.getInfo();
    }
}

运行结果如下:

name:深入理解JVM;price:99.8
name:Java多线程;price:69.8

我们来分析一下内存的变化

接下来我们看看那对象引用传递

例如:对象引用传递

public class Test{
    public static void main(String args[]){
         Book bookA = new Book();   //声明并实例化对象
         Book bookB = null;         //声明对象
         bookA.name = "深入理解JVM";
         bookA.price = 99.8;
         bookB = bookA;             //引用传递
         bookB.price = 69.8;
         bookA.getInfo();
    }
}

运行结果如下:

name:深入理解JVM;price:69.8

严格来讲bookA和bookB里面保存的是对象的地址信息,所以以上的引用过程就属于将bookA的地址赋给了bookB,此时两个对象指向的是同一块堆内存空间,因此任何一个对象修改了堆内存之后都会影响其他对象。

一块堆内存可以同时被多个栈内存所指向,但是反过来,一块栈内存只能保存一块堆内存空间的地址。

垃圾的产生

先看如下代码:

public class Test{
    public static void main(String args[]){
         Book bookA = new Book();   //声明并实例化对象
         Book bookB = new Book();   //声明并实例化对象
         bookA.name = "深入理解JVM";
         bookA.price = 99.8;
         bookB.name = "Java多线程";
         bookB.price = 69.8;
         bookB = bookA;             //引用关系
         bookB.price = 120.8;
         bookA.getInfo();
    }
}

运行结果如下:

name:深入理解JVM;price:120.8

整个过程内存又发生了什么变化呢?我们来看一下

在此过程中原来bookB所指向的堆内存无栈内存指向,一块没有任何栈内存指向的堆内存空间就将成为垃圾,等待被java中的回收机制回收,回收之后会释放掉其占用的空间。

虽然在java中支持了自动的垃圾收集处理,但是在代码的编写过程中应该尽量减少垃圾空间的产生。

END

本文分享自微信公众号 - 北风IT之路(beifengtz),作者:beifengtz

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 关于JVM内存的N个问题

    JVM的内存划分中,有部分区域是线程私有的,有部分是属于整个JVM进程;有些区域会抛出OOM异常,有些则不会,了解JVM的内存区域划分以及特征,是定位线上内存问...

    beifengtz
  • 手把手教你搭建Jenkins实现自动化部署Jar

    java_home地址可以用echo $JAVA_HOME 输出 (前提是/etc/profile有export)

    beifengtz
  • mysql中关于时间统计的sql语句总结

    在之前写VR360时有一个统计页面(https://vr.beifengtz.com/p/statistics.html),在此页面的数据统计时用到了很多mys...

    beifengtz
  • 风控监控体系(1)-逾期监控指标体系

    余额逾期率(互金口径)=\frac{逾期未还本金(互金统计口径)}{未结清本金(余额) }

    花鸣溪
  • Javascript的内存泄漏分析

         作为程序员(更高大尚的称谓:研软件研发)的我们,无论是用Javascript,还是.net, java语言,肯定都遇到过内存泄漏的问题。只不过他们都有...

    sam dragon
  • 求超大文件上传方案( WebUploader )

    public Map<String, JSONObject> toDic(JSONArray folders)

    用户6892101
  • Python自学成才之路 元类中的__new__和__init__方法

    元类其实和普通类一样,普通类的__new__方法是创建实例,__init__方法是初始化实例,说是初始化,其实就是可以给实例添加一些属性。在元类中也是一样,只是...

    我是李超人
  • C#GDI画立体渐变圆角panel

     private void panel1_Paint(object sender, PaintEventArgs e)         {

    Dabelv
  • pycharm里html注释是{# #}而不是<!-- -->的问题解决

    修改方式:如图修改成值None以后,ctrl+/快捷键,html注释的符号就是;django 的时候,注释符号就是{# 注释内容 #},可能有的版本显示的跟我的...

    小海怪的互联网
  • Linux分区的注意事项以及远程连接排错

    分区方式一般有三种 第一种:数据不是很重要 /boot(系统的引导分区): 系统引导的信息/软件 系统的内核   200M swap( 交换分区): 为了避免系...

    863987322

扫码关注云+社区

领取腾讯云代金券