Java相关

对象的创建:

Object o = new Object()

可以通过编译后的字节码文件知道,创建过程经历了5步:
1、new :申请内存空间(此时默认为0
2、dup :复制操作数栈顶值,并将其压入栈顶,也就是说此时操作数栈上有连续相同的两个对象地址(若init()有其他参数,也要入栈)
3、invokespecial: 进行初始化(即实例化),从栈顶弹出一个this引用 及 需要的参数;
4、 athrow :从操作数栈顶取出一个引用类型的值,并抛出(即与实例化对象关联的变量名);
5、return :结束

单例

实现方式:不允许外界直接new,将自己的构造方法设为private。
有著名的懒汉式 、 饿汉式、DCL、匿名内部类方式。

懒汉式:(延迟加载)

很懒,在类加载时,不创建实例,进入getInstance()时再判断是否已存在实例。

优缺点:因此类加载速度快,但运行时获取对象的速度慢

getInsance()加锁,是为了保证线程安全;然鹅方法中可能还有别的业务代码,这样会降低效率。

  public class LazySingleton {
  
    private static LazySingleton intance = null;
    
    private LazySingleton() {}
    
    public static synchronized LazySingleton getInstance()    
    {
        if(intance == null)
        {
            intance = new LazySingleton();
        }
        return intance;
    }
}
   

饿汉式:(线程安全)

单例类一开始就先行创建好一个对象了,getInstance()方法直接返回对象即可。

优缺点:与懒汉相反。类加载时就完成了初始化,所以类加载较慢,获取对象的速度快

public class EagerSingleton {
       
    private static EagerSingleton instance = new EagerSingleton();
    
    private EagerSingleton(){}
   
    public static EagerSingleton getInstance()    
    {
        return instance;
    }
}

DCL(Double Check Locking)

改进饿汉模式,不用每次都去进行线程锁定判断,节省资源,尤其是高并发环境中。

当发现对象不存在时再锁定代码块,然后同样要再进行一次实例是否存在判断。(防止某个线程在判断之后暂停,直到另一个线程已经new了对象走人了才继续,然后又new了一个新对象)

public class DCLSingleton {	

	private EagerSingleton(){}
        private volatile static DCLSingleton instance = null;
      
      public  static DCLSingleton getInstance() {
	if(null == instance) {    
              synchronized (DCLSingleton.class) {
                 if(null == instance) {                    
                     instance = new DCLSingleton();        
                  }
             }
         }
         return instance;    
    }
 }

静态内部类实现单例(线程安全

在内部类中创建单例对象,并在getInstance()调用返回即可。

该模式是饿汉模式的优化: 延迟了单例的实例化
相比懒汉模式: 不用再每次都去判断锁状态,大大节约资源
相比DCL模式: 该模式传参有限制,而DCL第一次创建需要判断锁,酌情选择

外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,所以不占内存。
即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE。

ps:这点区别于静态成员变量吧,静态成员变量的生命周期是随同类的,即类被加载,它也就在内存中被加载了。

public class StaticSingleton {    
    private static class LazyHolder {    
       private static final StaticSingleton INSTANCE = new  StaticSingleton();    
    }    
    private StaticSingleton (){}    
    public static final Singleton getInstance() {    
       return LazyHolder.INSTANCE;    
    }    
}

枚举实现单例

jdk1.5之后才出现的,因为前面所有的模式都存在由于反射攻击和自由序列化后无法实现单例的问题,往往需要手动解决;

因此诞生了枚举类,当进行反射攻击时,遇到Enum类修饰符会抛出异常,对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。

在加载时间上,类似于饿汉模式。但代码更简洁。
且enum类中同样支持编写普通类成员与成员方法。

注:枚举类对象INSTANCE天生就是一个单例。反编译后可以发现,它的实例是由 final修饰,即只存在一个。所以保证线程安全

public enum  EnumSingleton {
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}


最后注意在多线程时,DLC创建对象时要加 volatile修饰,原因如下:
(ps:网上的帖子都只提到了DCL需要,可博主认为只要是单例模式就需要吧)

实例一个类对象时,往往有以下过程:

1.分配内存。
2.初始化构造器
3.将引用指向分配的内存空间

然鹅,为了提高程序执行效率,虚拟机中有一种 out of order机制。即当一个线程中有多个独立的指令时,若某个指令获取较慢,虚拟机会将指令顺序打乱,让CPU先执行后面的指令,这就导致了指令重排序/out of order。
(注意,指令重排序只发生在多线程环境中)

因此,假如在单例模式创建实例的时候,指令刚好进行了重排序,先进行了 3 再进行 2,那么将会生成一个半初始化的单例对象,且它后面可能会被多个对象调用,这就会导致严重的问题。

因此需要通过加 volatile修饰符,利用内存屏障,防止指令重排序。

实例对象的存储结构

1、Markword 对象头(包含了对象的identity hashcode、GC分代年龄、线程锁状态)

2、对象的 ClassPoint (一个指向对象类型的指针)

3、对齐填充字节 (对于64位系统来说,处理8的倍数字节是最快的,所以会补齐为8的倍数)

注:虚拟机往往会将对象的ClassPoint跟 Markword进行压缩,但不管有无压缩 new Object对象都是16字节,只是补齐字节数的不同而已。

volatile、synchronized

Java的单继承与多实现、抽象类

1、单继承

众所周知,Java类只能最多继承一个类。因为当继承多个类时,会出现一系列问题:

(1)当多个父类中有多个同名变量,子类对象调用 该变量名,会产生歧义;

(2)当子类没有重载多个父类中的同名方法,子类对象调用该方法,会产生歧义。

由此,我们发明了可以进行 多实现 的

2、接口

根据接口定义的规则,可以完美的解决这两个问题:

(1)接口的变量只能为 public static final 类型; (直接由 【接口.变量名】方式引用即可区分。

(2)接口中不允许定义方法体。jdk1.8之后的static块除外。 (自然也不会出现调用混淆一事

当然,除了解决单继承带来的缺憾,接口还有别的优点:

(1)作为最高层次的抽象,可以定义它的实现类需要完成什么样的功能,尤其是在大型项目的架构中,作为一种标准规则存在

(2)容易维护、扩展性强。当对接口的当前实现类不满意 ,so easy!接口引用换一个指向就行了。
(当然,这种扩展性普通继承类的向上转型同样能做到)

(3)安全。是 OOP中实现松耦合的重要手段,体现了一定的封装性。(隐藏内部细节,只对外提供服务

讲到抽象,就不得不提到抽象类了。

抽象类可以说是一个比较奇葩的存在了——既能定义抽象方法,又能写入方法体;本来人家要实现接口、继承抽象类的都是要实现所有抽象方法的,但是用抽象类去实现、继承就没了这种限制!

为什么要抽象类呢?
当一些类对于接口的方法实现方式一毛一样的时候,先定义一个抽象类,把这些方法实现了,剩余的再让继承抽象类的子类去扩展,多方便啊!

抽象类跟接口一样,都是不能被实例化的 ,然鹅,他们的区别还是比较大的:

(1)抽象类依旧是类,还是只能单继承的; #根本区别

(2)对于变量 ,抽象类可以任意定义类型 ,接口只能是public static final 的全局变量;

(3)对于方法,抽象类可以定义抽象方法 & 非抽象方法,接口只能定义抽象方法,且必须为 public ;
(ps:接口的类型以及接口中的方法类型都是隐式 abstract 的)

(4)抽象类有构造方法 ,接口无。

为什么接口的变量必须为static 类型,而方法不行?

想一想 ,static类型变量的特性——生命周期跟随类,因此可以用 【类.变量/方法名】的方式来调用。

对于接口中的变量 ,由接口作为一种规范的性质 ,它的变量规定必须是 final的 ,也就是固定的、不能被实现类所改变的,所以我们可以直接用接口名来调用 ,还具有区分变量所属接口的作用 (当一个类实现多个接口时);
而对于接口中的方法,它不是固定的 ,可能被多个实现类所扩展,如果我们直接用接口名来调用 ,可能会产生歧义

关于java中引用对象的 “传值“ or ”传地址“?

毫无疑问,不管你是赋值 ,还是作为方法参数传递 ,最初传的当然是地址;

当我们把一个引用对象赋值给另一个新对象 or 作为参数传递,jvm就会在栈中给新变量开辟空间,后直接把新变量的指针(也就是变量中存储的内存地址)指向原对象所指向的内存空间

所以 ,当你修改被赋值对象属性 or 方法参数的对象属性,原对象一定会被修改,因为他们指的都是同一块储存实体对象的内存地址呀~

但是!敲黑板!
如果你对被赋值 or 方法参数对象 进行了 = 重新赋值的时候,他们就马上指向了另一块内存地址了,之后不管你再对它进行什么修改操作 ,都不会对原对象产生影响哦~
如下图:
方法中bt1其实是指向了另一内存地址,而不是修改原内存地址中的值,所以bt没有被改变

而如果在方法中改变了所传引用对象的某个属性而已,那原内存地址中的值就真的被改变了,如图:
在这里插入图片描述
ps:对于方法参数对象 ,方法执行完之后,存在栈中的引用马上被回收,而堆中的就等GC回收。

另,要注意 ,值为null的对象 ,也就是说 空引用的对象。当还没有被new的时候 ,在堆中是没有给它分配内存空间的 ;
所以如果传值为null的对象作为方法参数等的话 ,其实在方法中不管对他进行什么操作 ,都是白费力气的,因为:

当你传了一个值为null的变量过去,如果要修改属性 、执行方法 ,就一定要先new或者使用另一个实例给它赋值 ;
然鹅不管是哪种 ,都改变了它的指针指向。
或者从另一方面讲,这个对象的指针本来就是空的,它哪有内存给你去修改呢?它配吗==

所以!一定要注意,判空要在传参之前!不要传 null 值的引用变量作为方法参数!没意义!

根本就没有传引用地址进去 ,就别说修改地址的中的值了 。
如下图:
在方法中让形参new了一个对象,此时就丢弃了指向原对象的指针了

ps:
在一个进程中,所有线程共享堆和方法区,而栈是每个线程独有的。

基本数据类型直接在栈中分配内存;引用数据类型除了要在栈中分配放引用(也就是我们常说的变量名,实质上就是记录了该对象在堆中的内存地址),另外在堆中分配内存存放真正的对象。
堆中除了存放对象数据以外,还放了一个指向对象类类型信息的指针,目的是为了得到操作指令。(因为方法的代码在类中,也就是被加载在方法区中)

最初实例化对象时,在方法区中找不到类类型信息,那么就会去加载类 。
方法区中 ,是整个程序中永恒不变的东西 ,如 static数据 、类类型信息 。

栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值