Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java关键字new-----对象的内存分配原理

Java关键字new-----对象的内存分配原理

作者头像
Tanyboye
发布于 2018-07-02 03:44:43
发布于 2018-07-02 03:44:43
2.5K00
代码可运行
举报
运行总次数:0
代码可运行

一、关键字new概述

"new"可以说是Java开发者最常用的关键字,我们使用new创建对象,使用new并通过类加载器来实例化任何我们需要的东西,但你是否深入了解过new在编译的瞬间都做了什么?

在Java中使用new关键字创建对象变得很容易了,事实上,对这些事情你是不需要考虑的。需要访问一个文件吗?只需要创建一个新的File实例:new File(“jdbc.properties”),对于大多数Java开发人员而言,这就是他们需要知道的一切,是不是很简单呢?!但当你使用了多个类加载器时,问题就不一样了。

下面是对oracle官网文章的翻译:http://docs.oracle.com/javase/tutorial/java/javaOO/objectcreation.html

我们都知道,一个类为对象提供了蓝图,你从一个类创建一个对象。以下语句从createobjectdemo程序创建一个对象并将其赋值给一个引用变量:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Point originOne = new Point(23, 94); 
Rectangle rectOne = new Rectangle(originOne, 100, 200);
Rectangle rectTwo = new Rectangle(50, 100);

第一行创建了一个 Point 类的对象,第二个和第三个线创建一个Rectangle 矩形类的对象。

这些陈述中的每一个都有三个部分(详细讨论):

声明Declaration:粗体代码是将变量名称与对象类型关联的变量声明。

实例化Instantiating :new关键字是一个java运算符,它用来创建对象。

初始化Initialization:new运算符,随后调用构造函数,初始化新创建的对象。

声明一个变量来指向一个对象,即引用

在此之前,你知道,要声明一个变量,你需要写:type name;

这将告诉编译器你将使用name引用一个type类型的对象。用一个原始变量,这个声明也保留了适当的内存量的变量。

你也可以在自己的行上声明一个引用变量。例如:Point originone;

如果你只是声明一个像originone这样的引用变量,其价值将待定,直到有一个对象真正被创造和分配给它。只是简单地声明一个引用变量而并没有创建一个对象。对于这样,你需要使用new运算符。在你的代码中使用它之前,你必须指定一个对象给originone。否则,你会得到一个编译器错误-----空指针异常。

处于这种状态的变量,目前没有引用任何的对象,可以说明如下(变量名,originone,一个引用没指向任何对象)。

实例化一个类对象

new运算符实例化一个类对象,通过给这个对象分配内存并返回一个指向该内存的引用。new运算符也调用了对象的构造函数。

注意:“实例化一个类的对象”的意思就是“创建对象”。创建对象时,你正在创造一个类的“实例”,因而“实例化”一个类的对象。

new运算符需要一个单一的,后缀参数,需要调用构造函数。构造函数的名称提供了需要实例化类的名称。

new运算符返回它所创建的对象的引用。此引用通常被分配给一个合适的类型的变量,如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Point  originone =new Point2394);

由new运算符返回的引用可以不需要被赋值给变量。它也可以直接使用在一个表达式中。例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int height = new Rectangle().height;

初始化一个类对象

这是Point类的代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Point {    
    public int x = 0;    
    public int y = 0;    
    //constructor
    public Point(int a, int b) {
        x = a;
        y = b;
    }
}

这个类包含一个单一的构造函数。你可以识别一个构造函数,因为它的声明使用与类具有相同的名称,它没有返回类型。在Point类构造函数的参数是两个整数参数,如代码声明(int a,int b)。下面的语句提供了94和23作为这些参数的值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Point originOne = new Point(23, 94);    //结果可描述为下图

这是Rectangle类,包含4个版本的构造方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Rectangle {    
    public int width = 0;    
    public int height = 0;    
    public Point origin;    // four constructors
    public Rectangle() {
        origin = new Point(0, 0);
    }    
    public Rectangle(Point p) {
        origin = p;
    }    
    public Rectangle(int w, int h) {
        origin = new Point(0, 0);
        width = w;
        height = h;
    }    
    public Rectangle(Point p, int w, int h) {
        origin = p;
        width = w;
        height = h;
    }    
    // a method for moving the rectangle
    public void move(int x, int y) {
        origin.x = x;
        origin.y = y;
    }    
    // a method for computing the area of the rectangle
    public int getArea() {        
        return width * height;
    }
}
public class Rectangle {    
    public int width = 0;    
    public int height = 0;    
    public Point origin;    
    // four constructors
    public Rectangle() {
        origin = new Point(0, 0);
    }    
    public Rectangle(Point p) {
        origin = p;
    }    
    public Rectangle(int w, int h) {
        origin = new Point(0, 0);
        width = w;
        height = h;
    }    
    public Rectangle(Point p, int w, int h) {
        origin = p;
        width = w;
        height = h;
    }    
    // a method for moving the rectangle
    public void move(int x, int y) {
        origin.x = x;
        origin.y = y;
    }    // a method for computing the area of the rectangle
    public int getArea() {        
        return width * height;
    }
}

每个构造函数都允许你为矩形的起始值、宽度和高度提供初始值,同时使用原始类型和引用类型。如果一个类有多个构造函数,它们必须有不同的签名。java编译器区分构造函数基于参数的数量和类型。当java编译器遇到下面的代码,它知道在矩形类,需要一点争论,后面跟着两个整数参数调用构造函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Rectangle rectOne = new Rectangle(originOne, 100, 200);

结果可描述为下图:

总结:

1.Java关键字new是一个运算符。与+、-、*、/等运算符具有相同或类似的优先级。

2.创建一个Java对象需要三部:声明引用变量、实例化、初始化对象实例。

3.实例化:就是“创建一个Java对象”-----分配内存并返回指向该内存的引用。

4.初始化:就是调用构造方法,对类的实例数据赋初值。

5.Java对象内存布局:包括对象头和实例数据。如下图:

对象头:它主要包括对象自身的运行行元数据,比如哈希码、GC分代年龄、锁状态标志等;同时还包含一个类型指针,指向类元数据,表明该对象所属的类型。

实例数据:它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)。

在hotSpot虚拟机中,对象在内存中的布局可以分成对象头、实例数据、对齐填充三部分。对齐填充:它不是必要存在的,仅仅起着占位符的作用。

6.Object obj = new Object();

那“Object obj”这部分的语义将会反映到Java栈的本地变量表中,作为一个reference类型数据出现。而“new Object()”这部分的语义将会反映到Java堆中,形成一块存储了Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。

二、内存分配原理

内存分配,在哪分配?-------尽管Java对象的内存分配可以使用逃逸分析技术和栈外分配,但不可否认这仅仅是为了降低GC回收频率以及提升GC回收效率的一种辅助手段,所以Java堆区仍然是分配/存储对象实例的主要区域,这一点毋庸置疑。

参考《Java虚拟机规范(第7版)》的描述,JVM包含三种引用类型,分别是类型 (class type),数组类型(array type)和接口类型(interface type),这些引用类型的值则分别 由类实例、数组实例以及实现了某个接口的派生类实例负责动态创建,那么JVM中究 竟是如何为这些类型创建对应的对象实例呢?-------------如果是在Java语法层面上创建 一个对象,无非就是使用一个简单的new关键字即可,但是在JVM中就没有那么简 单了,其实牵扯到细节的实现相当复杂,而且过程繁多。简单地说,当Java语法层面 使用new关键字创建一个Java对象时,JVM首先会检查这个new指令的参数能否在常 量池中定位到一个类的符号引用,然后检查与这个符号引用相对应的类是否已经成功经 历加载、解析和初始化等步骤,当类完成装载步骤之后,就已经完全确定出创建对象实 例时所需的内存空间大小,接下来JVM将会对其进行内存分配,以存储所生成的对象 实例。如下图所示:

为新对象分配内存是一件非常严谨和复杂的任务,JVM的设计者们不仅需要考虑内存如何分配、在哪分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还要考虑GC执行完内存回收后是否会在内存空间中产生内部碎片。如果内存空间以规整和有序的的方式分布,当为新对象分配内存时,只需要修改指针的偏移量将新对象分配在第一个空闲内存位置上,这种分配方式就叫做指针碰撞(Bump the Pointer),反之则只能使用空闲列表(Free List)执行内存分配。

基于分代的概念,Java堆区如果进一步细分的话,还可分为:新生代 ( Young )和老年代 ( Old );这也就是JVM采用的“分代思想”,简单说,就是针对不同特征的java对象采用不同的策略实施存放和回收,所用分配机制和回收算法就不一样。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。(《Java虚拟机精讲》)

分代收集算法:采用不同算法处理[存放和回收]Java瞬时对象和长久对象。大部分Java对象都是瞬时对象,朝生夕灭,存活很短暂,通常存放在Young新生代,采用复制算法对新生代进行垃圾回收。老年代对象的生命周期一般都比较长,极端情况下会和JVM生命周期保持一致;通常采用标记-压缩算法对老年代进行垃圾回收。

这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。那么Java堆区被细分成这么多区域,对象实例究竟是存储在堆区中的那一个区域下呢?在JVM运行数据区中,堆区和方法区是线程共享的数据区,任何线程都可以访问到这两个区域中的共享数据,由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆中划分内存空间是非线程安全的,所以务必需要保证数据操作的原子性。基于线程安全的考虑,如果一个类在分配内存之前成功完成的类加载,JVM会优先选择在TLAB(Thread Local Allocation Buffer,本地线程分配缓存区)中为对象实例分配内存空间,TLAB在Java堆中是一块线程私有数据区,它包含在Eden空间内,除了可以避免一系列的非线程安全问题外,同时还能提高内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。

当为对象成功分配好所需的内存空间(实例化)后,JVM接下来要做的任务就是-------初始化对象实例。JVM首先会对分配好的内存空间进行零值初始化,这一步操作确保了对象的实例字段在Java代码中可以不用赋初值就能够直接使用,程序能够访问到这些字段的数据类型所对应的零值。

对分配后的内存空间进行零值初始化后,JVM就会初始化对象头和实例数据。最后将对象引入栈后,再更新PC寄存器中的字节码指令地址。经过这一系列的操作步骤之后每一个Java对象实例才算是真正的创建成功。

总结:

1.在Java语法层面上创建一个对象,使用一个简单的new关键字即可,但是在JVM中细节的实现相当复杂,而且过程繁多。

2.当Java语法层面使用new关键字创建一个Java对象时,JVM首先会检查相对应的类是否已经成功经历加载、解析和初始化等步骤;当类完成装载步骤之后,就已经完全确定出创建对象实例时所需的内存空间大小,才能对其进行内存分配,以存储所生成的对象实例。

3.实例化之后,进行初始化(初始化对象头和实例数据)。

4.内存分配方式有:指针碰撞(Bump the Pointer)、快速分配策略、空闲列表(Free List)。

5.在并发环境下从堆中划分内存空间是非线程安全的,new运算符具有-------数据操作的原子性;也就是说创建一个Java对象分配内存,要么所有步骤都成功,返回对象的引用,要么回归到创建之前的内存状态,返回为NULL。

6.通过new创建一个Java对象,如果成功则返回这个对象的引用,开发者不可直接操作对象实例,需要通过这个引用“牵引”。

看完这篇文章,相信你对Java关键字new及Java对象的完整创建过程有了更深的认识,就不会只停留在new一个对象就完了。

如果大家觉得这篇文章对你有帮助的话,欢迎大家关注我的公众号:java技术学习之道(javajsxxzd),长期分享各种技术文章。

原文地址:

https://blog.csdn.net/ljheee/article/details/522359

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-04-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java技术学习之道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java_内存分配
new出的空间都是作为动态内存在堆中分配的,比如new出的对象的成员属性、使用new开辟的数组中的各个元素、使用new创建的基本数据类型等
用户10551528
2023/05/09
5370
Java_内存分配
JAVA基础知识点:内存、比较和Final
1.java是如何管理内存的 java的内存管理就是对象的分配和释放问题。(其中包括两部分) 分配:内存的分配是由程序完成的,程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都在堆(Heap)中分配空间。 释放:对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作。但同时,它也加重了JVM的工作。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。 2.什么叫java的内存泄露 在java中,
老白
2018/03/19
1.3K0
Oracle Java类和对象
在题为“面向对象编程概念”的课程中,面向对象概念的介绍以自行车类为例,赛车、山地自行车和串联自行车为子类。下面是Bicycle类的可能实现的示例代码,为您提供类声明的概述。本课程的后续部分将逐步支持和解释类声明。目前,不要关心细节。
郭顺发
2023/07/17
9410
Oracle Java类和对象
一文详解JVM对象内存布局以及内存分配规则
对象头可能包含类型指针,通过该指针能确定对象属于哪个类。如果对象是一个数组,那么对象头还会包括数组长度。
架构狂人
2023/08/16
4020
一文详解JVM对象内存布局以及内存分配规则
java栈内存初始化,阿里面试官:小伙子,你给我说一下JVM对象创建与内存分配机制吧…
虚拟机遇到一条new指令(new关键字、对象的克隆、对象的序列化等)时,会先去检查这个指令的参数在常量池中定位到一个类的符号引用,并且这个符号引用代表的类是否应被加载过,如果没有那么就去加载该类
全栈程序员站长
2022/08/28
3290
java栈内存初始化,阿里面试官:小伙子,你给我说一下JVM对象创建与内存分配机制吧…
Java 中文官方教程 2022 版(三)
一个典型的 Java 程序会创建许多对象,正如您所知,这些对象通过调用方法进行交互。通过这些对象之间的交互,程序可以执行各种任务,比如实现 GUI、运行动画,或者在网络上传输和接收信息。一旦一个对象完成了它被创建的工作,它的资源就会被回收以供其他对象使用。
ApacheCN_飞龙
2024/05/24
3960
Java 中文官方教程 2022 版(三)
类和对象(万字总结!深度总结了类的相关知识)(上)
为什么C++要学习类?学习C++中的类是掌握面向对象编程的关键。类提供了将数据与操作封装在一起的结构化方式,帮助开发者解决复杂问题、提高代码的可重用性和安全性。通过类的继承、封装、多态,可以更灵活地设计和扩展程序,同时模拟现实世界中的对象和行为,提升代码的可维护性和效率。掌握类有助于编写高效、清晰的代码,并应对复杂的软件系统设计。下面就由我来带大家深度剖析一下类和对象的真正奥秘…
suye
2024/10/16
1250
Java关键字final、static总结与对比
Java关键字final有“不可改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。
chenchenchen
2022/03/09
9290
Java关键字final、static总结与对比
谁创建谁销毁,谁分配谁释放——JNI调用时的内存管理
在QQ音乐AndroidTV端的Cocos版本的开发过程中,我们希望尽量多的复用现有的业务逻辑,避免重复制造轮子。因此,我们使用了大量的JNI调用,来实现Java层和Native层(主要是C++)的代码通信。一个重要的问题是JVM不会帮我们管理Native Memory所分配的内存空间的,本文就主要介绍如何在JNI调用时,对于Java层和Native层映射对象的内存管理策略。 1. 在Java层利用JNI调用Native层代码 如果有Java层尝试调用Native层的代码,我们通常用Java对象来封装C++
QQ音乐技术团队
2018/01/30
4.7K0
谁创建谁销毁,谁分配谁释放——JNI调用时的内存管理
Java内存管理(一、内存分配)
关于Java内存分配,很多问题都模模糊糊,不能全面贯通理解。今查阅资料,欲求深入挖掘,彻底理清java内存分配脉络,只因水平有限,没达到预期效果,仅以此文对所研究到之处作以记录,为以后学习提供参考,避免重头再来。
bear_fish
2018/09/20
3.5K0
JVM创建对象之内存解析
加载类元信息 -》 为对象分配内存 -》处理并发问题 -》属性的默认初始化 -》设置对象头 -》init方法
程序员阿杜
2021/07/05
5100
JVM创建对象之内存解析
Java的前沿分享(1):value或许成为java的新关键字
布莱恩·格茨在去年底发表了一篇名为State of Valhalla的文章,里面信息量非常大,里面提到早在2014年Java项目组就启动了一个名叫Valhalla的项目,这个项目将为JVM平台带来更加灵活的、扁平化的数据类型。在2021年该项目将有进一步的动作,值对象(value objects)、原始类(primitive classes)、专用泛型(specialized generics)即将引入JVM平台。
码农小胖哥
2022/02/11
3050
Java的前沿分享(1):value或许成为java的新关键字
【java基础】java关键字总结及详解
Java关键字是电脑语言里事先定义的,有特别意义的标识符,有时又叫保留字,还有特别意义的变量。Java的关键字对Java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等,关键字不能用作变量名、方法名、类名、包名和参数。
全栈程序员站长
2022/09/08
4700
深入理解JVM之JVM内存区域与内存分配
在学习jvm的内存分配的时候,看到的这篇博客,该博客对jvm的内存分配总结的很好,同时也利用jvm的内存模型解释了java程序中有关参数传递的问题。
哲洛不闹
2019/07/10
6530
深入理解JVM之JVM内存区域与内存分配
Java之Java关键字及其作用
private 关键字是访问控制修饰符,可以应用于类、方法或字段(在类中声明的变量)。 只能在声明 private(内部)类、方法或字段的类中引用这些类、方法或字段。在类的外部或者对于子类而言,它们是不可见的。 所有类成员的默认访问范围都是 package 访问,也就是说,除非存在特定的访问控制修饰符,否则,可以从同一个包中的任何类访问类成员。
全栈程序员站长
2022/06/30
9390
「JAVA」只知对象属性,不知类属性?就算类答应,static都不答应
在面向对象的思想中,一切事物都可以认为是对象——万物皆对象,把对象定义成包含状态和行为的一个实体,存在于现实世界中并且可以与其他实体区分开来的。对象具有状态和行为;比如:想想你心仪的小姐姐,可以把这个小姐姐看作是一个对象,那么该对象有两方面的定义:状态和行为;状态,如身高,年龄,三围,头发(长发或者短发)等;行为,如调戏你、跳舞,玩手机等。
老夫编程说
2020/04/25
5590
「JAVA」只知对象属性,不知类属性?就算类答应,static都不答应
Java内存分配之堆、栈和常量池
在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。当在一段代码定义一个变量时,java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另做他用。
技术从心
2019/08/06
1.4K0
Java内存分配之堆、栈和常量池
Java基础篇 | 初始面向对象
不知道小伙伴发现问题没有,其实这两者方式虽然都能实现,但确是漏洞百出,接下来我们一点一点分析。
程序员Leo
2023/11/16
1970
Java基础篇 | 初始面向对象
java中的static关键字的作用?
是静态修饰符,什么叫静态修饰符呢?大家都知道,在程序中任何变量或者代码都是在编译时由系统 自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到程序退出内存才 会释放这个空间,也就是只要程序在运行,那么这块内存就会一直存在。这样做有什么意义呢? 在Java程序里面,所有的东西都是对象,而对象的抽象就是类,对于一个类而言,如果要使用他的成员,那么普通情况下必须先实例化对象后,通过对象的引用才能够访问这些成员,但是有种情况例外,就是该成员是用static声明的(在这里所讲排除了类的访问控制),例如: 未声明为static的例子:
全栈程序员站长
2022/09/15
3320
java中的static关键字的作用?
JVM栈上分配对象内存与逃逸分析原理分析(Escape Analysis)
JVM中较前沿的优化技术,它与类型继承关系分析一样,并非直接优化代码,而是为其他优化措施提供依据的分析技术。
JavaEdge
2022/11/30
2790
相关推荐
Java_内存分配
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验