JVM

谈谈你对Java的理解?

1、平台无关性

2、面向对象

3、GC

4、类库

5、语言特性

6、异常处理

 

动态代理类的字节码在程序运行时由 java 反射机制形成。cglib 动态代理原理是生成被 代理类的子类,并覆盖其中方法进行增强。

二分查找返回的是:-(插入点)-1

javap是jdk自带的反编译器可以查看字节码文件

 

关键字volatile是轻量级的同步机制。

Volatile变量对于所有线程的可见性,指当一条线程修改了这个值,新值对于其他线程来说是可见的、立即得知的。

Volatile变量在多线程下不一定安全,因为只有可见性、有序性,但是没有原子性。

 

线程的状态

在任意一个时间点,一个线程只能有且只有一种状态

1、新建:创建后尚未启动的线程处于这种状态

2、运行:包括OS中的Running和Ready状态,也就是处于次状态的线程可能正在运行,也可能正在等待cpu为他分配执行时间。

3、无限期等待:处于无限期等待的线程不会被分配cpu执行时间,要等待其他线程显式唤醒。以下方法会让线程进入无限期等待

一、没有设置timeout的object.wait方法

二、没有设置timeout参数的Thread.join方法

三、LockSupport.park

4、有限期的等待:处于这种状态的线程不会分配cpu的执行时间,不过无须等待其他线程唤醒,而是在一定时间后,他们会由os自动唤醒。

一、设置了timeout的object wait 二、设置timeout的Thread.join方法  3、LockSupport.parkNanos() 4、LockSupport.parkUnit()

5、阻塞:线程被阻塞了与等待状态的区别是:阻塞状态在等待获取一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生。等待状态在等待一段时间或者唤醒动作。

6、结束:已终止线程的线程状态,线程已经结束执行。

 

synchronized    vs  lock

1、L是接口  S是关键字

2、S在发生异常时会自动释放线程占有的锁,不会发生死锁,L在发生异常时,若没有主动unlock释放锁,则很可能造成死锁。所以用lock时要注意在finally中释放锁。

3、L可以当等待锁的线程响应中断,s等待的线程会一直等下去不能响应中断

4、通过L可以知道是否成功获得锁。S不可以

5、L可以提高多个线程进行读写操作的效率。

 

 第一个被加载的类是所谓的初始化类,也就是有main方法的类,这个类由jvm本身加载。

一个抽象类可以没有抽象方法。

一个接口可以继承任意数量的接口,一个抽象类只能继承一个抽象类。

Finally除了try块调用System.exit(0),finally都会执行,有return也会执行finally中的内容,再执行return

 

 

一次编译到处运行是如何实现的?

javac编译.java文件,生成字节码.class文件

JVM解析字节码转换成特定平台的指令。

Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要重新编译。Java虚拟机在执行字节码的时候,把字节码转换成具体平台的机器指令。

 

为什么JVM不直接将源码解析成机器码去执行

准备工作:每次执行都需要各种检查。

兼容性:也可以将别的语言解析成字节码。

 

JVM如何加载.class文件

ClassLoader依据特定格式,加载class文件到内存

Executor Engine 对命令进行解析

Native Interface 融合不同开发语言的原生库为Java所用。

Runtime Data Area :JVM内存空间结构模型

 

 -Xmx:最大堆大小

-Xms:初始堆大小

-Xmn:年轻代大小

-XXSurvivorRatio:年轻代中eden和survivor区大小比值。

 

Java虚拟机里的对象由三部分组成

1、对象头:标记字段(在32位和64位jvm中分别为32bit和64bit)+类型指针

2、实例数据

3、对齐填充

若对象是一个Java数组。则对象头中还必须有一块用于记录数组长度的数据。因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但从数组的元数据中无法确定数组的大小

对象大小必须是8字节的整数倍

 

Xms:堆初始大小

Xmx:堆最大大小

Xss设置栈的大小

 

Cms:并发收集,低停顿

gc进行时必须停顿所有java执行线程(stop the world),即使在号称几乎不会发生停顿的cms收集器中,枚举根节点时必须停顿(因为不可以在发生分析过程中对象引用关系还在不断变化)。

在大多数情况下,对象在新生代eden区中分配,当eden区域没有足够的内存空间时,虚拟机发生一次minor gc

Xmn:分给新生代的大小

虚拟机给每一个对象定义一个对象年龄计数器,若对象在eden出生并经过一次minor gc后依然存活,并且能被survivor容纳的话,将被移到survivor空间中,并且对象年龄设为1.

对象在survivor中熬过每一次minor gc年龄就+1,当他年龄达到一定程度(默认15)就会晋升到老年代

 

虚拟机可以从方法表中acc_synchronized访问标志得知一个方法是否为同步方法。当方法调用时,调用指令将会检查方法的acc_synchronized访问标志是否被设置了。若被设置了,执行线程就要求先成功持有monitor,然后才能执行方法,最后当方法完成了,释放moniter。在方法执行期间,执行线程有了monitor,其他线程都无法再获取到同一个monitor,若一个同步方法执行期间抛出了异常,并且再方法内部无法处理该异常,那么这个方法所持有的monitor将在异常执行到同步方法之外自动释放。

 

 

类加载:

类从加载到虚拟机内存中开始到卸载除内存为止,它的整个生命周期包括:

加载、验证、准备、解析、初始化、使用、卸载

其中 验证、准备、解析被称为链接。

在遇到下列情况时,若没有初始化,需要先触发其初始化

1、使用new关键字实例化对象、读取或设置一个类的静态字段 、调用一个类的静态方法。

2、使用反射方法对类进行反射调用时,若类没有进行初始化,则需要触发其初始化。

3、当初始化一个类时,如果发现其父类还没有初始化,则需要先触发其父类的初始化。

4、当虚拟机启动时,用户需要定制一个要执行的主类,虚拟机会先初始化这个类。

 

在加载阶段虚拟机需要完成下面的3件事情。

1、通过一个类的全限定名获取定义此类的二进制字节流

2、将这个字节流所表示的静态存储结构转换为方法区运行时数据结构

3、在内存中生成一个代表这个类的class对象,作为方法区各种数据的访问入口。

 

验证的目的是为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,且不会危害虚拟机自身的安全。

验证阶段大致会完成下面4个阶段的检验动作

1、文件格式

2、元数据验证

3、字节码验证

4、符号引用验证

字节码验证将对类的方法进行校验分析

保证被校验的方法在运行时不会做出危害虚拟机的事,一个类方法体的字节码没有通过字节码验证,那一定有问题,但若一个方法通过了验证,也不能说明它一定安全。

 

准备阶段:是正式为类变量分配内存并设置变量的初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。(不是实例变量,且是初始值,若public static int a = 123;准备阶段后a的值为0,而不是123,要在初始化之后才变成为123,但若被final修饰则在准备阶段就变成了123)

解析阶段:是虚拟机将常量池中的符号引用变成直接引用的过程。

符号引用:在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。

直接引用:直接引用可以是直接指向目标的指针

  

静态代码块只能访问在静态代码块之前的变量,在它之后的变量,在前面的静态代码块中可以赋值,但是不可以使用。

 

通过一个类的全限定名来获取定义此类的二进制字节流,实现这个动作的代码就是“类加载器”

比较两个类是否相同,只有两个类是由同一个类加载器加载的前提下才有意义,否则即使这两个类来源同一个class文件,被同一个虚拟机加载,只要加载他们的加载器不同,他们就是不同的类。

 

从Java虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器,这个类加载器使用cpp实现,是虚拟机自身的一部分。另一种就是所有其他的类的加载器,这些加载器都由Java实现,且全部继承自java.lang.classloader

从Java开发人员的角度,类加载器分为:

1、启动类加载器,这个加载器负责将<JAVA_HOME>\lib下或-X bootclasspath下的类库加载到虚拟机内存中,启动类加载无法被Java程序直接引用。

2、扩展类加载器:负责加载<JAVA_HOME>\lib\ext下或者java.ext.dirs系统变量指定路径下所有的类库,开发者可以直接使用扩展类加载器。

3、应用程序类加载器:负责加载用户路径classpath上指定的类库,开发者可以直接使用这个类加载器,若应用程序没有定义过自己的类加载器,一般情况下,这个就是程序中默认的类加载器。

 

双亲委派模型:若一个类加载器收到了类加载的请求,它首先不会尝试加载这个类,而是把这个请求委派给父类加载器区完成,每次加载器都是如此,因此所有加载请求最终都会传递到顶级的启动类加载器。只有在父类加载器反馈自己无法加载时(他的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

 

双亲委派模型的好处:如Object类,他存放在rt.jar中,无论哪种类加载器要加载这个类,最终都是委派给处于模型顶端的启动类加载器加载,因此object类在程序的各种加载环境中都是同一个类。

 

双亲委派模型是通过loadclass方法实现:先检测类是否被加载过,若没有,则调用父类加载器的loadclass方法,若父类加载器为空,则使用启动类加载器为父类加载器。若父类加载器失败,先抛出class Not Found Exception然后调用自己的findclass方法进行加载。要实现自定义类加载器,只需要继承java.lang.classLoader

当运行一个程序时,jvm启动,运行bootstrap classloader,该classloader加载核心API,然后调用ext classloader加载扩展API,最后app classloader加载classpath目录下定义的class,这就是一个程序最基本的加载流程。

通过classloader加载实际上就是加载的时候并不对该类进行解析,因此也不会初始化,而class类的forName方法则相反,使用forName方法加载的时候会将class进行解析与初始化。

 

反射:

Java反射运行机制是运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能被称为Java语言的反射机制。

 

加载、验证、准备、初始化、卸载这五个阶段的顺序是确定的。

到初始化的阶段才真正开始执行类中定义的Java程序代码(或者说是字节码)。初始化阶段是执行类的构造器(<init>())方法的过程。

<init>()方法是由编译器自动收集类的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。编译器收集的顺序是由语义在源文件中出现的顺序决定的。

<clinit>()方法与类的构造函数不同,他不需要显式的调用父类的构造器,虚拟机会保证子类的的<clinit>方法执行之前,父类的clinit方法已经执行完毕,因此在虚拟机第一个被执行的<clinit>方法的类肯定是java.lang.Object

由于父类的clinit方法先执行,也就意味着父类中的静态语句块要优于子类的变量赋值。

接口中,不能有静态代码块。

 

虚拟机会保证一个类的<clinit>方法在多线程环境中被正确的加锁同步。若多个线程同时区初始化一个类,那么只会有一个线程区执行这个类的<clinit>方法其他的线程都要阻塞等待,直到活动线程执行<clinit>()完毕。

 

若自己编写一个类放在java.lang.Object并放在classpath下,可以正常编译,但永远无法被加载。

 

若有一个类加载器请求,先检查是否已经被加载,若没有被加载则调用父类加载器的loadclass方法,若父类加载器为空,则默认 使用启动类加载器作为父类加载器,若父类加载器失败,抛出classNotFoundException,再调用自己的findclass方法进行加载。

语法糖:在计算机语言中添加的某种语法,这种语法对语言功能并没有影响,但是更方便程序员使用。Java中最常用的语法糖有:泛型、变长参数,自动拆箱装箱。虚拟机运行时不支持这些语法,他们在编译阶段还原回简单的基础语法结构,这个过程为解语法糖。

 

包装类的==运算,在不遇到算术运算的情况下不会自动拆箱,遇到了就自动拆箱。

Java中的运算并非原子操作,导致volatile变量的运算在并发下并不一定安全。

 

只有使用invoke special指令调用的私有方法,实例构造器,父类方法,以及invoke static指令调用的静态方法,才是编译期进行解析的,除了上述四种方法,其他Java方法调用都需要在运行期进行方法接收者的动态选择,并且很有可能存在多余一个版本的接收者(最多再除去被final修饰的方法,尽管它用invoke virtual调用,但也是非虚方法),Java中默认的实例方法是虚方法。

每次使用volatile变量之前都必须从主内存刷新最新的值,用于保证能看见其他线程对变量所作的修改后的值。在工作内存中,每次修改volatile变量后,都必须立刻同步回主内存中,用于保证其他线程可以看到自己对该变量所作的修改。volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序相同。

原子性、可见性、有序性。

 

Happen-before:对于一个volatile变量的写操作,先行发生于后面对这个变量的读操作。

 

各个线程可以共享进程资源(内存地址、文件IO),也可以独立调度。线程是CPU调度的基本单位

 

synchronized关键字经过编译后,会在同步块的前后分别形成monitor enter和montor exit这两个字节码指令,这两个字节码指令都需要一个reference类型的参数来指明需要锁定和解锁的对象。若Java程序中synchronized明确指定了对象参数,那就是这个这个对象的reference;若没有明确的指定,那就根据synchronized修饰的是实例方法或类方法去取相对应的对象实例或class对象来作为搜索对象。

在执行monitor enter指令时,首先要尝试获取对象的锁,若这个对象没被锁定,或当前线程已经拥有那个对象的锁,把锁的计数器+1.相应的在执行monitor exit时,会将计数器-1,当计数器为零时,锁被释放。若获取对象锁失败,则当前线程就会阻塞。直到对象锁被另外一个线程释放。

 

对于线程的阻塞和唤醒,需要用户态和核心态的切换,状态切换需要很多处理器时间。

 

类只可以是public或包访问权限

组合:只需要将对象引用置入新类中。

字符串与任意数据类型连接都用+,最终都变成字符串。S.O.P(a)调用的是a的toString方法。"source"+aa;由于只能将一个String对象和另一个String对象相加,所以会调用a的toString方法。

Arrays.sort对于基本数据类型用的是快速排序,对象数组用的是改进的归并排序

 

通常加载发生创建的第一对象之时,但是访问static变量和static方法时也会发生加载。

初次使用之处也是static初始化发生之处。所有static对象和static代码都会在加载时按程序中的顺序而依次初始化。当然定义为static的东西只会被初始化一次。

private不能被继承

将一个方法调用同一个方法主题关联起来叫做绑定。Java中除了static方法和final方法,其他所有方法都是动态绑定。

private方法默认是final的,private方法不能被覆盖。

 

初始化顺序:

1、成员变量默认初始化

2、调用基类构造器,一层一层调用

3、按声明顺序调用成员初始化方法

4、调用子类构造器主体

 

Collections.sort(list)

按自然顺序排序,元素必须有可比较性,实现Comparable接口

Collection.sort(list,comparator<q super T>)

 

List iterator是一个更强大的iterator子类型,只能用于各种list的访问。

Treeset将元素放在红黑树

LinkedList是queue的实现

PriorityQueue:优先级越高,越在头越早出来。

 

一致性hash:在移除或者添加一个cache时,它能够尽可能小的改变已经存在key映射关系

Hash冲突解决方法,链地址法、开发地址法

 

 

 

 

 

 getdeclaredmethod可以获得所有方法,但不能获得继承方法和接口

getmethod可以获得公有方法和继承方法接口方法,不能私有方法。

 

 

 

 就是将Java中的各种成分映射成对象

 

 

 

 

 

 

 

 

 

 

 

 

 

 这就是自定义的classloader

 

 

 

 

 

 

 

 

 避免重复生成class类的字节码

 

 

 显式加载需要通过newInstance生成实例

 

 

 

 

 loadClass在spring框架中经常使用就是懒加载

 

 

 

 

 

 

 

JVM有哪些分区?

程序计数器,Java虚拟机栈,本地方法栈,堆,方法区(元空间)

Java栈中存放的是一个个栈帧,每一个栈帧对应一个被调用的方法。

栈帧包括局部变量表,操作数栈,方法的返回地址,指向当前方法所属的类的运行时常量池的引用,附加信息。

jvm中只有一个堆

方法区中最重要的是运行时常量池。

线程私有的有:程序计数器,本地方法栈,虚拟机栈

线程共享的有:堆和元空间。

 

Out of Memory

1、程序计数器是唯一一个在虚拟机规范中没有规定任何oom情况的区域。

2、在Java虚拟机规范中,对于Java虚拟机栈中规定了两种异常

一是,线程请求的栈深度大于虚拟机所允许的深度就会抛出stack overflow error异常。

二是,若虚拟机可以动态扩展,若扩展时无法申请到足够的内存空间,则抛出oom异常。

 3、Java虚拟机栈为执行Java方法,本地方法栈为虚拟机使用native方法服务,本地方法栈也会抛出Stack Overflower error和oom

4、Java堆可以处于物理上连续的内存空间,只要逻辑上连续即可,可固定可扩展。

若堆中没有内存完成实例分配,并且堆也无法再扩展,则会抛出oom

5、直接内存不是运行时数据区的一部分。

 

堆上的OOM测试

 1 import java.util.ArrayList;
 2 //-verbose:gc -Xms20M  -Xmx20M -Xmn10M -XX:+PrintGCDetails
 3 //-Xms20M  -Xmx20M 堆最大最小容量都是20M,则限制了堆不可以扩容。
 4 //当堆上没有没有足够的空间分配对象,又不能扩容时,就抛出了OutOfMemoryError
 5 
 6 public class Test{
 7 public static void main(String[] args){
 8       List<Test> list = new ArrayList<Test>();
 9       while(true){
10            list.add(new Test());
11        }
12 }
13 }

 

Java内存模型和线程

每个线程都有都有一个工作内存,线程只可以修改自己工作内存中的数据,然后再同步回主内存。

主内存由多个内存共享。

下面8个操作都是原子的,不可再分的:

1、lock:作用于主内存的变量,它把一个变量标识为一个线程独占的状态。

2、unlock:作用于主内存的变量,他把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

3、read:作用于主内存变量,他把一个变量的值从主内存传输到线程的工作内存,以便随后的load操作使用。

4、load:作用于工作内存的变量,他把read操作从主内存得到的变量值放入工作内存的变量副本中。

5、use 作用于工作内存的变量,他把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用的值得字节码指令时将会执行这个操作。

6、assign:作用于工作内存得变量,他把一个从执行引擎接收到得值赋给工作内存得变量。每当虚拟机遇到一个给变量赋值得字节码指令时会执行这个操作。

7、store:作用于工作内存得变量,他把一个变量值传递到主内存中,以便随后得write使用

8、write:作用于主内存得变量,他把store操作从工作内存得到得变量值放入主内存得变量中。

 

 

 

 

 

 

 

 

 只为Java方法计数,native方法为undefined

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 还可以在字符串常量池中添加字符串堆中的引用

 

 

 

 

 

 

 

判断对象是否存活===判断对象是否是垃圾

1、引用计数法

每个对象实例都有一个引用计数器,引用计数器为0的对象就会判断为垃圾。

缺点:无法解决对象之间循环引用的问题。

优点:执行效率高。

2、可达性分析法

判断对象的引用链是否可达判断是否是垃圾。

基本思想:通过一系列的称为GC root的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当GC root到这个对象不可达,则证明此对象不可用是垃圾。

可作为GC root的对象

1、Java虚拟机栈中引用的对象。

2、本地方法栈JNI引用的对象。

3、方法区中类的静态属性引用的对象。

4、方法区中常量引用的对象。

 

 

对象的分配

1、大多数情况下,对象在新生代eden区中分配。当eden区中没有足够的内存空间进行分配时,虚拟机将发起一次minor Gc(minor Gc:发生在新生代的垃圾收集动作,非常频繁,一般回收速度也比较快    full gc:发生在老年代的gc)

2、大对象直接进入老年代。

3、长期存活的对象将进入老年代

4、若survivor空间中相同年龄所有对象大小的总和>survivor空间的一半,则年龄>=该年龄的对象直接进入老年代,无须等到MaxTeuringThreshold(默认为15)中的要求。

 

空间分配担保

在发生minor gc前,虚拟机会检测老年代最大可用连续空间是否大于新生代所有对象的总空间,若这个条件成立,那么minor gc就可用确保是安全的。

若不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。

若允许,那么会继续检测老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,若大于,则尝试进行一次minor gc,尽管这次minor gc是有风险的。若小于或HandlePromotionFailure设置不允许冒险,则这时要改为一次full gc。

 

 

 

 

 

 

 

 

 

 

 

 

 

 循环引用不可能为0,清理不掉

 

主流使用可达性分析法

 

 

 

复制算法:

它可以将可用内存按照容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,则就将还存活的对象复制到另一块上面,然后再把已经使用过的内存空间一次性清理掉。

商业虚拟机:将内存分为一块较大的eden空间和两块较小的survivor,默认比例是8:1:1,即每次新生代中可用内存空间为整个新生代容量的90%,每次使用eden和其中的一个survivor。当回收时,将eden和survivor中还存活的对象一次性复制到另一块survivor上,最后清理掉eden和刚才用过的survivor,若另外一块survivor空间没有足够的内存存放上次新生代收集下来存活的对象,这些对象将直接通过分配担保机制进入老年代。

 

标记清除算法:

缺点:产生大量的不连续的内存碎片。标记和清除效率都不高。

 

标记整理算法:

标记过程和标记清除算法一样,但后续步骤不是直接对可回收对象进行清除,而是让所有存活对象都向一端移动,然后直接清理掉端边界意外的内存。

 

分代收集:

新生代:停止复制算法

老年代:标记清除或标记整理算法

 

 

 

 

 

 

 碎片化

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 默认15岁

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

垃圾收集器,前3个是新生代,后3个是老年代。

1、serial收集器:单线程(单线程的意义不仅仅说明它会使用一个cpu或者一条垃圾收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集的时候,必须暂停其他所有的工作线程,直到收集结束)是停止复制算法。对于运行在client模式下的虚拟机来说是个很好的选择。

2、parNew搜集器:serial收集器的单线程版本,是许多运行在server模式下的虚拟机首选的新生代收集器。停止复制算法。

3、parall scaverge:停止复制算法,目标是达到一个可控制的吞吐量,适合在后台运算,没有太多的交互。

4、serial old:是serial的老年代版本,多线程,标记整理算法。

5、parallel old:是parallel scaverge老年代版本,多线程,标记整理算法。

6、cms收集器:一种以获取最短停顿时间为目标的收集器,“标记清除”,有4个过程

初始标记(查找与GC Root链接的对象)

并发标记(tracing过程)

重新标记(因为并发标记时有用户线程在执行,标记结果可能有变化)

并发清除 

其中初始标记和重新标记要stop the world(停止工作线程)

优点:并发收集,低停顿

缺点:不能处理浮动垃圾、对CPU资源敏感、产生大量内存碎片。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

任何一个对象的finalize方法都只会被系统调用一次。

若对象在进行可达性分析后发现没有与GC root相连接的引用链,那么他会被第一次标记并进行一次筛选,筛选的条件是该对象是否有必要执行finalize()方法,当对象没有重写finalize()方法或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没必要执行。

若该对象被判定为有必要执行finalize方法,则这个对象会被放在一个F-Queue队列,finalize方法是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,若对象要在finalize中拯救自己,只要重新与引用链上的任何一个对象建立关联即可,那么在第二次标记时他们会被移出“即将回收”集合。

Finalize方法不是c++的析构函数:

1、与C++的析构函数不同,析构函数调用确定,而它是不确定的。

2、将未被引用的对象放置于F-Queue队列。

3、方法执行随时可能会被终止。

4、给予对象最后一次重生的机会。

 

 

 

 

 

 

 

 引用强度:

强引用>软引用>弱引用>虚引用

强引用:就是使用new出来的,抛出outofmemoryerror终止程序也不会回收具有强引用的对象。通过将对象设为null,弱化引用使其被回收。

软引用:内存不足时,GC才会回收该引用的对象的内存。

弱引用:GC时会回收。

虚引用:任何时候都可能被回收。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 相当于链表

 

 

 

 

 

posted @ 2020-02-16 19:41  chyblogs  阅读(162)  评论(0)    收藏  举报