Java 对象及其内存控制
作者:禅楼望月(http://www.cnblogs.com/yaoyinglong)
更新:其实这里有好多的变戏法,只要你理解了他们在JVM的中的实现机制,就豁然开朗了。有时间我会把这些变戏法的东西说明的。
Java 向程序员许下了美好的承诺:无需关心内存的回收,Java提供了优秀的垃圾回收机制来回收已经分配的内存。
所以初学者往往会肆无忌惮的挥霍Java内存,从而导致Java程序的运行效率下降,主要坏处为:
- 不断分配内存使得系统中可用内存减少,从而降低程序运行性能;
- (更重要的)大量已分配内存的回收使得垃圾回收的负担加重,降低程序的运行性能。
1 前向应用
这说明Java中定义实例成员变量时,必须采用合法的前向引用。同样两个类成员变量也必须采用合法的前向引用:
但是,如果一个是实例成员变量,一个是类成员变量,则实例成员变量总是可以引用类成员变量:
这是因为:类成员变量初始化时机总是在实例成员变量初始化时机之前,确切的说是在类加载时进行的。
2、静态成员可实例成员
使用static修饰的静态变量属于类本身,而实例变量数据类的实例。在同一JVM中,每个类只对应一个Class对象,因此同一JVM内的一个类的类变量只需一块内存空间,但每个类可以创建多个Java对象,因此JVM必须为每个Java对象的实例变量分配一块内存空间。
Java允许通过类来访问类成员变量,也允许类实例访问类成员变量,(Java这样设计是不合理的)
但是java设计者,却在类实例访问类成员变量时,底层依然转换为类来访问类成员变量。怎么证明呢?
通过反编译来看看:
JVM在底层使用someTh对象所对应的引用类型来调用静态成员,这就给程序员造成了一定的错觉,以为调用的是自己对象的东西,但是改变静态成员的值,在其他的对象的中会体现出来,这个很危险:
在一个类实例中修改了类成员变量的值,在另一个类实例中却体现出来了。
3 实例变量的初始化时机
从语法角度看,我们可以在如下3个地方对实例变量执行初始化:
①定义实例变量是指定初始值
②非静态初始化块中对实例变量指定初始值
③构造器中对实例变量指定初始值。
其中①和②比③更早执行,①和②那个更早执行,就看那个在代码中出现的更早。如:
由此可见类实例变量只能放在构造器中初始化,但是作为程序员编程时,可以放在定义处,也可以放在非静态块中,但是结果都是一样的,JVM会把它们抽取出来放在构造函数中。
4 类变量初始化时机
从语法角度看,可以在如下两个地方对类变量初始化:
①定义类变量时初始化;
②静态初始化块中对类变量指定初始值。
这两种方式的执行顺序和它们在源代码中出现的顺序相同:
由此可见,类变量只能在静态块中被初始化,但是作为程序员编程来说,可以放在定义处也可以放在静态快中,结果都是一样的,JVM会把它们收取出来都放进静态快中。
5 父类构造器
看如下代码:
这里便引发了一个疑问:在这里Sub类还没有被创建(因为调用display的时候父类的构造函数还没有走完,怎么会走子类的构造函数),怎么能调用它的方法呢?
诶!难道类实例不是由构造器创建的吗?
很多书籍中都是这样说的:类实例是由构造器创建。
其实,这句话是完全错误的。实际的情况是构造器只是负责对Java对象实例变量执行初始化(即赋初始值),在执行构造器代码之前,该对象所占的内存已经被分配下来了。这些内存里的值都是各个类型的默认值。
所以上面代码在执行new Sub();的时候系统已经为Sub对象分配了内存空间(两块内存空间,一块用于存放Sub的i另一块用于存放Base的i(这一块内存,子类和父类共用,改变任何一个另一个会跟着动),原因是子类不能完全覆盖父类的成员变量)
注意:
对象是由new关键字创建的,在执行new……的时候,一个Java对象已经建成了,只是它的变量还没有初始化,构造函数的功能就是对这些变量进行初始化。没有运行完构造函数Java对象的方法是可以被调用的,因为它和一般Java对象没有任何的区别。
再来看一段代码:
是否会感觉到this指代有点混乱呢?
但是从打印出来的结果来看,this确实指代的是Sub,但是我们也知道,当this在构造器中this指的是正在被初始化Java对象。怎么理解呢?从源代码看,此时this位于Base构造器中,但是这些代码实际放在Sub()构造器内执行,是Sub()构造器隐式调用了Base()构造器的代码。由此可见,this指的是Sub而不是Base。现在问题又来了,既然this指的是Sub,那么,为什么System.out.println("I come from "+this.getClass()+" -->"+this.i);执行结果却为2?这是因为,虽然,this实际指向的是Sub对象,但是当在Base构造器中时,它的编译类型为Base。所以会输出2.
因此我们可以得出如下结论:
当变量(a)编译时类型和运行是类型不同时,通过该变量(a)访问它引用的对象的实例变量时,该实例变量的值是由声明该变量(a)的类型决定。但当通过该变量调用它引用的对象的实例方法时,该方法行为将由该变量(a)实际所引用的对象来决定。
6 父子实例的内存控制
由上图可知:
1、变量d2b和d实际指向同一个对象,但是访问他们的实例变量时却输出不同的值,这表明d2b和d变量所指向的java对象中包含了两块内存,更别存放着值为2的count实例变量和值为20的count实例变量。
2、不管d、db、d2b,只要它们指向一个Sub对象,不管声明它们使用什么类型,当通过这些变量来调用时,方法的行为总是表现出它们实际类型的行为。但如果通过这些变量来访问它们所指对象的实例变量,这些实例变量的值总是表现出声明这些变量所用的类型的行为。由此可见Java继承在处理成员变量和方法时,是有区别的。
但是,还是可以通过super来调用父类中被覆盖的方法。
我们再来看一下这段代码:
//父类 public class Base { private int x=10; public int getX() { return x; } public void setX(int x) { this.x = x; } }
//子类 public class Sub extends Base { public Sub() { this.setX(20); } }
//测试 public static void main(String[] args) { Base b=new Base(); System.out.println("我是父类:"+b.getX()); //-->10 Base base=new Sub(); System.out.println("我是父类:"+base.getX()); //-->20 Sub s=new Sub(); System.out.println("我是子类:"+s.getX()); //-->20 System.out.println("我是父类:"+base.getX()); //-->20 }
用javap工具查看:
由此可见子类继承了父类的实例变量,内存中值为父类中的变量申请了空间,并没有为子类中该变量开辟内存空间。有人可能说你这里的实例变量x是private,其实即是public也是一样的,不信的话可以试试。
那么,我们在子类中调用setX方法其实,设置的是父类中的实例变量x。因为这个方法是从父类继承过来的。由此也可以得出父类中一般不要设置静态全局变量,这样会有线程安全的问题。
所以在子类中使用super的意思是,使用自己对象里面保存的从父类继承下来的那个方法。
由此可见,super本身并没有引用任何对象,它只能算作一个标记。它的作用仅限于在子类中(不是子类的对象)调用在父类中定义的,被隐藏了的实例变量,或者在子类中定义的,被覆盖的方法。
注意:虽然说这是父类中的方法和变量。其实和父类没有一点关系了。只是在调用上有点区别,其他的和类自己的方法没什么区别。
7 父子类的类变量
记住:Java允许通过实例对象来调用类的静态变量
其他的和实例变量一样。
8 final
final修饰的变量
final修饰的变量必须显示的指定初始值(普通变量系统会为其设置默认值),而且只能在以下3个地方制定初始值:
对于一个final变量而言,不管它是类变量、实例变量还是局部变量,只要该变量被final修饰,并且被赋予的初始值(必须的),那么该在类编译的时候就被确定了,那么,这个final变量就不再是变量了,而是相当于一个直接量。
内部类中的局部变量
如果程序需要在内部类中使用局部变量,那么这个局部变量必须由final修饰。
但是为什么内部类中要访问的局部变量都必须使用final修饰呢?
原因是:对于普通的局部变量,它的作用于就停留在该方法内,该方法结束后该局部变量就消失了;但是内部类则可能产生隐式的“闭包”,闭包将使得局部变量脱离了它所在的方法继续存在。
Java 对象及其内存控制的更多相关文章
- (二)Java对象与内存控制
一.java的类变量和实例变量: java中的变量可分成两种:成员变量和局部变量. 1.局部变量包括以下几类: 方法内的局部变量:作用域为方法体. 代码块内的局部变量:作用域为代码块 形参:方法内的形 ...
- JAVA对象与内存控制
1.1 实例变量和类变量 成员变量和局部变量: 局部变量分为三大类: 1)形参:在方法签名中定义的局部变量,由方法调用者为其赋值,随方法的结束而消亡. 2)方法内的局部变量:在方法内定义的局部变量,随 ...
- Java对象的内存模型(一)
前言 新人一枚,刚刚入门编程不久,各方面都在学习当中,博文有什么错误的地方,希望我们可以多多交流! 最近,在开发App后台过程中,需要将项目部署到云服务器上.而云服务器的内存大小却只有1G.要如何做到 ...
- Java对象的内存布局
对象的内存布局 平时用java编写程序,你了解java对象的内存布局么? 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域: 对象头 实例数据 对齐填充 对象头 对象头包括两部分信息: ...
- JVM总结-java对象的内存布局
在 Java 程序中,我们拥有多种新建对象的方式.除了最为常见的 new 语句之外,我们还可以通过反射机制.Object.clone 方法.反序列化以及 Unsafe.allocateInstance ...
- java对象在内存的大小
前言 一直以来,对java对象大小的概念停留在基础数据类型,比如byte占1字节,int占4字节,long占8字节等,但是一个对象包含的内存空间肯定不只有这些. 假设有类A和B,当new A()或者n ...
- Java对象的内存(一)
前言 新人一枚,刚刚入门编程不久,各方面都在学习当中,博文有什么错误的地方,希望我们可以多多交流! 最近,在开发App后台过程中,需要将项目部署到云服务器上.而云服务器的内存大小却只有1G.要如何做到 ...
- java对象在内存中的分配
java对象在内存中的分配 http://blog.csdn.net/qq_30753945/article/details/54974899
- 10 Java 对象的内存布局
Java 创建对象的方式 1:new 语句和反射机制创建.该方式会调用类的构造器,同时满足诸多约束.如果一个类没有构造器的话,Java 编译器会自动添加一个无参数的构造器.子类的构造器需要调用父类的构 ...
随机推荐
- vector基础操作
//vector< T> vec; //构造一个名为vec的储存数据类型为T的动态数组.其中T为需要储存的数据类型 //初始时vec为空 //push_back 末尾添加一个元素 //po ...
- Centos安装docker#避免很多坑
采用yum方式安装 安装: step 1: 安装必要的一些系统工具 yum install -y yum-utils device-mapper-persistent-data lvm2 Step 2 ...
- 09 mongoDB基础(进阶)
mongoDB基础 阶段一.认识mongodb 1.mongodb 组织数据的基本形式 MongoDB————>数据库————>集合————>文档 mysql:表:行和列:字段 运用 ...
- 齐博cms最新SQL注入网站漏洞 可远程执行代码提权
齐博cms整站系统,是目前建站系统用的较多的一款CMS系统,开源,免费,第三方扩展化,界面可视化的操作,使用简单,便于新手使用和第二次开发,受到许多站长们的喜欢.开发架构使用的是php语言以及mysq ...
- linux execl()函数
关于execl()函数族的用法不在赘述,其他博主介绍的很详细.下面说下作者在使用该函数时所犯的错误: 作者想通过使用execl()函数在子进程中调用其他函数,起初楼主是 这样用的: if((a = e ...
- Moodle 3.4中添加小组、大组、群
Moodle在高中应用时经常要用到年级.班级和小组,我们可以用群.大组.小组来代替. 小组设置:网站首页-->现有课程-->右上角的设置按钮-->更多-->用户-->小组 ...
- Java:详解内部类
可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一个非常有用的特性但又比较难理解使用的特性(鄙人到现在都没有怎么使用过内部类,对内部类也只是略知一二). 第一次见面 内部类我们从外面 ...
- 清除远程桌面连接记录和SQLSERVER 连接记录的办法
1.清除远程桌面连接记录: 清除远程桌面访问痕迹.使用windows系统自带的“远程桌面协助”mstsc进行远程,如果连接的用户多了,会留下访问的痕迹.虽然能带来方便,但是如果对于公用电脑来说,这些访 ...
- 管理员常用Windows PowerShell命令Top25
即使Windows PowerShell已经由来已久,但很多管理员并不愿意主动熟悉PowerShell cmdlet命令行.随着微软扩展了PowerShell的功能,管理员应该对其功能及使用烂熟于心. ...
- jmeter常用的内置变量
1. vars API:http://jmeter.apache.org/api/org/apache/jmeter/threads/JMeterVariables.html vars.get(& ...