android study 14 java基础

Author Avatar
Xzhah 8月 02, 2022
  • 在其它设备中阅读本文章

java的三大特性

继承

​ 继承允许开发者可以创建分等级层次的类,子类可以继承父类的特征和行为。

多态

​ 多态是同一个接口,实例不同,执行的操作也不同。主要优点如下:

封装

​ 封装是将代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用。封装的目的在于保护信息,使用它的主要优点如下:

1. 保护类中的信息,它可以组织在外部定义的代码随意访问内部代码和数据。
2. 隐藏细节信息,不需要开发者修改和使用的信息,被保护起来,开发者只需要知道如何使用即可。
3. 有助于各个系统之间解耦,提高系统的独立性。
4. 提高软件复用率,降低成本。每个封装单位都是相对独立的整体

​ java基本的封装单位是类,由于类的用途是封装复杂性,所以类的内部有隐藏实现复杂性的机制。Java 提供了私有和公有的访问模式,类的公有接口代表外部的用户应该知道或可以知道的每件东西,私有的方法数据只能通过该类的成员代码来访问,这就可以确保不会发生不希望的事情。

java的特点

​ 除了以上说的面向对象特点,java还有以下特点:

​ 1.平台无关性,java虚拟机保证了java”一次编译,到处运行“的特点

​ 2.支持多线程,java语言自身提供了多线程支持

​ 3.支持网络编程且方便

​ 4.编译与解释并存

JVM,JDK,JRE

​ JDK是Java Development Kit的缩写,是功能齐全的Java SDK, 它拥有JRE所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。

​ JRE是Java Runtime Environment的缩写,它是运行已编译Java程序所需内容的集合,包括java虚拟机,java类库,java命令和其他基础构件,但是JRE不能用来创建新程序。

​ JDK包含JRE,JRE包含JVM。

Oracle JDK和Open JDK

​ 1.OpenJDK 是一个参考模型并且是完全开源的,而 Oracle JDK 是OpenJDK 的一个实现,并不是完全开源的

​ 2.Oracle JDK更稳定,它经过了更多测试以及有更多的类和一些错误修复,它经过了系统性的测试。

​ 3.性能方面,Oracle提供了更好的性能

值传递和引用传递

​ 值传递:指在方法调用时,传递的参数是按值得拷贝,传递的是值的拷贝,也就是说传递后就互不相关了。

​ 引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的是引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。

​ java基本类型作为参数被传递时肯定是值传递;引用类型作为参数被传递时也是值传递,只不过“值”为对应的引用。

为什么重写equals方法必须重写hashcode方法

​ 判断两个对象是否相等,是先判断hashcode是否相同,相同再根据equals进行判断,如果只重写了equals方法,不重写hashcode方法,可能造成hashcode判断是不同的,但是equals认为是相同得。

​ 在java得一些容器中(如Set容器),不允许有两个完全相同的对象,插入的时候判断相同则会进行覆盖,这时候如果只重写了equals()的方法,而不重写hashcode的方法,Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题。

String与StringBuilder

​ String对象是不可变的,StringBuilder和StringBuffer是可变的。对于已经存在的String修改都是重新建一个新的对象,然后把新的值传进去。String可以理解为常量,则是线程安全的,StringBuilder是非线程安全的,StringBuffer对方法加了同步锁,是线程安全的。

​ String为什么是不可变的呢?

​ 1.因为java字符串池的设计,在Java中,由于会使用大量的字符串常量,如果每次声明就创建一个对象,会造成空间浪费,Java提出了String pool的概念,在堆中开辟一块存储空间String pool,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。如果字符串是可变的,某一个字符串变量改变了其值,那么其指向的变量的值也会改变,String pool将不能够实现!

​ 2.使得线程安全

​ 在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争,但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全。

​ 3.避免安全问题

​ 在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。

​ 4.加快字符串处理速度

​ 由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。

gc机制

​ gc是一种回收空间避免内存泄露的机制,当JVM内存紧张,通过执行gc有效回收,转而分配给新对象从而实现内存的再利用。

如何标记不再被引用的对象

​ 目前有两种方法:引用计数法和可达性分析法。引用计数在对象头分配与一个字段,用来存储该对象的引用计数,一旦被引用就+1,这个引用失效就-1。对于技术为0的就可以回收。但是碰到对象之间存在循环引用就会出现问题,对象可能已经不再使用,但是对象内部还在相互引用,导致无法回收。第二种就是做个可达性分析,首先按规则找出当前活跃的引用,从这些GC Roots引用出发,遍历对象引用关系图,将可达的引用都标记为存活。比如方法中的局部变量,静态变量,常量,都可以在一开始被视为活跃引用。

在哪里回收垃圾

​ java内存区域可以分为:堆,方法区,虚拟机栈,本地方法栈,程序计数器。

​ 方法区与堆都是线程共享的区域,这两部分占用 JVM 大部分内存,剩下三个小弟将会跟线程绑定,随着线程消亡,自动将会被 JVM 回收。

如何回收

​ 1.标记-清除法:最简单暴力的方法,通过上述 GC Roots 标记出可达对象。把所有标记了的对象清理掉,存在的问题是,可能会产生很多碎片化内存,因为这些对象并不是连续的,实际分配过程,由于没有连续内存,导致虚拟机感知到内存不足,又不得不提前再次触发 GC。另一个问题是标记与清除效率比较低。这就竟会导致 GC 占用时间过长,影响正常程序使用。

​ 2.复制算法:这个方法会将内存分为两块,每次只使用其中一块,当这一块内存使用完毕,触发gc,就会把还存活的对象复制到另外一块内存上。这个算法每次只需要操作一半内存,GC 回收之后也不存在任何空间碎片,新对象内存分配时只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。但是这个算法闲置一半内存空间,空间利用效率不高。

​ 3.标记-整理算法:标记-整体法也是通过 GC Roots 标记存活对象。将存活对象往一端移动,按照内存地址一次排序,然后将末端边界之外内存直接清理。这个算法问题是效率不高。

trade-off

​ 1.可以看到,上面三种算法并没有十分完美的解决方案,所以只能做的是综合利用各种算法特点将其作用到不用的内存区域。目前商业虚拟机会根据对象存活周期不同,划分不同的内存区域。新生代每次 GC 之后都可以回收大批量对象,所以比较适合复制算法,只需要付出少量复制存活对象的成本。这里内存划分并没有按照 1:1 划分,默认将会按照 8:1:1 划分成 Eden 与两块 Survivor空间。每次使用 Eden 与一块Survivor空间,这样我们只是闲置 10% 内存空间。不过我们每次回收并不能保证存活对象小于 10%,在这种情况下就需要依靠老年代的内存分配担保。当Survivor空间并不能保存剩余存活对象,就将这些对象通过分配担保进制移动至老年代。

​ 老年代中对象存活率将会特别高,且没有额外空间进行分配担保,所以并不适合复制算法,所以需要使用标记-清除或标记-整理算法。

References

https://www.jianshu.com/p/7687559d29a3