Java编程思想学习笔记_1(Java内存和垃圾回收)

时间:2023-03-08 15:10:55
Java编程思想学习笔记_1(Java内存和垃圾回收)

1.Java中对象的存储数据的地方:

共有五个不同的地方可以存储数据.

1)寄存器.最快,因为位于处理器的内部,寄存器按需求分配,不能直接控制.

2)堆栈.位于通用RAM,通过堆栈指针可以从处理器那里获得直接支持.堆栈指针向下移动,分配新的内存,向上移动,则释放那些内存.Java系统必须知道存储在堆栈内的所有项目的确切的生命周期.

3)堆.编译器不需要知道存储的数据在堆里活多长时间.

4)常量存储.通常直接放在代码内部.

5)非RAM存储,如果数据完全存活与程序之外,那么它可以不受程序的任何控制,在程序没有运行的时候也存在,其中最基本的例子是流对象和持久化对象.

2.Java数据的存储:

根据《Java虚拟机规范》的规定,运行时数据区通常包括这几个部分:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。

1)程序计数器,程序计数器是一块较小的区域,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的模型里,字节码指示器就是通过改变程序计数器的值来指定下一条需要执行的指令。分支,循环等基础功能就是依赖程序计数器来完成的。在多线程中,需要通过程序计数器才可以了解到当前线程执行到哪一个位置,在下次抢到执行权后,从原来的位置开始.因此,程序计数器是线程私有的.如果虚拟机正在执行的是一个Java方法,则计数器指定的是字节码指令对应的地址,如果正在执行的是一个本地方法,则计数器指定问空undefined.

2)Java栈也被称为虚拟机栈,和程序计数器一样也是线程私有的,生命周期和线程相同,虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。虚拟机栈又分为局部变量表(存储方法所要用到的局部变量),操作数栈(数据结构里用来执行计算过程),指向运行时常量池的引用,方法返回地址以及附加消息.Java栈是Java方法执行的内存模型.

3)本地方法栈:Java栈是为执行Java方法服务的,本地方法栈是为执行本地方法服务的.

4)堆:在Java中用来存储对象本身(包括对象中的成员变量,数组),Java的自动垃圾回收机制主要就是回收这一部分的空间.堆是所有线程共享的,Java虚拟机只有一个堆.

5)方法区:方法区和堆一样也是被线程所共享的,在方法区中,存储了每个类的信息(包括类的名称,方法信息,字段信息),静态变量,常量以及编译器编译后的代码等。在方法区中有一个重要的部分叫做常量池,当类和接口被加载到JVM中,对于的常量池就被加载出来.在运行期间可以把新的常量放入常量池中.

3.Java的自动垃圾回收机制:终结处理和垃圾回收:

1)finalize方法详解:

  finalize方法为Object的protected方法,子类可以覆盖此方法实现操作,GC在回收对象前调用此方法.不建议用finalize方法完成“非内存资源”的清理工作,但建议用于:① 清理本地对象(通过JNI创建的对象);② 作为确保某些非内存资源(如Socket、文件等)释放的一个补充:在finalize方法中显式调用其他资源释放方法.

  首先,大致描述一下finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收.否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法.执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”.(也就是说当对象变成GC Roots不可到达的时候,对象先执行finalize方法,此时由于存在方法的调用,对象复活(但是内部标记显示已经调用过finalize方法)之后再判断的时候,如果仍然为GC Roots不可到达,且内部标记显示已经调用过finalize方法,则直接回收).可以手动的在finalize方法内部显示关闭资源或者复活对象.

  另外由于finalize方法调用的不确定性(实际上垃圾回收线程的优先级是较低的),因此极有可能出现了垃圾但是未被及时回收.

2)垃圾回收器工作原理:

  其他系统的垃圾回收机制:

  ①引用计数法

  每个对象包含一个引用计数器,当有引用连接至对象的时候,引用计数+1,当引用离开作用域或被置位null的时候,引用计数-1.垃圾回收器会在含有全部对象的列表中进行遍历,如果发现某个对象计数值变为0的时候,则会立即释放该对象.但是这个方法有个缺陷,也就是对象之间存在循环调用的情况下,则会出现对象应该被回收,但是计数器却不为0的情况.如下代码演示了循环调用:

public class Test2 {
public static void main(String[] args) {
Dog d=new Dog();
Cat c=new Cat();
d.cat=c;
c.dog=d;
     d=null;
     c=null;
}
}
class Dog {
public Cat cat;
}
class Cat {
public Dog dog;
}

  ②优化的垃圾回收:

  对于任何活的对象,一定能追朔到其存活在堆栈或者静态存储区之中的引用.由此,如果从堆栈和静态存储区域开始,遍历所有的引用,就能找到活的对象.对于发现的每个引用必须追踪它所引用的对象,然后是此对象包含的引用,知道根源于堆栈和静态存储区的引用所形成的网络全部被访问为止.没有被访问到的对象即是需要被垃圾回收机制处理的对象.

  ③Java的垃圾回收机制:

  停止复制:先暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全都是垃圾.当对象被复制到新堆的时候,它们是一个挨着一个的,所以新堆能紧凑排列.但是在Java中,大型对象仍然不会被复制.内含小型对象的那些块被复制和整理.

  标记清扫:从堆栈和静态存储区出发,遍历所有的引用进而找出所有存活的对象,每当找到一个存活的对象就会设置一个标记,最后当所有标记工作都完成的时候,没有被标记的对象将会被释放.所以剩下的堆空间都是不连续的.Java虚拟机将会监视:如果所有的对象都很稳定,垃圾回收器的效率降低的话,就切换到"标记清扫"模式,同样虚拟机会跟综标记清扫的结果,如果堆空间中出现很多碎片,就会切换回停止复制方式.这就是Java虚拟机的垃圾回收机制的"自适应"技术.

4.方法接受基本数据类型时候的自动类型提升:

  传入byte类型的方法:byte(优先匹配)==>short==>int==>float==>double.(byte数据不能传入char参数)

  传入char类型的方法:char(优先匹配)==>int==>float==>double(跳过short,char数据不能传入short参数)

  传入short类型的方法:short(优先匹配)==>int==>float==>double.

  传入int类型的方法:int(优先匹配)==>float==>double.

  传入float类型的方法:float(优先匹配)==>double

  传入double类型的方法:只有double的参数才能匹配.