美文网首页
Java基础乱谈

Java基础乱谈

作者: 墨源为水 | 来源:发表于2021-02-19 14:04 被阅读0次

    Java内存结构

    java数据分为两类,一类是基本数据类型,一类是引用数据类型;讲这两类类型,先讲一下
    java运行时需要分配内存空间,JVM内存空间分为:

    1. 寄存器;
    2. 本地方法区;
    3. 方法区;
    4. 栈内存(stack);
    5. 堆内存(heap);
    • 栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。

    • 堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。
      Jvm在堆中存储实际对象的内容如下:


    • 方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。 常数池,源代码中的命名常量、String 常量和 static 变量保存在方法区。

    一.基本数据类型

    分别为int(4)、float(4)、double(8)、long(8)、char(2)、short(2)、byte(1)、boolean(1)(boolean 类型比较特别可能只占一个 bit,多个 boolean 可能共同占用一个字节)。其值是直接存储在栈内存中。

    二.引用数据类型

    实际对象的内存是分配在堆内存当中,栈内存存储的是堆内存的分配的内存地址。
    eg 主函数里的语句 int [] arr=new int [3];在内存中是怎么被定义的:

    主函数先进栈,在栈中定义一个变量arr,接下来为arr赋值,但是右边不是一个具体值,是一个实体。实体创建在堆里,在堆里首先通过new关键字开辟一个空间,内存在存储数据的时候都是通过地址来体现的,地址是一块连续的二进制,然后给这个实体分配一个内存地址。数组都是有一个索引,数组这个实体在堆内存中产生之后每一个空间都会进行默认的初始化(这是堆内存的特点,未初始化的数据是不能用的,但在堆里是可以用的,因为初始化过了,但是在栈里没有),不同的类型初始化的值不一样。所以堆和栈里就创建了变量和实体:
    给堆分配了一个地址,把堆的地址赋给arr,arr就通过地址指向了数组。所以arr想操纵数组时,就通过地址,而不是直接把实体都赋给它。这种我们不再叫他基本数据类型,而叫引用数据类型。称为arr引用了堆内存当中的实体。

    这里引申一下,讲一下垃圾的产生
    如果当int [] arr=null; arr不做任何指向,null的作用就是取消引用数据类型的指向。

    当一个实体,没有引用数据类型指向的时候,它在堆内存中不会被释放,而被当做一个垃圾,在不定时的时间内自动回收,因为Java有一个自动回收机制,(而c++没有,需要程序员手动回收,如果不回收就越堆越多,直到撑满内存溢出,所以Java在内存管理上优于c++)。自动回收机制(程序)自动监测堆里是否有垃圾,如果有,就会自动的做垃圾回收的动作,但是什么时候收不一定。

    1、基础数据类型直接在栈空间分配;
    2、方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
    3、引用数据类型,需要用 new 来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
    4、方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
    5、局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收, 堆空间区域等待 GC 回收;
    6、方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放;
    7、字符串常量在 DATA 区域分配 ,this 在堆空间分配;
    8、数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小!

    在拓展讲一下,拷贝

    Person p = new Person(age=23, name="zhang");
    

    有一个p的Person引用数据类型,new 操作符的本意是分配内存。程序执行到 new 操作符时, 首先去看 new 操作符后面的类型,因为知道了类型, 才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化, 构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对 象。
    1.引用复制

    Person p1 = p;
    

    地址值是相同的,p,p1指向同一个对象
    2.对象复制

    Person p1 = (Person) p.clone();
    

    其中对象拷贝分为深拷贝和浅拷贝

    Person 中有两个成员变量,分别是 name 和 age, name 是 String 类型, age 是 int 类型,由于 age 是基本数据类型,那么对它的拷贝没有什么疑议,直接将一个 4 字节的整数值拷贝过来就行。但是 name 是 String 类型的, 它只是一个引用, 指向一个真正的 String 对象,那么对它的拷贝有两种方式: 直接将原对象中 的 name 的引用值拷贝给新对象的 name 字段,或者是根据原 Person 对象中的 name 指向的字符串对象创建一个新 的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的 Person 对象的 name 字段。这两种拷贝方式分别叫 做浅拷贝和深拷贝。

    对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
    类:类是一个模板,它描述一类对象的行为和状态。

    2.1.Java 中引用类型

    Java 中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

    如果一个对象被被人拥有强引用,那么垃圾回收器绝不 会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意 回收具有强引用的对象来解决内存不足问题。
    Java 的对象是位于 heap 中的,heap 中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到 达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。

    String str1 = "abc";
    String str2 = new String("abc");
    System.out.println(str1==str2);//false
    
    String str1 = "abc"; 
    String str2 = "abc"; 
    System.out.println(str1==str2);//true
    

    在Java的实现中,new出来的String对象一般是放在堆中的。如果是 String s ="xxx"; 这种,那就是放在常量池中.JDK6将常量池放在方法区中。但是从JDK7开始, 常量池的实现 已经从方法区中移出来放到 堆内存里面了。

    类初始化

    java类的生命周期:
    指一个class文件从加载到卸载的全过程,类的完整生命周期包括7个部分:加载——验证——准备——解析——初始化——使用——卸载,如下图所示,其中,验证——准备——解析 称为连接阶段,除了解析外,其他阶段是顺序发生的,而解析可以与这些阶段交叉进行,因为Java支持动态绑定(晚期绑定),需要运行时才能确定具体类型;在使用阶段实例化对象

    1.类初始化

    类的初始化:是完成程序执行前的准备工作。在这个阶段,静态的(变量,方法,代码块)会被执行。同时在会开辟一块存储空间用来存放静态的数据。初始化只在类加载的时候执行一次。
    类的实例化:是指创建一个对象的过程。这个过程中会在堆中开辟内存,将一些非静态的方法,变量存放在里面。在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存。即new操作

    2.类初始化顺序

    java类初始化原则是

    • 静态对象(变量)先于非静态对象(变量)初始化。其中静态对象(变量)只初始化一次,因为static在jvm中只有一块区域存储,方法区(Method Area),
      他之所以被称为静态是因为从程序创建到死亡他的地址值都不会改变,他只在class类对象初次加载时初始化,因此static只需要初始化一次,而非静态对象(变量)可能会初始化很多次。类中存在初始化语句(如 static 变量和 static 块),那就依次执行这些初始化语句。
    • 如果类之间存在继承关系,那么父类优先于子类进行初始化。
    • 按照成员变量的定义顺序进行初始化。即使变量定义散布于方法之中,他们依然在任何方法(包括构造函数)被调用前先初始化

    Java程序初始化的顺序:父类静态变量 -> 父类静态代码块 -> 子类静态变量 -> 子类静态代码块 -> 父类非静态变量 -> 父类非静态代码块 -> 父类构造器 -> 子类非静态变量 -> 子类非静态代码块 -> 子类构造器。


    Kotlin:

    • init关键字==>java非静态方法块
    • companion object伴生对象==>java静态方法
    • 伴生对象中的init方法==>java静态代码块
    Java调用静态方法需要类装载还是初始化?
    • 结论:Java调用静态方法时会对类进行装载、连接和初始化
    • 原因:Java类的加载方式是按需加载,遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
    类什么时候被初始化?

    1)创建类的实例,也就是 new 一个对象
    2)访问某个类或接口的静态变量,或者对该静态变量赋值
    3)调用类的静态方法
    4)反射(Class.forName("com.lyj.load"))
    5)初始化一个类的子类(会首先初始化子类的父类)
    6)JVM 启动时标明的启动类,即文件名和类名相同的那个类

    相关文章

      网友评论

          本文标题:Java基础乱谈

          本文链接:https://www.haomeiwen.com/subject/wcngxltx.html