疯狂java讲义对象!

Java对象实例的创建时间
精品学习网考试频道小编应广大考生的需要,特为参加考试的考生策划了&Java对象实例的创建时间&专题等有关资料,供考生参考!
对象实例何时被创建,这个问题也许你用一句话就能回答完了。但是它的潜在陷阱却常常被人忽视,这个问题也许并不像你想的那么简单,不信请你耐心看下去。
我前几天问一个同学,是不是在调用构造函数后,对象才被实例化?他不假思索的回答说是。
请看下面代码:
Date date=new Date();
em.out.println(date.getTime());
新手在刚接触构造函数这个概念的时候。他们常常得出这样的结论:对象实例是在调用构造函数后创建的。因为调用构造函数后,调用引用(date)的实例方法便不会报NullPointerException的错误了。
二、经验者的观点
然而,稍稍有经验的Java程序员便会发现上面的解释并不正确。这点从构造函数中我们可以调用this关键字可以看出。
请看下面代码:
public class Test
public Test()
this.DoSomething();
private void DoSomething()
System.out.println(&do init&);
这段代码中我们在构造函数中已经可以操作对象实例。这也就证明了构造函数其实只是用于初始化,早在进入构造函数之前。对象实例便已经被创建了。
三、父类构造函数
当创建一个有父类的子类的时候。对象的实例又是何时被创建的呢?我们也许接触过下面经典的代码:
public class BaseClass
public BaseClass()
System.out.println(&create base&);
public class SubClass
public SubClass()
System.out.println(&create sub&);
public static void main(String[] args)
new SubClass();
结果是先输出create base,后输出create sub。这个结果看起来和现实世界完全一致,先有老爸,再有儿子。因此我相信有很多程序员跟我一样会认为new SubClass()的过程是:实例化BaseClass-&调用BaseClass构造函数初始化-&实例化SubClass-&调用SubClass构造函数初始化。然而非常不幸的是,这是个错误的观点。
四、奇怪的代码
以下代码是为了驳斥上面提到的错误观点。但是这种代码其实在工作中甚少出现。
public class BaseClass
public BaseClass()
System.out.println(&create base&);
protected void init()
System.out.println(&do init&);
public class SubClass
public SubClass()
System.out.println(&create sub&);
protected void init()
assert this!=
System.out.println(&now the working class is:&+this.getClass().getSimpleName());
System.out.println(&in SubClass&);
public static void main(String[] args)
new SubClass();
这段代码运行的结果是先调用父类的构造函数,再调用子类的init()方法,再调用子类的构造函数。
这是一段奇妙的代码,子类的构造函数居然不是子类第一个被执行的方法。我们早已习惯于通过super方便的调用父类的方法,但是好像从没这样尝试从父类调用子类的方法。
再次声明,这只是个示例。是为了与您一起探讨对象实例化的秘密。通过这个示例,我们再次印证了开头的观点:早在构造函数被调用之前,实例便已被创造。若该对象有父类,则早在父类的构造函数被调用之前,实例也已被创造。这让java显得有些不面向对象,原来老子儿子其实是一块儿出生的。
五、奇怪但危险的代码
本篇是对上篇奇怪代码的延续。但是这段代码更加具有疑惑性,理解不当将会让你出现致命失误。
请看下面代码:
public class BaseClass {
public BaseClass()
System.out.println(&create base&);
protected void init() {
System.out.println(&in base init&);
public class SubClass extends BaseClass{
int i=1024;
String s=&13 leaf&;
public SubClass()
System.out.println(&create sub&);
protected void init() {
assert this!=
System.out.println(&now the working class is:&+this.getClass().getSimpleName());
System.out.println(&in SubClass&);
/////////////great line/////////////////
System.out.println(i);
System.out.println(s);
public static void main(String[] args) {
new SubClass();
//oh!my god!!
更多内容进入:在C或者C++里经常会通过sizeof来计算一个对象所占空间的大小,但是对于java对象,如何获得其大小呢?
该问题被发起重新开启投票
投票剩余时间:
之前被关闭原因:
该问题被发起删除投票
投票剩余时间:
距离悬赏到期还有:
参与关闭投票者:
关闭原因:
该问题已经被锁定
锁定原因:()
保护原因:避免来自新用户不合宜或无意义的致谢、跟帖答案。
该问题已成功删除,仅对您可见,其他人不能够查看。
java.lang. instrument包中有个接口:public interface Instrumentation 此类提供检测 Java 编程语言代码所需的服务。
此类中有个方法:getObjectSize(Object objectToSize)返回指定对象使用的特定于实现的近似存储量。
要想使用这个方法当然要先获取Instrumentation的实例,有两种方法:1.permain方法。在代理行指定代理jar,在main方法启动前启动一个代理程序。2.agentmain方法。不同于permain,在JVM启动后动态添加代理。
用Unsafe, 主要代码引自:
@Testpublic void testea() throws Exception{
System.out.println(sizeOf1(new Object()));
System.out.println(sizeOf1(new TestTesteTestB()));}
private long sizeOf(Object o) throws Exception {
Unsafe u = getUnsafe();
HashSet&Field& fields = new HashSet&Field&();
Class c = o.getClass();
while (c != Object.class) {
for (Field f : c.getDeclaredFields()) {
if ((f.getModifiers() & Modifier.STATIC) == 0) {
fields.add(f);
c = c.getSuperclass();
long maxSize = 0;
for (Field f : fields) {
long offset = u.objectFieldOffset(f);
System.out.println("sdfsd"+offset);
if (offset & maxSize) {
return ((maxSize / 8) + 1) * 8;}private Unsafe getUnsafe() throws Exception {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe) theUnsafe.get(null);}
我的运行结果:832
看了一些资料, 这里说一下我的理解: 1). Java对象实例大小都是8的倍数;2). 开头为 4字节java对象头, 4字节为 类对象(Klass) 的引用; 然后是对象 实例成员(field);3). 对象 实例成员的存储, java不一定按照类定义的顺序, 为了优化存储空间, 会做重排;4). 为了对齐, 可能会做padding.
这样, 上面的sizeof中, 先拿到偏移对大的那个field, 再做padding((maxSize / 8) + 1) * 8;); 从而拿到对象的大小.
Object对象为8个字节, 这已经是最小的了(因为其没有额外的field定义).
不是您所需,查看更多相关问题与答案
德问是一个专业的编程问答社区,请
后再提交答案
关注该问题的人
共被浏览 (6345) 次Java对象的生命周期
每天15篇文章
不仅获得谋生技能
更可以追随信仰
Java对象的生命周期
作者: JAVA2010CZP,发布于
要理解java对象的生命周期,我们需要要明白两个问题,
1、java是怎么分配内存的 ,2、java是怎么回收内存的。
喜欢java的人,往往因为它的内存自动管理机制,不喜欢java的人,往往也是因为它的内存自动管理。我属于前者,这几年的coding经验让我认识到,要写好java程序,理解java的内存管理机制是多么的重要。任何语言,内存管理无外乎分配和回收,在C中我们可以用malloc动态申请内存,调用free释放申请的内存;在C++中,我们可以用new操作符在堆中动态申请内存,编写析构函数调用delete释放申请的内存;那么在java中究竟是内存怎样管理的呢?要弄清这个问题,我们首先要了解java内存的分配机制,在java虚拟机规范里,JVM被分为7个内存区域,但是规范这毕竟只是规范,就像我们编写的接口一样,虽然最终行为一致,但是个人的实现可能千差万别,各个厂商的JVM实现也不尽相同,在这里,我们只针对sun的Hotspot虚拟机讨论,该虚拟机也是目前应用最广泛的虚拟机。
虚拟器规范中的7个内存区域分别是三个线程私有的和四个线程共享的内存区,线程私有的内存区域与线程具有相同的生命周期,它们分别是:
指令计数器、 线程栈和本地线程栈,四个共享区是所有线程共享的,在JVM启动时就会分配,分别是:方法区、
常量池、直接内存区和堆(即我们通常所说的JVM的内存分为堆和栈中的堆,后者就是前面的线程栈)。接下来我们逐一了解这几个内存区域。
1 指令计数器。我们都知道java的多线程是通过JVM切换时间片运行的,因此每个线程在某个时刻可能在运行也可能被挂起,那么当线程挂起之后,JVM再次调度它时怎么知道该线程要运行那条字节码指令呢?这就需要一个与该线程相关的内存区域记录该线程下一条指令,而指令计数器就是实现这种功能的内存区域。有多少线程在编译时是不确定的,因此该区域也没有办法在编译时分配,只能在创建线程时分配,所以说该区域是线程私有的,该区域只是指令的计数,占用的空间非常少,所以虚拟机规范中没有为该区域规定OutofMemoryError。
2、线程栈。先让我看以下一段代码:
class Test{
public static void main(String[] args) {
Thread th = new Thread();
th.start();
在运行以上代码时,JVM将分配一块栈空间给线程th,用于保存方法内的局部变量,方法的入口和出口等,这些局部变量包括基本类型和对象引用类型,这里可能有人会问,java的对象引用不是分配在堆上吗?有这样疑惑的人,可能是没有理解java中引用和对象之前的区别,当我们写出以下代码时:
public Object test()
Object obj = new Object();
其中的Object obj就是我们所说的引用类型,这样的声明本身是要占用4个字节,而这4个字节在这里就是在栈空间里分配的,准确的说是在线程栈中为test方法分配的栈帧中分配的,当方法退出时,将会随栈帧的弹出而自动销毁,而new
Object()则是在堆中分配的,由GC在适当的时间收回其占用的空间。每个栈空间的默认大小为0.5M,在1.7里调整为1M,每调用一次方法就会压入一个栈帧,如果压入的栈帧深度过大,即方法调用层次过深,就会抛出StackOverFlow,,SOF最常见的场景就是递归中,当递归没办法退出时,就会抛此异常,Hotspot提供了参数设置改区域的大小,使用-Xss:xxK,就可以修改默认大小。
3、本地线程栈。顾名思义,该区域主要是给调用本地方法的线程分配的,该区域和线程栈的最大区别就是,在该线程的申请的内存不受GC管理,需要调用者自己管理,JDK中的Math类的大部分方法都是本地方法,一个值得注意的问题是,在执行本地方法时,并不是运行字节码,所以之前所说的指令计数器是没法记录下一条字节码指令的,当执行本地方法时,指令计数器置为undefined。
接下来是四个线程共享区。
1、方法区。这块区域是用来存放JVM装载的class的类信息,包括:类的方法、静态变量、类型信息(接口/父类),我们使用反射技术时,所需的信息就是从这里获取的。
2、常量池。当我们编写如下的代码时:
class Test1{
private final int size=50;
这个程序中size因为用final修饰,不能再修改它的值,所以就成为常量,而这常量将会存放在常量区,这些常量在编译时就知道占用空间的大小,但并不是说明该区域编译就固定了,运行期也可以修改常量池的大小,典型的场景是在使用String时,你可以调用String的
intern(),JVM会判断当前所创建的String对象是否在常量池中,若有,则从常量区取,否则把该字符放入常量池并返回,这时就会修改常量池的大小,比如JDK中java.io.ObjectStreamField的一段代码:
ObjectStreamField(Field field, boolean unshared, boolean showType) {
this.field =
this.unshared =
name = field.getName();
Class ftype = field.getType();
type = (showType || ftype.isPrimitive()) ? ftype : Object.
signature = ObjectStreamClass.getClassSignature(ftype).intern();
这段代码将获取的类的签名放入常量池。HotSpot中并没有单独为该区域分配,而是合并到方法区中。
3、直接内存区。直接内存区并不是JVM可管理的内存区。在JDK1.4中提供的NIO中,实现了高效的R/W操作,这种高效的R/W操作就是通过管道机制实现的,而管道机制实际上使用了本地内存,这样就避免了从本地源文件复制JVM内存,再从JVM复制到目标文件的过程,直接从源文件复制到目标文件,JVM通过DirectByteBuffer操作直接内存。
4、堆。主角总是最后出场,堆绝对是JVM中的一等公民,绝对的主角,我们通常所说的GC主要就是在这块区域中进行的,所有的java对象都在这里分配,这也是JVM中最大的内存区域,被所有线程共享,成千上万的对象在这里创建,也在这里被销毁。
java内存分配到这就算是一个完结了,接下来我们将讨论java内存的回收机制,内存回收主要包含以下几个方面理解:
第一,局部变量占用内存的回收,所谓局部变量,就是指在方法内创建的变量,其中变量又分为基本类型和引用类型。如下代码:
public void test()
char y='a';
long z=10L;
变量x y z即为局部变量,占用的空间将在test()所在的线程栈中分配,test()执行完了后会自动从栈中弹出,释放其占用的内存,再来看一段代码:
public void test2()
Date d = new Date();
System.out.println("Now is "+d);
我们都知道上述代码会创建两个对象,一个是Date d另一个是new Date。Date
d叫做声明了一个date类型的引用,引用就是一种类型,和int x一样,它表明了这种类型要占用多少空间,在java中引用类型和int类型一样占用4字节的空间,如果只声明引用而不赋值,这4个字节将指向JVM中地址为0的空间,表示未初始化,对它的任何操作都会引发空指针异常。
如果进行赋值如d = new Date()那么这个d就保存了new Date()这个对象的地址,通过之前的内存分配策略,我知道new
Date()是在jvm的heap中分配的,其占用的空间的回收我们将在后面着重分析,这里我们要知道的是这个Date
d所占用的空间是在test2()所在的线程栈分配的,方法执行完后同样会被弹出栈,释放其占用的空间。
第二,非局部变量的内存回收,在上面的代码中new Date()就和C++里的new创建的对象一样,是在heap中分配,其占用的空间不会随着方法的结束而自动释放需要一定的机制去删除,在C++中必须由程序员在适当时候delete掉,在java中这部分内存是由GC自动回收的,但是要进行内存回收必须解决两问题:那些对象需要回收、怎么回收。判定那些对象需要回收,我们熟知的有以下方法:
一,引用计数法,这应是绝大数的的java 程序员听说的方法了,也是很多书上甚至很多老师讲的方法,该方法是这样描述的,为每个对象维护一个引用计数器,当有引用时就加1,引用解除时就减1,那些长时间引用为0的对象就判定为回收对象,理论上这样的判定是最准确的,判定的效率也高,但是却有一个致命的缺陷,请看以下代码:
package com.mail.
import java.util.ArrayL
import java.util.L
public class Test {
private byte[]
public Test() {
this.buffer = new byte[4*];
this.ls = new ArrayList();
private List getList() {
public static void main(String[] args) {
Test t1 = new Test();
Test t2 = new Test();
t1.getList().add(t2);
t2.getList().add(t1);
Test t3 = new Test();
System.out.println(t3);
我们用以下参数运行:-Xmx10M -Xms10M M 将jvm的大小设置为10M,不允许扩展,按引用计数法,t1和t2相互引用,他们的引用计数都不可能为0,那么他们将永远不会回收,在我们的环境中JVM共10M,t1
t2占用8m,那么剩下的2M,是不足以创建t3的,理论上应该抛出OOM。但是,程序正常运行了,这说明JVM应该是回收了t1和t2的我们加上-XX:+PrintGCDetails运行,将打印GC的回收日记:
[GC [DefNew: 252K->64K(960K), 0.0030166 secs][Tenured: 8265K->137K(9216K), 0.0109869 secs] 8444K->137K(10176K),
[Perm : 2051K->K)], 0.0140892 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
com.mail.czp.Test@2ce908
def new generation
total 960K, used 27K [0x029eaeae0000)
eden space 896K,
3% used [0x029ee6c40, 0x02ac0000)
from space 64K,
0% used [0x02adadae0000)
space 64K,
0% used [0x02acacad0000)
tenured generation
total 9216K, used 4233K [0x02aeee0000)
the space 9216K,
45% used [0x02aef0f0e0000)
compacting perm gen
total 12288K, used 2077K [0x033efee0000)
the space 12288K,
16% used [0x033ee74d8, 0x035efe0000)
No shared spaces configured.
从打印的日志我们可以看出,GC照常回收了t1 t2,这就从侧面证明jvm不是采用这种策略判定对象是否可以回收的。
二,根搜索算法,这是当前的大部分虚拟机采用的判定策略,GC线程运行时,它会以一些特定的引用作为起点称为GCRoot,从这些起点开始搜索,把所用与这些起点相关联的对象标记,形成几条链路,扫描完时,那些没有与任何链路想连接的对象就会判定为可回收对象。具体那些引用作为起点呢,一种是类级别的引用:静态变量引用、常量引用,另一种是方法内的引用,如之前的test()方法中的Date
d对new Date()的引用,在我们的测试代码中,在创建t3时,jvm发现当前的空间不足以创建对象,会出发一次GC,虽然t1和t2相互引用,但是执行t1=t2=null后,他们不和上面的3个根引用中的任何一个相连接,所以GC会判定他们是可回收对象,并在随后将其回收,从而为t3的创建创造空间,当进行回收后发现空间还是不够时,就会抛出OOM。
接下来我们就该讨论GC 是怎么回收的了,目前版本的Hotspot虚拟机采用分代回收算法,它把heap分为新生代和老年代两块区域,如下图:
默认的配置中老年代占90% 新生代占10%,其中新生代又被分为一个eden区和两个survivor区,每次使用eden和其中的一个survivor区,一般对象都在eden和其中的一个survivor区分配,但是那些占用空间较大的对象,就会直接在老年代分配,比如我们在进行文件操作时设置的缓冲区,如byte[]
buffer = new byte[],这样的对象如果在新生代分配将会导致新生代的内存不足而频繁的gc,GC运行时首先会进行会在新生代进行,会把那些标记还在引用的对象复制到另一块survivor空间中,然后把整个eden区和另一个survivor区里所有的对象进行清除,但也并不是立即清除,如果这些对象重写了finalize方法,那么GC会把这些对象先复制到一个队列里,以一个低级别的线程去触发finalize方法,然后回收该对象,而那些没有覆写finalize方法的对象,将会直接被回收。在复制存活对象到另一个survivor空间的过程中可能会出现空间不足的情况,在这种情况下GC回直接把这些存活对象复制到老年代中,如果老年代的空间也不够时,将会触发一次Full
GC,Full gc会回收老年代中那些没有和任何GC Root相连的对象,如果Full GC后发现内存还是不足,将会出现OutofMemoryError。
Hotspot虚拟机下java对象内存的分配和回收就算完结了,后续我们将讨论java代码的重构。
更多课程...&&&
希望我们的资料可以帮助你学习,也欢迎投稿&提建议给我
频道编辑:winner
邮&&&&&&&件:winner@
&&京ICP备号&&京公海网安备号用心创造滤镜
扫码下载App
汇聚2000万达人的兴趣社区下载即送20张免费照片冲印
扫码下载App
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
除了自己一无所有的北漂!!
LOFTER精选
网易考拉推荐
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
阅读(6572)|
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
loftPermalink:'',
id:'fks_',
blogTitle:'JAVA对象转换为JSON字符串',
blogAbstract:'\r\n文转自:'
{list a as x}
{if x.moveFrom=='wap'}
{elseif x.moveFrom=='iphone'}
{elseif x.moveFrom=='android'}
{elseif x.moveFrom=='mobile'}
${a.selfIntro|escape}{if great260}${suplement}{/if}
{list a as x}
推荐过这篇日志的人:
{list a as x}
{if !!b&&b.length>0}
他们还推荐了:
{list b as y}
转载记录:
{list d as x}
{list a as x}
{list a as x}
{list a as x}
{list a as x}
{if x_index>4}{break}{/if}
${fn2(x.publishTime,'yyyy-MM-dd HH:mm:ss')}
{list a as x}
{if !!(blogDetail.preBlogPermalink)}
{if !!(blogDetail.nextBlogPermalink)}
{list a as x}
{if defined('newslist')&&newslist.length>0}
{list newslist as x}
{if x_index>7}{break}{/if}
{list a as x}
{var first_option =}
{list x.voteDetailList as voteToOption}
{if voteToOption==1}
{if first_option==false},{/if}&&“${b[voteToOption_index]}”&&
{if (x.role!="-1") },“我是${c[x.role]}”&&{/if}
&&&&&&&&${fn1(x.voteTime)}
{if x.userName==''}{/if}
网易公司版权所有&&
{list x.l as y}
{if defined('wl')}
{list wl as x}{/list}

我要回帖

更多关于 java面向对象 的文章

 

随机推荐