对象对于java程序员来说,那是想要多少就有多少,所以那些嘲笑程序员的单身狗,哼,只有无知使你们快乐,想我大java开发,何曾缺少过对象。我们不仅仅知道创建对象,还知道创建对象的过程是啥样的,不信?往下看。
一、论程序员的对象由来
我作为java程序员都知道newObject()可以创建一个对象,那么newObject()为啥就能创建一个对象呢,首先我们需要了解对象是怎么一步步创建的:
1.检查加载
首先我们要检查这个指令的参数能否在常量池中定位到一个类的符号引用(符号引用我理解就是由于对象具体地址位置,用字面量来代替,等运行时再用实际的引用地址替换),并检查类是否已经被加载,解析,初始化,(作为严谨的程序员,对象还是要先检查有没有男朋友,有没有结婚,坚决不做接盘侠)
2.分配内存
确定单身之后,那就要给她一个家,虚拟机会给新生对象分配内存(内存大小是确定的)。内存分配的方式主要有两种流派:指针碰撞,空闲列表
2.1指针碰撞:当需要内存时,将指针向后移动一定大小的内存,指针将已用内存和未使用内存分开。这种方式要求内存是完全规整的。
2.2空闲列表:这种方式虚拟机需要维护一个列表,记录哪些内存块可用,在内存分配时从列表中找到一个足够大的内存区域分配给对象实例(必须是连续的内存区域),并更新列表上的记录,这种方式看着就比上述指针碰撞复杂,但是并不是所有的垃圾收集器都可以贤惠的将内存规规整整的分好(具体后面垃圾回收章节会细讲)。
怎么选择其实上面也算是讲到了,指针碰撞简单粗暴,要求你有个贤惠的垃圾回收器,空闲列表不需要那么高的要求,但是你自己做的就多一点。对象的创建很频繁,可以使用指针碰撞自然最好,否则就用空闲列表,而在如此频繁的对象创建过程,内存分配是否会出现并发安全的问题呢,答案是会,那么我们来了解一下jvm虚拟机是如何处理并发分配内存的问题呢:
解决这个问题主要有两种方案:1.对内存分配进行同步处理,其实虚拟机采用cas加失败重试保证更新操作的原子性,
3.内存空间初始化
小伙伴们,有没有发现实例对象明明只定义了一个引用,但是却依然可以访问(如int为0,普通对象为null),不过局部变量不可以,你想不初始化就用,编译器不会放过你的。这一步基本可以保证所有的没有初始化的对象都可以使用。
4.设置
这一步虚拟机会对你要创建的对象做必要的设置,比如对象是哪个类的实例,类的元数据信息设置,对象哈希码,GC分代年龄信息等,这些信息存放在对象的对象头中,这一步完成,从虚拟机角度来说,新对象诞生了,也可以看出,这一步之前,除对象头之外的所有信息都创建完成。
5.对象初始化
这一步才是我们普通码农角度的new对象,就是执行构造方法,进行对象初始化。
很神奇,我们明明就用了一个new,jvm却做了那么多事情,jvm还是很贴心的,作为java程序员还是很幸福的,但是同时,java程序员的差距也是很大的,正是因为jvm保姆太贴心,导致很多做java的只会写crud,尤其对于我们这些跨专业的人来说,体现的更是淋漓尽致,所以还是加油吧。
二、剥开对象的神秘外衣
对象创建完了,那么对象她的内涵是啥样的呢,是的你没听错,我们程序员最注重的还是内涵,那就让我们剥开她神秘的外衣,一探究竟吧:
首先说一下,这张图是我凭实力copy过来的,对象的存储布局主要分为对象头,实例数据,对其填充,
对象的另一个组成部分实例数据应该没什么好说的,就是存储的数据嘛
对其填充其实就是hotsportvm要求对象大小必须为8字节整数倍,所以当不满8字节的时候,就需要这部分填满。
三、如何找对象
言归正传,本节讲的是如何找对象,找对象呢一般分为两种,媒婆介绍和自己直接认识,媒婆介绍呢就是其实就是我不知道我的对象在哪,但是我能找到媒婆,她知道我的对象在哪,这种就是句柄的方式,直接认识就很简单粗暴了,自己认识,直接找就行了,这种在程序员的世界也叫直接指针。
句柄:java堆中会划分一块内存做句柄池,句柄中包含对象的具体地址信息,我们的引用存储句柄地址
直接指针:对象引用存储的是对象的地址
估计要是有弹幕肯定很多人都会打出:裤子都脱了你就给我看这个?哈哈那我们聊一块钱正经的。
四、对象的引用
对象的引用主要分为四种:强引用,软引用,弱引用,虚引用
1.强引用:一般Objectobj=newObject();就属于强引用,强引用对象只要有强引用关联(GCRoot可达),垃圾回收器就不会回收。这种是我们平时用的最多的,毕竟new对象嘛。
2.软引用:软引用就是比强引用稍次一点的,它在系统将要发生内存溢出时,这些对象就会被回收。这种比较适合用来做缓存这种可以被随时回收,但是又不会经常被出发回收。我们看下代码:
publicclassTestSoftRef{//对象publicstaticclassUser{publicintid=0;publicStringname="";publicUser(intid,Stringname){super();this.id=id;this.name=name;}@OverridepublicStringtoString(){return"User[id="+id+",name="+name+"]";}}//publicstaticvoidmain(String[]args){Useru=newUser(1,"King");//new是强引用SoftReference
可以看到第一次gc甚至fullGC之后,对象依然没有被回收,随后循环多次,执行了多次FullGc,但每次都没回收多少,而且一直内存占用很高,这说明即将oom了,明显报错之后,对象被回收了。
3.弱引用:弱引用就是更次一点了,用的地方不太多,比如经典的ThreadLocal(也因为这个存在内存泄漏问题),弱引用在每次gc时都会回收对象,看代码:
publicclassTestWeakRef{publicstaticclassUser{publicintid=0;publicStringname="";publicUser(intid,Stringname){super();this.id=id;this.name=name;}@OverridepublicStringtoString(){return"User[id="+id+",name="+name+"]";}}publicstaticvoidmain(String[]args){Useru=newUser(1,"King");WeakReference
很明显结果是被回收了,所以也印证了上述结果。
4.虚引用:也叫幽灵引用,听名字就感觉随时都可能被回收掉,确实是这样,据我所知,只有类似心跳机制监控垃圾回收器是否正常工作会用到,尝试测过,基本上测出来的都是null,被回收掉了。