Java学习笔记 —— 对象与内存控制(一)

Java的内存管理

Java的内存管理分为两个方面:内存分配内存回收

  • 内存分配:创建Java对象时JVM为该对象在堆内存中所分配的空间。
  • 内存回收:当Java对象失去引用,变成垃圾时,JVM的垃圾回收机制自动清理该对象,并回收该对象所占用的内存。

由于JVM的垃圾回收机制是由一条后台线程完成,其本身也是消耗性能的,因此如果肆无忌惮的创建对象,让系统分配内存,那这些分配的内存都将由垃圾回收机制进行回收,这样做有两个坏处:

  • 不断分配内存导致系统中可用的内存减少,从而降低系统的运行性能。
  • 大量已分配的内存的回收使得垃圾回收的负担加重,降低程序的运行性能。

Java中的变量

Java中的变量大体可分为成员变量局部变量两大类。

  • 局部变量:作用时间短暂,存储在方法的栈内存中。
分类解释
形参在方法签名中定义的全局变量,由方法的调用者为其赋值,随方法的结束而消亡
方法内的局部变量在方法内部定义的局部变量,必须在方法内对其进行显式初始化,这种类型的变量,从初始化完成开始生效,随方法的结束而消亡
代码块中的局部变量必须在代码块内显式的初始化,从初始化完成开始生效,随方法的结束而消亡
  • 成员变量:在类体内定义的变量
分类解释
非静态变量(实例变量)没有static修饰
静态变量(类变量)有static修饰
  • 关于static关键字
    static的作用就是将实例变量变为类变量,static只能修饰类里定义的成员
    部分,包括成员变量、方法、内部类、初始化块、内部枚举类,不能修饰局部变量、外部类、局部内部类。
  • 成员变量的前向引用
// 示例1
public class ParamTest1{
	int num2 = num1 + 1; //非法前向引用,报错
	int num1 = 1;
}
// 示例2
public class ParamTest2{
	static int num2 = num1 + 1; //非法前向引用,报错
	static int num1 = 1;
}
// 示例3
public class ParamTest3{
	int num2 = num1 + 1; // 正确,因为类变量的初始化时机总在实例变量之前
	static int num1 =1;
}

实例变量与类变量的属性

  • 由于在同一个JVM内每一个类只对应一个Class对象,因此同一个JVM内的一个类的类变量只需一块内存空间,但对于实例变量来说,该类每创建一次实例,就需要为实例变量分配一块内存空间。换句话说,程序中有多少个实例就需要为实例变量开辟几块内存空间,而类变量只需一块。
  • 类变量的使用:既可以通过 类名.变量名 的方式直接使用,也可通过类的任意实例像访问实例变量那般访问。不论通过哪种方式对类变量的值进行改变,其他实例访问时得出的值也将发生相应的变化。即类变量是共享的!
public class PramaTest4{
	public static void main(String[] args){
		 // 直接通过 类名.类变量名 使用类变量,因为 类变量 属于该类本身,
		 // 只要类初始化完成,程序即可使用类变量
		Person.eyeNum = 2;
		System.out.println(Person.eyeNum + "只眼睛");
		Person p1 = new Person();
		p1.name = "zhangsan";
		p1.age = 21;
		System.out.println(p1.eyeNum + "只眼睛"); // 通过 p1 访问 eyeNum;
		Person p2 = new Person();
		p2.name = "lisi";
		p2.age = 22;
		p2.eyeNum = 3; // 通过 p2 修改 eyeNum
		System.out.println(Person.eyeNum + "只眼睛"); // "3只眼睛“
		System.out.println(p1.eyeNum + "只眼睛"); //  "3只眼睛“
		System.out.println(p2.eyeNum + "只眼睛"); //  "3只眼睛“
	}
}
class Person{
	String name;
	int age;
	static int eyeNum;
	public void info(){
		System.out.println("姓名:" + name + ",年龄:" + age + ",“ + eyeNum + "只眼睛");
	}
}

类变量的内存示意图

  • 实例变量的初始化时机
    1、定义实例化变量时指定初始值;
    2、非静态初始化块中对实例变量指定初始值;
    3、构造器中对实例变量指定初始值;
    其中,第1,2种方式较第3种方式更早执行,第1、2种方式的执行顺序依据它们在代码中的先后关系确定。
public class ParamTest5{
	public static void main(String[] args){
		Person p3 = new Person("wangwu", 23);
		p3.info(); // 体重是55.0
		Person p4 = new Person("xiaoming", 24);
		p4.info(); // 体重是55.0
	}
}
class Person{
	String name;
	int age;
	public Person(String name, int age){
		this.name = name;
		this.age = age;
	}
	{
		weight = 50.0;
	}
	double  weight = 55.0;
	public void info(){
		System.out.println("姓名是:" + name + ",年龄是:" + age + ",体重是:" + weight);
	}
}

定义实例化变量时指定的初始化值、初始化块中为实例变量指定的初始化值的语句是平等的,当经过编译器处理后,它们都将被提取到构造器中。也就是说,对于类中的定义语句:

double weight = 55.0;

实际上被分为了如下两次执行:

double weight; // 创建Java对象时系统根据该语句为该变量分配内存
weight = 55.0; // 这条语句将会被提取到构造器中执行
  • 类变量的初始化时机
    1、定义类变量时指定初始值;
    2、静态初始化块中对类变量指定初始值;
    这两种方式的执行顺序依据它们在代码中的先后关系确定。类变量属于Java类本身,只有当程序初始化该Java类时,才会为该类的类变量分配内存空间,并执行初始化。从程序运行的角度来看,每JVM对一个Java类只初始化一次,因此Java程序每运行一次,系统只为类变量分配一次内存空间,执行一次初始化。
public class ParamTest6{
	public static void main(String[] args){
		System.out.println(Person.eyeNum); // 3
	}
}
class Person{
	String name;
	int age;
	static int eyeNum = 2;
	static{
		eyeNum = 3;
	}
}

难点

class Price{
	// 类成员是Price的实例
	final static Price INSTANCE = new Price(2.8);
	// 再定义一个类变量
	static double initPrice = 20;
	// 定义该Price的CurrentPrice实例变量
	double currentPrice;
	public Price(double discount){
		// 根据静态变量计算实例变量
		currentPrice = initPrice - discount;
	}
}
public class PriceTest{
	public static void main(String[] args){
		// 通过Price的INSTANCE访问currentPrice实例变量
		System.out.println(Price.INSTANCE.currentPrice); // 输出:-2.8
		// 显式创建 Price 实例
		Price p = new Price(2.8);
		// 通过显式创建的Price 实例访问currentPrice变量
		System.out.println(p.currentPrice); // 输出:17.2
	}
}

这个难点内容,起初一看,一脸懵逼,以至于一篇文章分了两段来写,哈哈,感叹基础的重要性!跟着解析看了几遍,颇有收获,特来分享我的理解,如有错误,欢迎批评指正!
首先我们来看第一次调用Syso语句

System.out.println(Price.INSTANCE.currentPrice); // 输出 -2.8

对于这个问题的解答,我们的分析还是要回归到内存管理上去。我们通过Price.INSTANCE访问类变量的方式对Price进行初始化,我们上面曾经讲过,类中的定义语句实际上会被分为两步执行,第一步是依照变量类型为变量分配存储空间,第二步才是为变量赋值。所以在初始化阶段的第一步中,系统为变量分配内存空间,此时,INSTANCE的系统默认值为null,而initPrice的默认值为0;接着执行第二步,为变量赋值,INSTANCE首先被赋值,INSTANCE = new Price(2.8),这时,因为会创建Price实例

public Price(double discount){
	currentPrice = initPrice - discount;
}

而与此同时,initPrice还未被赋值而依旧是其默认值0,所以此时 currentPrice = -2.8。INSTANCE赋值完后,才是 initPrice 的赋值操作 initPrice = 20;而此时对INSTANCE的currentPrice实例变量已经不起作用了!

然后,我们来看第二个Syso语句

Price p = new Price(2.8)
System.out.println(p.currentPrice); // 输出 -2.8

首先,我们要明确一点,经过第一个Syso的执行,实际上我们已经初始化好了两个类变量,前面已经说过了,每JVM对一个Java类只初始化一次,因此Java程序每运行一次,系统只为类变量分配一次内存空间,执行一次初始化,所以当再次创建Price实例时,initPrice = 20,currentPrice = 20 - dicount,自然就能得到17.2了!

我想,经过上面的解答,思路应该稍稍开拓了一些吧!表达可能有些差强人意,大体上还是过的去吧!如有不对的地方,还请指正!

下一篇:对象与内存控制(二)

对象与内存控制(二)

所谓迷茫,就是你的才华还配不上你的梦想

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值