java与网络
1、关键字final和static是怎么使用的。
1、final变量即为常量,只能赋值一次。
2、final方法不能被子类重写。
3、final类不能被继承。
static:
1、static变量:对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
2、static代码块 static代码块是类加载时,初始化自动执行的。
3、static方法
static方法可以直接通过类名调用,任何的实例也都可以调用,因此static方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。
2、String,StringBuffer,StringBuilder区别
1、三者在执行速度上:StringBuilder > StringBuffer > String (由于String是常量,不可改变,拼接时会重新创建新的对象)。
2、StringBuffer是线程安全的,StringBuilder是线程不安全的。(由于StringBuffer有缓冲区)
3、Java中重载和重写的区别:
1、重载:一个类中可以有多个相同方法名的,但是参数类型和个数都不一样。这是重载。
2、重写:子类继承父类,则子类可以通过实现父类中的方法,从而新的方法把父类旧的方法覆盖。
4、hashmap原理
特点:
1.进行键值对的快速存取
2.线程不安全
3.无序
HashMap采用Entry(安吹)数组来存储key-value,每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体。 只是在JDK1.8中,链表长度大于8的时候,链表会转成红黑树!*为什么用数组+链表?*数组是用来确定桶的位置,利用元素的key的hash值对数组长度取模得到. 链表是用来解决hash冲突问题,当出现hash值一样的情形,就在数组上的对应位置形成一条链表。
5、GC垃圾回收机制
垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。
1、引用计数法
记录每个引用的被引用个数,当引用个数为0时代表成为垃圾,应该被清理。
2、可达性分析法
确定根对象(GC ROOT),顺着根对象遍历,凡是被根对象直接或者间接引用的都不是垃圾,剩下就是垃圾。
一、创建字符串有多少个对象
1、String str = “hello”;
创建了一个对象
jvm在编译阶段会判断常量池中是否有“hello”这个常量对象,如果有,str就直接指向这个常量的引用,如果没有就会在常量池中创建这个对象。
2、String str = new String(“hello”);
创建了两个对象
jvm编译阶段会判断常量池中是否有“hello”这个常量对象,进而判断是否创建常量对象,然后运行阶段通过new关键字在java 堆上开辟一块儿空间来创建String 对象。
3、String str = “hello” + “world”;
创建了一个对象
jvm编译阶段通过编译器优化后,会把字符串常量直接合并成“helloworld”,所以在常量池中只创建了一个对象。
4、String str = “hello” + new String(“world”);
创建了五个对象
常量池对象"hello",“world”,new String(“world”)创建堆对象,还有一个堆对象“helloworld”,还有一个StringBuilder对象。
二、重写equals为什么要重写hashcode
2.1、什么是equals、hashcode
equals:在Object类源码(如下所示)中,其底层是使用了“==”来实现,也就是说通过比较两个对象的内存地址是否相同判断是否是同一个对象。
public boolean equals(Object obj) { return (this == obj); }
hashcode:Object类源码(如下所示)中,hashCode()是一个native方法,哈希值的计算利用的是内存地址。
public native int hashCode();
2.2、两者关系
equals用于两两比较,比如比较100个数据,就需要比价C(100,2) = 4950次。但是同时使用哈希表的话,就能快速定位到对象的大概存储位置,并且在定位到大概存储位置后,后续比较过程中,如果两个对象的hashCode不相同,也不再需要调用equals()方法,从而大大减少了equals()比较次数。所以从程序实现原理上来讲的话,既需要equals()方法,也需要hashCode()方法。那么既然重写了equals(),那么也要重写hashCode()方法,以保证两者之间的配合关系。因此有以下结论:
1、如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相 同!!!!;
2、如果两个对象不同(即用equals比较返回false),那么它们的hashCode值可能相同也可能不同;
3、如果两个对象的hashCode相同(存在哈希冲突),那么它们可能相同也可能不同(即equals比 较可能是false也可能是true)
4、如果两个对象的hashCode不同,那么他们肯定不同(即用equals比较返回false)
2.3、示例
下面展示了Student类的equals、hashCode重写,建议使用idea自动生成,尽量不要自己敲因为很有可能会出错。
class Student{
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
现在有两个Student对象:
Student s1=new Student("小明",18);
Student s2=new Student("小明",18);
假如只重写equals而不重写hashcode,那么Student类的hashcode方法就是Object默认的hashcode方法,由于默认的hashcode方法是根据对象的内存地址经哈希算法得来的,显然此时s1!=s2,故两者的hashcode不一定相等。
然而重写了equals,且s1.equals(s2)返回true,根据hashcode的规则,两个对象相等其哈希值一定相等,所以矛盾就产生了,因此重写equals一定要重写hashcode,而且从Student类重写后的hashcode方法中可以看出,重写后返回的新的哈希值与Student的两个属性有关。
jvm虚拟机
一、Java强引用、软引用、弱引用、虚引用
1.1、强引用
当内存空间不足,系统撑不住了,JVM 就会抛出 OutOfMemoryError 错误。即使程序会异常终止,这种对象也不会被回收
Object obj = new Object()
1.2、软引用
软引用用于维护一些可有可无的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。
这种特性非常适合用在缓存技术上。比如网页缓存、图片缓存等。
第一次回收:内存还够,所以没被回收掉。
第二次回收:又开辟了2G导致内存不足,所以软引用持有的对象就被释放掉了。
public class JavaTest {
public static void main(String[] args) throws InterruptedException {
//2G的缓存数据
byte[] cacheData = new byte[2000 * 1024 * 1024];
//将缓存数据用软引用持有
SoftReference<byte[]> cacheRef = new SoftReference<>(cacheData);
//将缓存数据的强引用去除
cacheData = null;
System.out.println("第一次GC前" + cacheData);
System.out.println("第一次GC前" + cacheRef.get());
//进行一次GC后查看对象的回收情况
System.gc();
//等待GC
Thread.sleep(1000);
System.out.println("第一次GC后" + cacheData);
System.out.println("第一次GC后" + cacheRef.get());
//在分配一个2G的对象,看看缓存对象的回收情况
byte[] newData = new byte[2000 * 1024 * 1024];
System.out.println("分配后" + cacheData);
System.out.println("分配后" + cacheRef.get());
}
}
//第一次GC前null
//第一次GC前[B@45ee12a7
//第一次GC后null
//第一次GC后[B@45ee12a7
//分配后null
//分配后null
1.3、弱引用
弱引用对象相比较软引用,要更加无用一些,它拥有更短的生命周期。当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
第一次回收:弱引用关联的对象存在强引用,所以没被回收掉
第二次回收:弱引用关联的对象不存在强引用,所以被回收
public class JavaTest {
public static void main(String[] args) throws InterruptedException {
//100M的缓存数据
byte[] cacheData = new byte[100 * 1024 * 1024];
//将缓存数据用软引用持有
WeakReference<byte[]> cacheRef = new WeakReference<>(cacheData);
System.out.println("第一次GC前" + cacheData);
System.out.println("第一次GC前" + cacheRef.get());
//进行一次GC后查看对象的回收情况
System.gc();
//等待GC
Thread.sleep(500);
System.out.println("第一次GC后" + cacheData);
System.out.println("第一次GC后" + cacheRef.get());
//将缓存数据的强引用去除
cacheData = null;
System.gc();
//等待GC
Thread.sleep(500);
System.out.println("第二次GC后" + cacheData);
System.out.println("第二次GC后" + cacheRef.get());
}
}
//第一次GC前[B@45ee12a7
//第一次GC前[B@45ee12a7
//第一次GC后[B@45ee12a7
//第一次GC后[B@45ee12a7
//第二次GC后null
//第二次GC后null
public class JavaTest {
static Map<Object,Object> container = new HashMap<>();
public static void putToContainer(Object key,Object value){
container.put(key,value);
}
public static void main(String[] args) {
//某个类中有这样一段代码
Object key = new Object();
Object value = new Object();
putToContainer(key,value);
//..........
/**
* 若干调用层次后程序员发现这个key指向的对象没有用了,
* 为了节省内存打算把这个对象抛弃,然而下面这个方式真的能把对象回收掉吗?
* 由于container对象中包含了这个对象的引用,所以这个对象不能按照程序员的意向进行回收.
* 并且由于在程序中的任何部分没有再出现这个键,所以,这个键 / 值 对无法从映射中删除。
* 很可能会造成内存泄漏。
*/
key = null;
}
}
1.4、虚引用
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象之前,把这个虚引用加入到与之关联的引用队列中。
public class JavaTest {
public static void main(String[] args) {
String[] arr = {"aa", "bb", "cc"};
ReferenceQueue queue = new ReferenceQueue();
// 虚引用,必须与一个引用队列关联
PhantomReference pr = new PhantomReference(arr, queue);
System.out.println(pr.get());//null
}
}
二、对象的生命周期
在Java中,对象的生命周期包括以下几个阶段:
2.1、创建阶段(Created)
为对象分配存储空间
开始构造对象
从超类到子类对static成员进行初始化
超类成员变量按顺序初始化,递归调用超类的构造方法
子类成员变量按顺序初始化,子类构造方法调用
一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段
2.2、应用阶段(In Use)
对象至少被一个强引用持有着。
2.3、不可见阶段(Invisible)
当一个对象处于不可见阶段时,说明程序本身不再持有该对象的任何强引用,虽然该这些引用仍然是存在着的。
简单说就是程序的执行已经超出了该对象的作用域了。
2.4、不可达阶段(Unreachable)
对象处于不可达阶段是指该对象不再被任何强引用所持有。
与“不可见阶段”相比,“不可见阶段”是指程序不再持有该对象的任何强引用,这种情况下,该对象仍可能被JVM等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root”。存在着这些GC root会导致对象的内存泄露情况,无法被回收。
2.5、收集阶段(Collected)
当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。如果该对象已经重写了finalize()方法,则会去执行该方法的终端操作。
2.6、终结阶段(Finalized)
当对象执行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。
GC回收前会调用object finalize方法(临终遗言)
2.7、对象空间重分配阶段(De-allocated)
垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间重新分配阶段”。
三、GC垃圾判断算法
3.1、引用计数法
基本思路:给对象中添加一个引用计数器,每当一个地方引用这个对象,计数器值+1,,当引用失效时,计数器值-1。任何计数为0的对象可以被当做垃圾回收。
缺点:无法解决两个对象相互引用的情况
3.2、可达性分析【用的多】
基本思路:通过一些被称为垃圾回收根(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路称为引用链(Reference Chain),当一个对象到GC Roots没有任何的引用连相连时,则证明该对象是不可用的。
四、GC垃圾回收算法
4.1、标记清除算法
是最基础的一种垃圾回收算法。分为标记,清除和分配3个阶段。
缺点:标记清除后会产生大量的不连续的内存碎片。需要一个空闲列表记录所有的空闲区域及大小。
标记阶段
遍历对象并标记的处理过程。
清除阶段
遍历堆,具体来说就是从堆首地址开始,按顺序一个个遍历对象的标志位。
回收对象就是把对象作为分块,连接到被称为“空闲链表”的单向链表。在之后进行分配时只要遍历这个空闲链表,就可以找到分块了。
在清除阶段,程序会遍历所有堆,进行垃圾回收。也就是说,所花费时间与堆大小成正比。堆越大,清除阶段所花费的时间就会越长。
分配阶段
将分配的内存进行回收再利用。
4.2、复制算法
复制算法是在标记清除算法上演化过来的,解决内存碎片化问题。
它将可用的内存分为两块,每次只用其中的一块,当这一块内存用完了,就将还存活的对象复制到另一块上面,把使用过的内存一次清理掉。缺点:内存的有效使用率太低。
4.3、标记整理算法
标记整理算法和标记清除算法一样,只是后续步骤不是直接回收对象进行清理,而是让所有存活的对象向一端移动,然后直接清理端边界以外的内存。
4.4、分代垃圾回收算法
分代垃圾回收中把对象分类成几代,针对不同的代使用不同的 GC 算法,我们把刚生成的对象称为新生代对象,到达一定年龄的对象则称为老年代对象。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,选用复制算法。在老年代中因为对象存活率高,没有额外空间对它进行分配担保,必须使用标记清除算法或者标记整理算法来进行回收。