关于 JVM 内存管理
-
JVM(Java虚拟机)主要包括三块内存空间:栈内存、堆内存、方法区内存。
-
堆内存和方法区内存各有一个。一个线程一个栈内存。
-
方法调用的时候,该方法所需要的内存空间在栈内存中分配,称为压栈。方法执行结束之后,该方法所属的内存空间释放,称为弹栈。
-
栈中主要存储的是方法体当中的局部变量。
-
方法的代码片段以及整个类的代码片段都被存储到方法区内存中,在类加载的时候这些代码片段会载入。
-
在程序执行过程中使用 new 运算符创建的 java 对象,存储在堆内存当中。对象内部有实例变量,所以实例变量存储在堆内存当中。
-
变量分类:
- 局部变量【方法体中声明】
- 成员变量【方法体外声明】
- 实例变量【前边修饰符没有 static 】
- 静态变量【前边修饰符中有 static 】
-
静态变量存储在方法区内存当中。
-
三块内存当中变化最频繁的是栈内存,最先有数据的是方法区内存,垃圾回收主要针对的是堆内存。
-
垃圾回收器【自动垃圾回收机制、GC 机制】什么时候会考虑将某个 java 对象的内存回收呢 ?
- 当堆内存中的 java 对象称为垃圾数据的时候,会被垃圾回收期回收。
- 什么时候堆内存中的 java 对象会变成垃圾呢 ?
- 没有更多的引用指向它的时候
- 这个对象无法被访问,因为访问对象只能通过引用的方式访问
构造方法
1、构造方法的作用:
1、创建对象
2、创建对象的同时,初始化实例变量的内存空间
- 成员变量之实例变量,属于对象级别的变量,这种变量必须先有对象才能有实例变量。
- 实例变量没有手动赋值的时候,系统默认赋值,那么这个系统默认赋值是在什么时候完成的呢?
- 是在类加载的时候吗?
- 不是,因为类加载的时候只加载了代码片段,还没来得及创建对象。所以此时实例变量并没有初始化。
- 实际上,实例变量的内存空间是在构造方法执行过程当中完成开辟的,完成初始化的。
- 系统在默认赋值的时候,也是在构造方法执行过程当中完成的赋值。
实例变量默认值:
- byte,short,int,long :0
- float,double :0.0
- 引用数据类型:null
2、对象和引用
2.1 对象和引用的概念
- 对象:目前在使用 new 运算符在堆内存中开辟的内存空间称为对象。
- 引用:是一个变量,不一定是局部变量,还可能是成员变量。引用保存了内存地址,指向了堆内存当中的对象。
- 所有访问实例相关的数据,都需要通过**“引用.”**的方式访问,因为只有通过引用才能找到对象。
- 只有一个空的引用,访问对象的是相关的数据会出现空指针异常。
class Student {
Computer com; // com 是一个引用【实例变量】
public static void dosome() {
Computer cc; // cc 是一个引用【局部变量】
}
}
this 关键字
3.1 关于 java 语言当中的 this 关键字
- this 是一个关键字,翻译为:这个
- this 是一个引用,this 是一个变量,this 变量中保存了内存地址指向了自身,this 存储在 JVM 堆内存 java 对象内部
- 创建100个 java 对象,每一个对象都有 this ,也就是说有100个不同的 this
- this 可以出现在实例方法当中,this 指向当前正在执行这个动作的对象(this 代表当前对象)
- this 在多数情况下都是可以省略不写的,在区分实例变量和局部变量的时候,this 不能省略
- this 不能使用在带有 static 的方法中
实例变量:必须采用“引用”的方式访问(引用.name)
*没有 static 关键字的方法被称为“实例方法”
*没有 static 关键字的变量被称为“实例变量”
3.2 最终结论
Customer.java
package com.heima04;
public class Customer {
// 姓名【堆内存的对象内部中存储,所以访问该数据的时候,必须先创建对象,通过引用的方式访问】
String name; // 实例变量:必须采用“引用”的方式访问
// 构造方法
public Customer() {
}
// 不带有 static 关键字的一个方法
// 顾客购物行为
// 每一个顾客购物最终的结果是不一样的
// 所以购物这个行为为是属于对象级别的行为
// 由于每一个对象咋在执行购物这个动作的时候最终结果不同,所以购物这个动作必须有“对象”的参与。
// 重点:没有 static 关键字的方法被称为“实例方法”
// 重点:没有 static 关键字的变量被称为“实例变量”
public void shopping() {
// System.out.println(name + "在购物");
// 完整写法:(因为 name 是实例变量)
// System.out.println(引用.name + "在购物");
System.out.println(this.name + "在购物");
}
// 带 static 关键字的方法
public static void doSome() {
// 这个执行过程中没有“当前对象”,因为带有 static 的方法是通过类名的方式访问的
// 或者说这个“上下文”当中没有“当前对象”,自然也不存在 this(this 代表的是当前正在执行这个动作的对象)
// 以下程序为什么编译错误呢?
// doSome() 方法调用不是对象去调用,是一个类名去调用,执行过程中没有“当前对象”
// name 是一个“实例变量”,一下代码的含义是:访问当前对象的 name,没有当前对象,自然也不能访问当前对象的 name
// System.out.println(name); // 编译报错
// static 的方法调用不需要对象,直接使用类名,所以执行过程中没有当前对象,所以不能使用 this
// System.out.println(this); // 编译报错
}
public static void doOther() {
// 假设想访问 name 这个实例变量的话应该怎么做?
// 创建对象
Customer c = new Customer();
System.out.println(c.name);
}
}
CustomerTest1.java
package com.heima04;
public class CustomerTest {
public static void main(String[] args) {
Customer c1 = new Customer();
Customer c2 = new Customer();
c1.name = "zhangsan";
c1.shopping(); // 输出“zhangsan在购物”
c2.name = "李四";
c2.shopping(); // 输出“李四在购物”
// 调用 doSome() 方法(修饰符有 static 关键字)
// 类名.方法名,显然这个方法在执行的时候不需要对象参加。
Customer.doSome();
Customer.doOther();
}
}
ThisTest2.java
package com.heima04;
public class ThisTest {
// 带有 static
// 主方法
public static void main(String[] args) {
// 调用 doSome() 方法
ThisTest.doSome();
// 调用 doSome() 方法
doSome(); // 省略写法
// 调用 doOther() 方法。
// 【编译错误】实例方法必须先创建对象,通过引用.的方式访问
// ThisTest.doOther();
// doOther() 是实例方法
// 实例方法调用必须有对象的存在
// 以下代码表示的含义是:调用当前对象的 doOther 方法。
// 但是由于 main 方法中没有 this,所以一下方法不能调用。
// this.doOther();
// 创建对象
ThisTest tt = new ThisTest();
tt.doOther();
tt.run();
}
// 静态方法
public static void doSome() {
System.out.println("do Some!");
}
// 实例方法
public void doOther() {
System.out.println("do Other!");
}
// run 是实例方法,调用 run 方法的一定是有对象存在的。
// 一定是先创建了一个对象才能调用 run 方法。
public void run() {
// 在大括号中的代码执行过程当中一定是存在“当前对象”的。
// 也就是说这里一定是有“this”的。
System.out.println("run!");
// doOther 是一个实例方法,实例方法调用必须有对象的存在。
// 以下代码表示的含义是:调用当前对象的 doOther 方法。
doOther(); // “this.”大部分情况下都是可以省略的
// this.doOther(); 完整写法
}
}
- 在带有 static 的方法中不能“直接”访问实例变量和实例方法
- 因为实例变量和实例方法都需要对象的存在
- 而 static 的方法当中是没有 this 的,也就是说当前对象是不存在的
- 自然也是无法访问当前对象的实例变量和实例方法
3.3 this 可以用在哪里
- 可以使用在实例方法当中,代表当前对象【语法格式:”this.“】
- 可以使用在构造方法当中,通过当前的构造方法调用其他的构造方法【语法格式:this(实参);】
重点:this() 这种语法只能出现在构造函数的第一行
Date.java
package com.heima04;
public class Date {
// 定义属性
private int year;
private int month;
private int day;
// 带参构造函数
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
// 无参构造函数
// 需求:当程序员调用一下无参数的构造方法的时候,默认创建的日期是“1970年1月1日”
public Date() {
/*
this.year = 1970;
this.month = 1;
this.day = 1;
*/
// 以上代码可以通过调用另一个构造方法来完成
// 但前提是不能创建新的对象,以下代码表示创建了一个全新的对象
// new Date(1970, 1, 1);
// 需要采用以下的语法来完成构造方法的调用
// 这种方法不会创建新的 java 对象,但同时又可以达到调用其他的构造方法的目的
this(1970, 1 ,1);
}
// get set 方法
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
// 打印日期
public void print() {
System.out.println(this.year + "年" + this.month + "月" + this.day + "日");
}
}
DateTest3.java
package com.heima04;
public class DateTest {
public static void main(String[] args) {
// 创建对象
Date time1 = new Date();
Date time2 = new Date(2022, 1, 18);
//调用 print 方法
time1.print(); // 输出:1970年1月1日
time2.print(); // 输出:2022年1月18日
}
}
3.4 空指针异常
*什么时候程序在运行的时候会出现空指针异常呢?
空引用访问实例相关的数据,因为实例相关的数据就是对象相关的数据,
这些数据在访问得到时候,必须有对象的参与,当空引用的时候,对象不存在,
访问这些实例数据一定会出现空指针异常
*实例相关的数据包括:
-
实例变量【对象需要存在】
-
实例方法【对象需要存在】
ThisTest4.java
package com.heima04;
public class ThisTest4 {
public static void main(String[] args) {
ThisTest4.doSome();
doSome();
ThisTest4 t = new ThisTest4();
t.doSome();
// 引用是空
t = null;
// 带有 static 的方法,其实既可以采用类名的方式访问,也可以采用引用的方式访问
// 但是即使采用引用的方式访问,实际上执行的啥时候和引用指向的对象无关
// 所以带有 static 的方法还是建议使用“类名.”的方式访问
t.doSome(); // 这里不会出现空指针异常
}
// 带 static 的方法,需要使用“类名.”的方式访问
public static void doSome() {
System.out.println("do Some!");
}
}
static 关键字
4、静态变量
4.1 什么是静态变量
- 带有 static 修饰符的变量是静态变量,静态变量在类加载的时候初始化,不需要创建对象,内存就开辟了
- 静态变量存储在方法区内存当中
4.2 关于 java 中的 static 关键字
- static 翻译为静态的
- static 修饰的方式是静态方法
- static 修饰的变量是静态变量
- 所有 static 修饰的元素都称为静态的,都可以使用“类名.”的方式访问,当然也可以用“引用.”的方式访问【但不建议】
- static 修饰的所有元素都是类级别的特征,和具体的对象无关
4.3 静态变量的使用时机
*什么时候成员变量声明为实例变量呢?
所有对象都有这个属性,但是这个属性的值会随着对象的变化而变化【不同对象的这个属性具体的值不同】
*什么时候成员变量声明为静态变量呢?
所有对象都有这个属性,并且所有对象的这个属性值是一样的,建议定义为静态变量,节省内存的开销
*静态变量在类加载的时候初始化,内存在方法区这种开辟,访问的时候不需要创建对象,直接使用“类名.静态变量名”的方式访问
Chinese.java
package com.heima05;
/**
* "中国人"类
*/
public class Chinese {
// 姓名
String name;
// 身份证号
int id;
// 国籍【所有对象国籍一样,这种特征属于类级别的特征,可以提升为整个模板的特征,可以在变量前添加 static 关键字修饰】
// 带有 static 修饰符的变量是静态变量,静态变量在类加载的时候初始化,不需要创建对象,内存就开辟了
// 静态变量存储在方法区内存当中
static String country = "中国";
// 无参构造函数
public void Chinese() {}
// 带参构造函数
public Chinese(String name, int id) {
this.name = name;
this.id = id;
}
}
ChineseTest.java
package com.heima05;
public class ChineseTest {
public static void main(String[] args) {
// 创建对象张三
Chinese zhangsan = new Chinese("张三", 1);
System.out.println(zhangsan.name + "," + zhangsan.id + "," + Chinese.country);
// 创建对象李四
Chinese lisi = new Chinese("李四", 2);
// 所有静态的数据都是采用“类名.“,也可以采用”.“,但是建议采用”类名.“的方式访问
// 采用引用.的方式访问的时候,即使引用是 null,也不会出现空指针异常
System.out.println(lisi.name + "," + lisi.id + "," + Chinese.country);
}
}
4.4 静态代码块
*可以使用 static 关键字来定义“静态代码块”
-
语法格式
static {
java 语句;
}
-
静态代码块在类加载时执行,并且只执行一次
-
静态代码块在一个类中可以编写多个,并且遵循自上而下的顺序一次执行
-
静态代码块的作用是什么?怎么用?用在哪儿?什么时候用?
-
和具体的需求有关,例如项目中要求在类加载的时刻执行代码完成日志的记录。
那么这段记录日志的代码就可以编写到静态代码块当中,完成日志记录
-
静态代码跨是 java 为程序员准备的一个特殊的时刻,这个特殊的时刻被 称为类加载时刻。
若希望在此时刻执行一段特殊的程序,这段程序可以直接放到静态代码块当中
-
StaticTest01.java
package com.heima05;
public class StaticTest01 {
static {
System.out.println(1);
}
static {
System.out.println(2);
}
public static void main(String[] args) {
System.out.println(3);
}
}
继承
5.1 关于 java 语言当中的继承
-
继承是面向对象三大特征之一,三大特征分别是:封装、继承、多态
-
继承“基本”的作用是:代码复用。但是继承是“重要”的作用是:有了继承才有以后“方法的覆盖”和“多态机制”
-
继承语法格式:
[修饰符列表] class 类名 extends 父类名 {
类体 = 属性 + 方法
}
-
java 语言当中的继承只支持但继承,一个类不能同时继承很多类,只能继承一个类
-
关于继承中的一些术语:
B类继承A类,其中:- A类称为:父类、基类、超类、superclass
- B类称为:子类、派生类、subclass
-
在 java 语言当中子类继承父类哪些数据呢?
- 私有的不支持继承
- 构造方法不支持继承
- 其他数据都可以被继承
-
虽然 java 语言当中只支持单继承,但是一个类也可以间接继承其他类,例如:
C extends B {
}
B extends A {
}
A extends T {
}
C 类直接继承 B 类,但是 C 类间接继承 T、A类 -
java 语言中假设一个类没有显示继承任何类,该类默认继承 JavaSE 库当中提供的 java.lang.Object 类,java 语言中任何一个类都有 Object 类的特征
5.2 方法的覆盖(方法重写)
5.2.1 方法重载回顾
- 方法重载又称为 Overload
- 方法重载什么时候使用?
- 当在同一个类当中,方法完成的功能是相似的,建议方法名相同,这样方便程序员的编程,就像在调用一个方法似的,代码美观
- 什么条件满足之后构成方法重载?
- 在同一个类当中
- 方法名相同
- 参数列表不同:类型、顺序、个数
- 方法重载和什么无关?
- 和方法的返回值类型无关
- 和方法的修饰符列表无关
5.2.2 关于 java 语言中方法的覆盖
- 方法覆盖又被称为方法重写,英语单词:override【官方的】/ overwrite
- 什么时候使用方法重写?
- 当父类中的方法已经无法满足当前子类的业务需求
- 子类有必要将父类中继承过来的方法进行重新编写
- 这个重新编写的过程称为方法重写/方法覆盖
- 什么条件满足之后方法会发生重写呢?
- 方法重写发生在具有继承关系的父子类之间
- 方法重写的时候:返回值类型相同,方法名相同,形参列表相同
- 方法重写的时候:访问权限不能更低,可以更高
- 方法重写的时候:抛出异常不能更多,可以更少
- 注意:
- 私有方法不能继承,所以不能覆盖
- 构造方法不能继承,所以不能覆盖
- 静态方法不存在覆盖
- 覆盖只针对方法,不谈属性
多态
6.1 关于 java 语言当中的多态语法机制
-
关于多态中涉及到的几个概念
-
向上转型(upcasting)
子类型 --> 父类型
又被称为:自动类型转换
-
向下转型(downcasting)
父类型 --> 子类型
又被称为:强制类型转换【需要加强制类型转换符】
-
重点
- 无论是向上转型还是向下转型,两种类型之间必须要有继承关系
- 没有继承关系,程序是无法编译通过的
-
6.2 多态的好处与弊端
- 多态的好处:提高了程序的扩展性
具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作 - 多态的弊端:不能使用子类的特有功能
Animal.java
package com.heima07;
public class Animal {
public void move() {
System.out.println("动物在移动");
}
public void eat() {
System.out.println("动物吃东西");
}
}
Bird.java
package com.heima07;
public class Bird extends Animal {
@Override
public void move() {
System.out.println("飞儿飞翔");
}
@Override
public void eat() {
System.out.println("鸟吃虫");
}
}
Cat.java
package com.heima07;
public class Cat extends Animal {
@Override
public void move() {
System.out.println("猫走猫步");
}
public void catchMouse() {
System.out.println("猫捉老鼠");
}
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
AnimalOperator.java
package com.heima07;
/**
* 动物操作类
*/
public class AnimalOperator {
/*public void useAnimal(Cat c) {
c.eat();
}
public void useAnimal(Bird b) {
b.eat();
}*/
// 可以直接将父类作为参数,使用具体的子类型进行操作
public void useAnimal(Animal a) {
a.eat();
}
}
AnimalTest.java
package com.heima07;
public class AnimalTest {
public static void main(String[] args) {
Cat c = new Cat();
c.move();
c.catchMouse();
Bird b = new Bird();
b.move();
/**
* 1、先分析编译阶段,再分析运行阶段,编译无法通过,根本是无法运行的
* 编译阶段编译器检查 a 这个引用的数据类型为 Animal(编译看左边,运行看右边)
* 2、由于 Animal.class 字节码当中有 move() 方法,所以编译通过了。这个过程我们称为静态绑定,编译阶段绑定。
* 只有静态绑定成功后才有后续的运行
* 3、在程序运行阶段,JVM 堆内存当中真实创建的对象是 Cat 对象,那么一下程序在运行阶段一定会
* 调用 Cat 对象的 move() 方法,此时发生了程序的动态绑定,运行阶段绑定
* 4、无论 Cat 类有没有重写方法,运行阶段一定调用的是 Cat 对象的 move 方法,因为底层真实对象就是 Cat 对象
* 5、父类型引用指向子类型对象这种机制导致程序存在编译阶段绑定和运行阶段绑定两种不同的形态/状态,这种机制可以称为一种多态语法机制
* 6、new Cat()创建的对象的类型是 Cat,a 这个引用的数据类型是 Animal,可见它们进行了类型转换
* 子类型转换成父类型,称为向上转型 / upcasting,或者称为自动类型转换
* java 中允许这种语法:父类型引用指向子类型对象
*/
Animal a = new Cat(); // 向上转型
a.move(); // 输出:猫走猫步
/**
* 分析以下程序为什么不能调用?
* 因为编译阶段编译器检查到 a2 的类型是 Animal 类型,
* 从 Animal.class 字节码文件当中查找 catchMouse()
* 方法,最终没有找到该方法,导致静态绑定失败,没有绑定成
* 功,也就是说编译失败了,别谈运行了
*/
// a.catchMouse();
/**
* 访问子类中的特有方法,使用向下转型
*/
Cat a1 = (Cat)a;
a1.catchMouse();
/**
* - 多态的好处:提高了程序的扩展性
* 具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,
* 使用具体的子类型参与操作
* - 多态的弊端:不能使用子类的特有功能
*/
AnimalOperator ao = new AnimalOperator();
ao.useAnimal(c);
ao.useAnimal(b);
}
}
本文详细介绍了JVM内存区域划分及其工作原理,包括栈内存、堆内存和方法区内存的特点与用途。此外,还深入探讨了Java中的面向对象特性,如构造方法、this关键字、静态变量和方法、继承、方法覆盖与多态等核心概念。

1959

被折叠的 条评论
为什么被折叠?



