面向对象及jvm的内存分配

目录

一、面向对象的编程方式:

二、对象与类的说明

三、类可以包含的成员

四、Java程序运行时的内存结构

jdk1.8中有那些改变?

JVM系列 - Java对象都是创建在堆内存中的吗?

构造器为什么能给对象赋,是改变堆中对象的属性吗?

如何判断对象已“死”!

Java栈

堆中存什么?栈中存什么?

基本数据类型的成员变量放在jvm得哪块内存区域里?

答:堆中!


前言:面向过程的编程方式(c语言)

采用自顶而下,逐层细化的方式来编写程序,在历史中得到了广泛的应用。具体的写法,在一个项目中创建多个函数,多个函数之间相互调用,通过函数的划分可以细分出多个模块。这种方式不利于设计大型的项目,大量的函数不容易管理,函数间的调用变得非常的复杂容易出错,模块的划分越来越不清晰。

一、面向对象的编程方式:

C语言是面向过程的,经过改进后出现了c++语言,c++是第一个面向对象的开发语言。java是参照了c++语言的实现方式来设计自身的,也做了一些改进比如不允许多继承,还提供了垃圾回收器。

java是面向对象的编程语言,其优势在于,可以对已经实现的功能进行很好的复用,“不用重复造轮子”,也就是尽量的使用已有的产品。

面向对象的语言的最大好处在于可以设计大型的应用项目,项目的结构更加合理,项目更容易维护和管理。

二、对象与类的说明

1、对象经过抽象可以产生类,所谓抽象就是去掉对属性的具体描述

System.out.println(person.getClass());

总结:类是抽象的,对象是具体的,类与对象可以相互转化。new对象 以及通过getClass拿到类。

三、类可以包含的成员

类可以包含多种成员,各种成员有其特定的作用。

1、成员变量:定义在类中方法外的变量,这是类的属性的具体形式。

基本语法:修饰符 数据类型 变量名称 ---》 private String name;

(1)静态变量

(2)非静态变量

(3)final修饰的常量(后期不能被修改)

2、成员方法:定义在类中的方法,它是类的行为的具体形式

基本语法:修饰符 返回类型 方法名(形参列表){方法体}

(1)静态方法

(2)非静态方法(实例方法,与对象有关)

(3)没有方法体,带有abstract关键字,属于抽象方法(接口中全是,后期实现类进行重写自定义功能)

(4)final,属于最终方法不允许被重写

3、构造方法:创建对象的时候会被执行,作用是对类进行实例化(也可以叫做初始化,也就是给成员变量赋初值 空参构造器直接赋默认值0、null、\u0000等值)。

  • 构造方法执行的时机问题:由jvm调用,当类创建对象时就调用它。

  • 构造器中的this是当前类对象的引用 :这就是为什么有参构造器可以给对象赋值的原因所在(不是局部变量那种出了方法就失效)

4、内部类:写在类中的类,另一个类出现在本类的大括号中

  • static 静态内部类

  • 未加static 实例内部类

  • 匿名内部类:没有类名直接new接口的方式创建对象,称为匿名内部类

new Iterable<String>(){
    @Override
    public Iterator iterator() {
        return null;
    }
};

5、本地方法 :带有native关键字,它没有方法体,只有方法的声明,可以直接调用。

new 对象后p代表什么?

Person p = new Person();

解释:p变量实际是该类型的对象的引用。直接打印为对象的地址值。

  • 对象是类的具体化,基本类型中,凡是整数默认值为0,小数默认0.0,char是\u0000,boolean是false,引用为null。

  • 静态方法的调用可以用对象也可以用类 但推荐用类 (基于阿里巴巴代码规范 直接判定用对象调静态方法是不能通过编译的)

  • 每个对象内部都有一个this用来引用自己,this也可以用于非静态的方法中。

  • this是jvm在创建对象时所声明并赋值的对象变量的引用

四、Java程序运行时的内存结构

Java程序运行在jvm中,jvm的主要作用是管理程序的运行(深入理解Java虚拟机 周志明)。

jvm把内存划分为多个部分,每个部分具有不同的职责。划分如下:

以下区域属于操作系统划分一部分内存专门给jvm使用的

元数据区:类字节码文件(.class)的所有内容都存放在该区,包括方法的代码,静态的变量值等。

堆区:主要用来存放运行时创建的对象数据。

栈区:是方法的执行场所,每个方法都会被分配一块工作栈,工作栈中包括当前执行的代码,局部变量,另外还有一个程序计数器来记录方法的执行位置。

常量池:在类中有一些数据是永久不变的,比如在类中声明的常量(final),通过赋值而产生的字符串(String str = "abc"),还有对类中方法引用的地址。

本地方法栈:用来执行带有native关键字方法的场所。

堆外内存:执行大量IO操作时用到的临时存放数据的空间。

方法执行过程:栈区里面对象.方法的引用指向堆区方法的地址 接着堆区方法的地址对应元空间中的具体方法 (静态或非静态方法)这些方法都是磁盘里的.class文件加载进元空间中的

最后加载进栈中对应方法的工作栈执行此方法。

jdk1.8中有那些改变?

在JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代

在JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代

在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)

对于直接做+运算的两个字符串(字面量)常量,并不会放入字符串常量池中,而是直接把运算后的结果放入字符串常量池中
(String s = "abc"+ "def", 会直接生成“abcdef"字符串常量 而不把 "abc" "def"放进常量池)
对于先声明的字符串字面量常量,会放入字符串常量池,但是若使用字面量的引用进行运算就不会把运算后的结果放入字符串常量池中了
(String s = new String("abc") + new String("def"),在构造过程中不会生成“abcdef"字符串常量)

在执行str = new String("abc")时,会在编译期时,在常量池中放入一个"abc",在执行期时,把str中的value指向常量池中的"abc"。

总结一下就是JVM会对字符串常量的运算进行优化,未声明的,只放结果;已经声明的,只放声明 常量池中同时存在字符串常量和字符串引用。直接赋值和用字符串调用String构造函数都可能导致常量池中生成字符串常量;而intern()方法会尝试将堆中对象的引用放入常量池

String str1 = "a"; String str2 = "b"; String str4 = str1 + str2; //该语句只在堆中生成一个对象(str4) 这句被Java编译器做了优化, 实际上使用StringBuilder实现的(不在堆里生成str1和str2对象)

String str5 = new String("ab");(字符串常量池中不存在"ab"时)在字符换常量池中创建"ab"对象,在堆中生成了一个对象str5, str5指向堆上new的对象,而str5内部的char value[]则指向常量池中的char value[]

JVM系列 - Java对象都是创建在堆内存中的吗?

Java对象并不是都会在堆内存中分配空间的。之前写了一篇比较长的关于JVM学习的笔记,里面说过,Java创建对象实例的时候,大部分新生对象都是存放在堆内存Eden区中的,少数情况下也可能会直接分配到老年代中,分配规则并不是固定不变的,这主要取决于当前选用的哪种垃圾回收器,以及设置的JVM参数。比如对于大多数垃圾回收器来说,如果要创建的对象大小超过 -XX:PretenureSizeThreshold: 参数的设置时,这个大对象会直接接入老年代。而对于G1垃圾回收器,它是将整个堆分成固定大小的分区域,有一类分区标记称为H,代表Humongous,表示这类分区是用于存储巨大对象的(humongous object,H-obj),即大小大于等于region一半的对象,这样超大对象就直接分配到了老年代。

总结 正常情况下,对象是要在堆上进行内存分配的,但是随着编译器优化技术的成熟,虽然虚拟机规范是这样要求的,但是具体实现上还是有些差别的。

如HotSpot虚拟机引入了JIT优化之后,会对对象进行逃逸分析,如果发现某一个对象并没有逃逸到方法外部,那么就可能通过标量替换来实现栈上分配,而避免堆上分配内存。

所以,对象一定在堆上分配内存,这是不对的。

构造器为什么能给对象赋,是改变堆中对象的属性吗?

虽然是栈 但是你new对象也是在main方法中new的 相当于局部变量。

new一个对象,在堆中分配内存空间存储对象实例数据(对象中各个实例数据)

main方法栈 属于栈 new对象后 对象实际存在于堆内存 而引用是存在于栈

this.name 实际是在堆中改变对象的属性值 当前对象的引用直接指向堆内存的的对象并修改其对象属性(不是修改对象 如对象一改为对象二) 相当于真实的修改了 !

对于程序计数器、虚拟机栈、本地方法栈这三个部分而言,其生命周期与相关线程有关,随线程而生,随线程而灭。并且这三个区域的内存分配与回收具有确定性,因为当方法结束或者线程结束时,内存就自然跟着线程回收了。

如何判断对象已“死”!

jvm垃圾回收机制也会回收这些对象

main方法执行完毕后 不仅回收结束了 jvm虚拟机都关了!

        

(1)用Person zhang;声明一个对象zhang时,将在栈内存为对象的引用变量zhang分配内存空间,但Person的值为空,称zhang是一个空对象。空对象不能使用,因为它还没有引用任何“实体”。

(2)对象实例化时的内存模型当执行zhang=new Person(zhangsan,20);时,会做两件事:在堆内存中为类的成员变量name、age分配内存,并将其初始化为各数据类型的默认值;接着进行显式初始化(类定义时的初始化值);最后调用构造方法,为成员变量赋值。返回堆内存中对象的引用(相当于首地址)给引用变量zhang,以后就可以通过zhang来引用堆内存中的对象了。

Java栈

每当一个java方法被执行时都会在虚拟机中新创建一个栈帧,main也不例外。

另外,**main方法运行在主线程上,一个线程对应着一个java栈,**默认分配内存1M,一个java栈里面可以包含多个栈帧。

理论上正常退出的话,main方法对应的栈帧也一定会弹出,然后主线程退出,进程退出。

java进程意外被杀死的话就可能没有弹出的机会了。

main作为程序的入口是

压栈  先进后出

堆中存什么?栈中存什么?

堆中存的是对象。 栈中存的是形参:基本数据类型和堆中对象的引用。

基本数据类型的成员变量放在jvm得哪块内存区域里?

答:堆中!

比如
class{
private int i;
}
如上代码,之前一直以为基本数据类型都是放在虚拟机栈中的,最近看了《深入理解jvm》,里面说到方法内定义的基本数据类型放在帧栈里,而且栈里面的数据是线程独有的,不共享。 那么基本数据类型的全局变量,到底是放在栈里面 还是堆里面,或者方法区里?

java虚拟机栈是线程私有的,生命周期跟线程相同,每个方法调用的时候都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法调用的过程,就代表了一个栈帧在虚拟机栈中入栈到出栈的过程,当进入一个方法时,这个方法在栈中需要分配多大的内存都是完全确定的,方法运行时不会改变局部变量表的大小——《深入理解java虚拟机第二版》

​       很多java程序员一开始就被网上的一些教程所误导:基本数据类型放在栈中,数组和类的实例放在堆中。 这个说法不准确。事实上,如上面的全局变量i,他是存放在java堆中。因为它不是静态的变量,不会独立于类的实例而存在,而该类实例化之后,放在堆中,当然也包含了它的属性i。

​       如果在方法中定义了int i = 0;则在局部变量表创建了两个对象:引用i和0。 这两个对象都是线程私有(安全)的。 比如定义了int[] is = new int[10]. 定义了两个对象,一个是is引用,放在局部变量表中,一个是长度为10的数组,放在堆中,这个数组,只能通过is来访问,方法结束后出栈,is被销毁,根据java的根搜索算法,判断数组不可达,就将它销毁了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值