Java知识点整理二

时间:2022-11-17 15:22:41

六、简要介绍一下 Object 类中 wait 和 notiify 方法

        wait 和 notify 的本质是基于条件对象的,而且只能由已经获得锁的线程调用。java 的每个 Object 都有一个隐式锁,这个

隐式锁关联一个 Condition 条件对象,线程拿到这个隐式锁(比如进入 synchronized 代码区域),就可以调用 wait,语义是

在 Condition 条件对象上等待,其他的线程可以在这个 Condition 条件对象上等待,等满足条件之后,就可以调用 notify 或者 

notifyAll 来唤醒指定或者所有在此条件对象上等待的线程。


七、wait 和 sleep 的区别

           sleep() 是 Thread 类中的方法,而 wait() 则是 Object 类中的方法。sleep() 方法导致了程序暂停,但是它的监控状态依

然保持着,当指定的时间到了又会自动恢复运行状态。在调用 sleep() 方法的过程中,线程不会释放对象锁。

           wait() 方法会导致线程放弃对象锁,进入等待此对象的等待池,只有针对此对象调用 notify() 方法后本线程才会进入对象

等待池准备获取对象锁进入运行状态。


八、JVM 的内存结构

           JVM 在运行时将数据划分为了 6 个区域来储存,而不仅仅是大家熟知的 Heap 区域。

            1) PC Register(PC 寄存器)

                PC 寄存器是一块很小的内存区域,主要作用是记录当前线程所执行的字节码的行号。字节码解释器工作时就是通过改

变当前线程的程序计数器选取下一条字节码指令来工作的。任何分支、循环、方法调用、判断、异常处理、线程等待以及恢复线

程、递归等等都会通过这个计数器来完成的。

               由于 Java 多线程是通过交替线程轮流切换并分配处理器时间的方式来实现的,在任何一个确定的时间里,在处理器

的一个内核中只会执行一条线程中的指令。

               因此为了线程等待结束后需要恢复到正确的位置执行,每条线程都会有一个独立的程序计数器来记录当前指令的行号。

计数器之间相互独立、互不影响,我们称这块内存为“线程私有”的内存。

                如果所调用的方法为 native 方法,则 PC 寄存器中不存储任何信息。

            2)JVM 栈

                JVM 栈是线程私有的,每个线程创建的同时都会创建 JVM 栈,JVM 栈中存放的为当前线程中局部基本类型的变量

(java 中定义的八种基本类型:boolean, char, byte, short, int, long, float, double)、部分的返回结果以及 Stack Frame。

                非基本类型的对象在 JVM 栈上仅存放一个指向堆上的地址,因此 Java 中基本类型的变量是值传递,而非基本类型的

变量是引用传递,Sun JDK 的实现中 JVM 栈的空间是在物理内存上分配的,而不是从堆上分配的。

                由于 JVM 栈是线程私有的,因此其在内存分配上非常高效,并且当线程运行完毕后,这些内存也就被自动回收。

                当 JVM 栈的空间不足时,会抛出 *Error 的错误,在 Sun JDK 中可以通过 -Xss 来指定栈的大小。

            3)堆(Heap)

                Heap 是大家最为熟悉的区域,它是 JVM 用来存储对象实例以及数组值得区域,可以认为 Java 中所有通过 new 创建

的对象的内存都在此分配,Heap 中的对象的内存需要等待 GC 进行回收,Heap 在 32 位的操作系统上最大为 2G,在 64 位的

操作系统上则没有限制,其大小通过 -Xms 和 -Xmx 来控制,-Xms 为 JVM 启动时申请的最小 Heap 内存,默认为物理内存的 

1/64 但小于 1G,-Xmx 为 JVM 可申请的最大 Heap 内存,默认为物理内存的 1/4,默认当空余堆内存小于 40% 时,JVM 会

增大 Heap 的大小到 -Xmx 指定的大小,可通过 -XX:MinHeapFreeRatio= 来指定这个比例,当空余堆内存大于 70% 时,JVM 

会将 Heap 的大小往 -Xms 指定的大小调整,可通过 -XX:MaxHeapFreeRatio= 来指定这个比例。但对于运行系统而言,为了

避免频繁的调整 Heap Size 的大小,通常都会将 -Xms 和 -Xmx 的值设成一样,因此这两个用于调整比例的参数通常是没用

的。其实 JVM 中对于堆内存的分配、使用、管理、收集等有更为精巧的设计,此处不做详细解释。

                当堆中需要的使用的内存超过其允许的大小时,会抛出 OutOfMemory 的错误信息。

            4)方法区域(MethodArea)

                方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为 final 类型的常量、类中的 

Field 信息、类中的方法信息。当开发人员在程序通过 Class 对象中的 getName、isInterface 等方法来获取信息时,这些数据

都来源于方法区域、可见方法区域的重要性。同样,方法区域也是全局共享的,它在虚拟机启动时在一定的条件下它也会被 

GC,当方法区域需要使用的内存超过其允许的大小时,会抛出 OutOfMemory 的错误信息。

                在 Sun JDK 中这块区域对应的为 PermanetGeneration,又称为持久代,默认为 64M,可通过 -XX:MaxPermSize 

来指定其大小。

            5)运行时常量池(RuntimeConstant Pool)

            存放类中固定的常量信息、方法和 Field 的引用信息等,其空间从方法区域中分配。类或接口的常量池在该类的 class 文

件被 java 虚拟机成功装载时分配。

            6)本地方法堆栈(NativeMethod Stacks)

                JVM 采用本地方法堆栈来支持 native 方法的执行,此区域用户存储每个 native 方法调用的状态。


九、强引用、软引用和弱引用的区别

        在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft 

Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。

        强引用就是指在程序代码之中普遍存在的,类似“Object obj = new Boject()”这类的引用,只要强引用还在,垃圾收集

器永远不会回收掉被引用的对象。

        软引用用来描述一些还有用,但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这

些对象列进行回收范围之中并进行二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后,提

供了 SoftReference 类来实现软引用。

          弱引用也是用来描叙非必需对象的,但是它的强度比软引用更弱一些,被弱引用的关联对象只能生存到下一次垃圾收集发

生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用的关联对象。在 JDK 1.2 之后,提供了 

WeakReference 类来实现弱引用过。

           虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间

构成影响,也无法通过虚引用来获取一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收

时收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用。 


十、数组在内存中如何分配

        在 Java 中,数组变量是引用类型的变量,同时因为 Java 是典型的静态语言,因此它的数组也是静态的,所以想要使用就

必须先初始化(为数组对象的元素分配元素空间)。对于 Java 数组的初始化,有以下两种方式:

        1)静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组长度;

        2)动态初始化:初始化时由程序员显式指定数组的长度,由系统为数组每个元素分配初始值。

        静态初始化方式,程序员虽然没有指定数组长度,但是系统已经自动帮我们分配了,而动态初始化,程序员虽然没有显式的

指定初始化值,但是因为 Java 数组是引用类型变量,所以系统也为每个元素分了初始化值 null,当然不同类型的初始化值也是

不一样的,假设是基本类型 int 类型,那么系统分配的初始化值也是对应的默认值 0 。