JVM常识了解一下

JVM

运行时数据区域包括哪些?

程序计数器(线程私有):当前线程所执行字节码的行号指示器

Java 虚拟机栈(线程私有): 为虚拟机执行 Java 方法(字节码)服务, 虚拟机栈描述的是 Java 方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧,存储局部变量表、操作栈、动态链接、方法出口。 每一个方法被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

**本地方法栈(线程私有)**:(Native Method Stacks)为虚拟机使用到的 Native 方法服务。

java 堆(线程共享):虚拟机启动时创建(内存最大),用于存放对象实例。垃圾收集器主要管理的就是 Java 堆。Java 堆在物理上可以不连续,只要逻辑上连续即可。

方法区(线程共享): 存储已被虚拟机加载的类信息、常量、静态
变量、即时编译器编译后的代码
等数据,也不需要连续的内存

运行时常量池: 方法去的一部分,保存Class文件的符号引用、翻译出来的直接引用。运行时常量池可以在运行期间将新的常量放入池中。用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

如何判断对象死亡?

引用计数法: 给对象添加一个引用计数器,每当有一个地方引用它,计数器就+1,;当引用失效时,计数器就-1;任何时刻计数器都为0的对象就是不能再被使用的。缺点在于:很难解决对象之间的循环引用问题。

根搜索算法:通过 “GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的。

java 的4种引用方式

强引用 Strong Reference: 把一个对象赋给一个引用变量,这个引用变量就是一个强引用。Object obj = new Object();这个就是强引用,垃圾收集永远不会回收被引用的对象。

软引用Soft Reference还有用,但非必须的对象。在内存溢出前,会被列入回收范围,并进行二次回收。

弱引用 Weak Reference: 只能生存到下一次垃圾收集发生前

虚引用 Phantom Reference:目的是希望在这个对象被收集器回收时,收到一个系统通知

垃圾回收基本回收策略

标记-清除(Mark-Sweep):第一阶段标记所有被引用的对象,第二阶段遍历整个堆,删除未标记对象,需要暂停,会产生内存碎片

复制(Copying)-新生代:划分2块,回收时,将未使用的复制到另一块,空间成本高,在对象存活率较高时,需要执行较多的复制操作,效率会变低。一般用来回收新生代,一般是8:1,1是新生代。

标记-整理(Mark-Compact)-老年代: 同标记,第二阶段把存活对象“压缩”到堆的其中一块,按顺序排放

分代收集算法:把堆分为新生代(用复制算法)老生代(用标记-清理)

Minor GC (新生代) Full GC (老生代),一般老比新慢10倍以上。

**永久代(Permanent Generation)**: 用于存储被 JVM 加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据(java8已经移除,被元数据区取代,元空间并不在虚拟机中,而是使用本地内存)

Class文件

组成:Class文件是一组以8位字节为基础单位的二进制流,各个数据项目间没有任何分隔符。当遇到8位字节以上空间的数据项时,则会按照高位在前的方式分隔成若干个8位字节进行存储。

魔数: 每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件

版本号:第5,6字节是次版本号,第7,8字节是主版本号。

类加载器

作用:实现加载动作,用于确定类。任意一个类都需要由类加载器这个类本身确立其在虚拟机中的唯一性

什么是双亲委派模型?

双亲委派模型要求除了顶层的启动类加载器外,其余加载器都应当有自己的父类加载器。类加载器之间的父子关系,通过组合关系复用。

工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有到父加载器反馈自己无法完成这个加载请求(它的搜索范围没有找到所需的类)时,子加载器才会尝试自己去加载。

为什么要使用双亲委派模型,组织类加载器之间的关系?

Java类随着它的类加载器一起具备了一种带优先级的层次关系。比如java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都是委派给启动类加载器进行加载,因此Object类在程序的各个类加载器环境中,都是同一个类。

如果没有使用双亲委派模型,让各个类加载器自己去加载,那么Java类型体系中最基础的行为也得不到保障,应用程序会变得一片混乱。

什么是volatile?

关键字volatile是Java虚拟机提供的最轻量级的同步机制(线程不安全),性能优于锁。2个特性:

  1. 保证此变量对所有线程的可见性。当一条线程修改了这个变量的值,新值对于其他线程是可以立即得知的。

  2. 禁止指令重排序优化。普通变量仅仅能保证在该方法执行过程中,得到正确结果,但是不保证程序代码的执行顺序

4种锁状态

在JavaSE1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。

锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区,避免了线程切换的开心。(让自己循环取值,等其他人完成后改变值才醒来,占cpu,但恢复快)缺点是占用处理器时间。

偏向锁:消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。即在无竞争的情况下,把整个同步都消除掉。这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。

轻量级锁:在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。

Java对象的生命周期

创建阶段 、 应用阶段 、不可见阶段 、不可达阶段 、收集阶段 、终结阶段、 对象空间重新分配阶段 详情

描述一下JVM加载class文件的原理机制?

JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。

什么是类加载机制?

虚拟机的类加载机制是 :虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。

JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化(后面还有 使用和卸载)。

GC是什么?为什么要有GC?

GC是垃圾回收,java自动检测不在作用域的对象,自动清理内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。GC通过有向图确定对象是否 “可达”,对不可达对象进行回收。可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行

Java 中堆和栈有什么区别?

JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。

​ 栈:在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。

​ 堆:堆内存用来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象,引用变量就相当于是为数组或者对象起的一个名称。