JVM知识点总结

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」。

1、Object类下有哪些方法

(1)clone方法

保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

(2)getClass方法

final方法,获得运行时类型。

(3)toString方法

该方法用得比较多,一般子类都有覆盖。

(4)finalize方法

该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。

(5)equals方法

该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。

(6)hashCode方法

该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。

(7)wait方法

wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生。

(1)其他线程调用了该对象的notify方法。

(2)其他线程调用了该对象的notifyAll方法。

(3)其他线程调用了interrupt中断该线程。

(4)时间间隔到了。

此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

(8)notify方法

该方法唤醒在该对象上等待的某个线程。

(9)notifyAll方法

该方法唤醒在该对象上等待的所有线程。

2、JVM 的主要组成部分及其作用

JVM包含两个子系统和两个组件。

两个子系统为:Class loader(类装载)、Execution engine(执行引擎);

两个组件为:Runtime data area(运行时数据区)、Native Interface(本地接口)。

  • Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。
  • Execution engine(执行引擎):执行classes中的指令。
  • Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。
  • Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。

作用 :首先通过编译器把 Java 代码转换成字节码,类加载器(Class。Loader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

3、JVM内存模型划分

jvm内存模型共分为虚拟机栈、本地方法栈、堆、方法区和程序计数栈五个部分。

(1)程序计数器(线程私有)

每个线程都有一个独立的程序计数器,计数器所记录的是虚拟机字节码指令当前的地址。

(2)虚拟机栈(线程私有):

每个线程对应一个虚拟机栈,栈中的每一个栈帧对应一个方法。它的生命周期与线程相同。每个方法被执行时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接和方法返回地址等信息。每个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

1)局部变量表

存放了编译器可知的各种基本数据类型(int,long,short,double,float,char,byte,boolean)、对象引用(reference类型)和returnAddress类型(指向一条字节码指令的地址)。同时在编译器就确定了局部变量表的最大容量。

2)操作数栈

虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。

3)动态链接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连 接。

4)方法返回地址

当方法被执行后,退出方法有两种:遇到返回字节码指令或者产生异常,并且异常没有在该方法体内得到处理。但无论哪种退出方式都需要返回被调用位置,正常退出时,一般使用栈帧中保存的地址,异常退出时则由异常处理表来确定返回地址。

3.本地方法栈(线程私有)

和虚拟机栈类似,但主要为虚拟机使用到的Native方法服务。

4.Java堆(线程共享)

所有线程共享,在虚拟机创建时启动,用于存放对象的实例。 堆是JVM内存占用最大,管理最复杂的一个区域。唯一的途径就是存放对象实例:所有的对象实例以及数组都在堆上进行分配。

5.方法区(线程共享)

所有方法线程共享的一块内存区域,用于存储已经被虚拟机加载的类信息,常量,静态变量等。

4、描述深克隆和浅克隆区别

浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,

深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,

使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。

5、JAVA堆栈的区别

  • 物理地址

堆的物理地址分配对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩)

栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。

  • 内存分别

堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。

栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。

  • 存放的内容

堆存放的是对象的实例和数组。因此该区更关注的是数据的存储

栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。

  • 程序的可见度

堆对于整个应用程序都是共享、可见的。

栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。

6、描述新生代、老年代、持久代区别

  • 年轻代

所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。

  • 年老代

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

  • 持久代

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

7、描述强引用、软引用、弱引用、虚引用区别

强引用:发生 gc 的时候不会被回收。(GC的主要作用就是自动释放逻辑堆里实例对象所占的内存)

软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。

弱引用:有用但不是必须的对象,在下一次GC时会被回收。

虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。

  1. 如何判断一个对象应该被回收?

一般有两种方法来判断:

引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;

可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

  1. 描述类的生命周期

1、加载:查找,并且加载类的二进制数据

2、连接

3、验证:确保类被正确加载

4、准备:为类的静态变量分配内存,并且初始化为默认值

5、解析:把类中的符号引用转化为直接引用

6、初始化:为类的静态变量赋予正确的初始值。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%