1、JVM是什么?
1)Java Virtual Machine(Java虚拟机)的缩写;
2)实现跨平台的最核心的部分;
3).class 文件会在 JVM 上执行,JVM 会解释给操作系统执行;
4)有自己的指令集,解释自己的指令集到 CPU 指令集和系统资源的调用;
5)JVM 只关注被编译的 .class 文件,不关心 .java 源文件。
2、Java跨平台运行的原理是什么?
1).java 源文件要先编译成与操作系统无关的 .class 字节码文件,然后字节码文件再通过 Java 虚拟机解释成机器码运行;
2).class 字节码文件面向虚拟机,不面向任何具体操作系统;
3)不同平台的虚拟机是不同的,但它们给 JDK 提供了相同的接口;
4)Java 的跨平台依赖于不同系统的 Java 虚拟机。
3、JVM 的主要组成部分?及其作用?
class loader( 类加载器):加载类文件到内存。Class loader只管加载,只要符合文件结构就加载,至于能否运行,它不负责,那是有Exectution Engine 负责的;
exection engine( 执行引擎也叫解释器):负责解释命令,交由操作系统执行;
native interface( 本地接口):本地接口的作用是融合不同的语言为java所用;
Runtime Data area( 运行数据区):运行数据区是jvm的重点,我们所有所写的程序都被加载到这里,之后才开始运行;
stack(栈也叫栈内存):是java程序的运行区,是在线程创建时创建,它的生命周期跟随线程的生命周期,线程结束栈内存释放;对于栈来说不存在垃圾回收的问题,只要线程一结束,该栈就结束。栈中的数据以栈帧的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法和运行期数据的集合,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,执行完毕后,先弹出F2栈帧,再弹出F1栈帧,遵循“先进后出”原则;
heap(堆内存):一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类的加载器读取了类文件之后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分三部分:永久存储(用于存放jdk自身携带的class,interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载至此区域的数据是不会被垃圾回收掉的,只有关闭jvm释放此区域所占用的内存)区、新生区、老年代;
method area(方法区):是被所有线程共享,该区域保存的所有字段和字节方法码以及一些特殊方法如构造函数,接口代码也在此定义;
PC Register(程序计数器):每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令。
4、JVM内存管理是什么?
JVM在执行Java程序的过程中,会把它管理的内存划分为几个不同的数据区域,这些区域都有各自的用途、创建时间、销毁时间。
![image.png](https://upload-
images.jianshu.io/upload_images/12784506-b085320b1d038448.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
1)程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
2)Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
3)本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务Java方法的,而本地方法栈是为虚拟机调用Native方法服务的;
4)Java 堆(Java Heap):Java虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
5)方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
5、怎么判断对象是否可以被回收?
1)引用计数法
简单的说就是给对象添加一个计数器,每当有一个地方引用它时,计数器就加1;当引用失效,计数器就减1;任何时刻计数器为0的对象,就是不可能再使用的。
优点:效率高,实现简单
缺点:无法解决对象之间循环引用的问题
2)可达性算法
算法的基本思想是通过一系列的成为“GC Roots”的对象作为起点,从这些起点开始向下搜索,搜索的路径就成为引用链(Reference Chain),当一个对象到GC Roots没有任何的引用链相连的话,也就是该对象不可达,则证明该对象是不可用的。
6、堆和栈的区别?
1)栈内存储程序中定义的变量(基本类型和引用类型),堆中存储引用变量所引用的对象;
2)栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
3)栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的对象会被垃圾回收机制不定时的回收。
7、队列和栈是什么?有什么区别?
队列(Queue):是限定只能在表的一端进行插入和在另一端进行删除操作的线性表;
栈(Stack):是限定只能在表的一端进行插入和删除操作的线性表。
1)规则不同
队列:先进先出(First In First Out)FIFO;
栈:先进后出(First In Last Out )FILO。
2)插入和删除操作的限定不同
队列:只能在表的一端进行插入,并在表的另一端进行删除;
栈:只能在表的一端插入和删除。
3)遍历数据速度不同
队列:基于地址指针进行遍历,而且可以从头部或者尾部进行遍历,但不能同时遍历,无需开辟空间,因为在遍历的过程中不影响数据结构,所以遍历速度要快;
栈:只能从顶部取数据,也就是说最先进入栈底的,需要遍历整个栈才能取出来,而且在遍历数据的同时需要为数据开辟临时空间,保持数据在遍历前的一致性。
8、类加载器的类别
1)BootstrapClassLoader(启动类加载器)
c++编写,加载java核心库 java.*,构造ExtClassLoader和AppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
2)ExtClassLoader (标准扩展类加载器)
java编写,加载扩展库,如classpath中的jre ,javax.*或者 java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器。
3)AppClassLoader(系统类加载器)
java编写,加载程序所在的目录,如user.dir所在的位置的class
4)CustomClassLoader(用户自定义类加载器)
java编写,用户自定义的类加载器,可加载指定路径的class文件
9、什么是双亲委派机制?作用是什么?
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
作用:
1)防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全;
2)保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。
10、类加载的执行过程?
类加载的过程主要分为加载、链接(验证、准备、解析)、初始化三部分;
加载:通过类的全限定名获取到类的二进制流,然后加载到JVM中;
重点:
1)字节码来源。一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译;
2)类加载器。一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。
验证:确保Class文件的字节流中包含的信息符合虚拟机的要求,并且不会危害虚拟机的安全;
1)包括对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息;
2)对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载;
3)对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性;
4)对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问。
准备:为类变量(注意,不是实例变量)分配内存空间,并设置初始值;
重点:
1)8种基本类型的初值,默认为0;引用类型的初值则为null;
2)常量的初值即为代码中设置的值,final static tmp = 1, 那么该阶段tmp的初值就是1。
解析:虚拟机常量池的符号引用替换为直接引用过程,比如调用方法hello(),这个方法的地址是123,那么hello就是符号引用,123就是直接引用。
重点:
1)符号引用。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息;
2)直接引用。可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量;
3)在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
初始化:根据用户指定的代码初始化字段和其它资源,执行static块;
重点:
1)只对static修饰的变量或语句进行初始化;
2)如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类;
3)如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
11、什么是Java的垃圾回收机制?
垃圾回收机制,简称GC。
1)Java 语言不需要程序员直接控制内存回收,由 JVM 在后台自动回收不再使用的内存;
2)提高编程效率;
3)保护程序的完整性;
4)JVM 需要跟踪程序中有用的对象,确定哪些是无用的,影响性能。
特点
1)回收 JVM 堆内存里的对象空间,不负责回收栈内存数据;
2)无法处理一些操作系统资源的释放,如数据库连接、输入流输出流、Socket 连接;
3)垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行;
4)可以将对象的引用变量设置为 null,垃圾回收机制可以在下次执行时回收该对象;
5)JVM 有多种垃圾回收 实现算法,表现各异;
6)垃圾回收机制回收任何对象之前,会先调用对象的 finalize() 方法;
7)可以通过 System.gc() 或 Runtime.getRuntime().gc() 通知系统进行垃圾回收,会有一些效果,但系统是否进行垃圾回收依然不确定;
8)不要主动调用对象的 finalize() 方法,应该交给垃圾回收机制调用。
12、Java中都有哪些引用类型?
1)强引用:当我们使用new创建对象时,被创建的对象就是强引用,如Object object = new Object(),其中的object就是一个强引用了。如果一个对象具有强引用,JVM就不会去GC它,JVM宁可会报OOM来终止程序,也不回收该对象。
2)软引用: 如果一个对象只具备软引用,如果内存空间足够,那么JVM就不会GC它,如果内存空间不足了,就会GC该对象。
3)弱引用: 如果一个对象只具有弱引用,只要JVM的GC线程检测到了,就会立即回收。弱引用的生命周期要比软引用短很多。不过,如果垃圾回收器是一个优先级很低的线程,也不一定会很快就会释放掉软引用的内存。
4)虚引用:如果一个对象只具有虚引用,那么它就和没有任何引用一样,随时会被JVM当作垃圾进行GC。
13、jvm有哪些垃圾回收算法?
常用的垃圾回收算法有如下四种:标记-清除算法、复制算法、标记-整理算法和分代收集算法。
标记-清除算法
标记清除分为两个过程,即标记阶段和清除阶段。首先从根Root出发将可以达到的对象进行标记,遍历堆将未标记的对象进行删除。
缺点:①标记清除效率不高,需要遍历整个堆空间,②会产生碎片化空间,清除后产生不连续的地址空间,当程序在以后的运行过程中需要分配较大对象时,如果无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。清除时会暂停程序的进行,等待标记清除结束后才会恢复应用程序的运行,这也是Stop-The-World这个单词的来历。该算法一般应用于老年代,因为老年代的对象生命周期比较长。
复制算法
复制算法将可用内存按容量划分为大小相等的两块,每次只使用一块内存,当这块内存用完后,会将还存活的对象(这个地方我有点不理解,就是将还存活的对象复制到另一块内存,是不是也用到了标记呢?标记那些还存活的对象,然后复制的时候,遍历堆内存,将标记的对象复制到另一块内存中,不过我查了相关的资料,发现没有标记这一过程,标记-清除算法的标记过程是为清除做准备的,需要获取的内容是哪些对象是没有用的,可达性分析过程中找到的都是需要可达的对象,也就是不可回收的对象,不是需要回收的对象,所以需要为有用的对象作标记,在清除的时候清除没有标记的对象,然后再将标记的对象去除标记,好准备下一次GC;但是复制算法需要获取的是哪些对象是有用的,也就是说可达性分析的过程中已经完成了筛选,分析过程中就可以将这一部分对象复制到另一半内存中,然后把原来的一半内存完全清除就可以了,没有标记的必要。)复制到另一块等大的内存中,最后再把已使用过的内存空间清理掉。这样每次都只会对半个内存回收,分配时不需要考虑内存空间碎片等问题。缺点:但这样的代价就是牺牲了一半的内存,成本太高。
这种算法适用于处理新生代 ,在Java JVM中新生代不需要按照1:1的比例来分配,而是分为一块较大的Eden(伊甸园区) 和 两块Survivor(存活区S0,S1)区域(S0,S1一个是from,另一个是to,但他们不是固定的,他们会随着Minor GC发生变化),每次使用Eden和其中一块的Survivor区域,当进行垃圾回收时,会将Eden和使用过的Survivor中存活的对象复制到另一块没使用过的Survivor区域中。HotSpot默认Eden 和 两块Survivor比例为8:1:1,这样相对1:1浪费一半的内存来说,JVM只浪费了10%的堆内存。
标记-整理算法
这个算法分为三部分:一是标记出所有需要被回收的对象;二是把所有存活的对象都向一端移动;三是把所有存活对象边界以外的内存空间都回收掉。
标记-整理算法解决了复制算法复制效率低、空间利用率低的问题,同时也解决了内存碎片的问题。
分代收集算法
根据对象生存周期的不同将内存空间划分为不同的块,然后对不同的块使用不同的回收算法。一般把Java堆分为新生代和老年代,新生代中对象的存活周期短,只有少量存活的对象,所以可以使用复制算法,而老年代中对象存活时间长,而且对象比较多,所以可以采用标记-清除和标记-整理算法。
14、jvm有哪些垃圾回收器?
新生代收集器:
1)Serial
2)ParNew
3)Parallel Scavenge
老年代收集器:
1)Serial Old
2)CMS
3)Parallel Old
堆内存垃圾收集器:
1)G1
15、既然有GC机制,为什么还会有内存泄露的情况发生?
理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被广泛使用于服务器端编程的一 个重要原因)。然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被 GC 回收,因此也会导致内存泄露的情况发生。
例如 hibernate 的 Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中 可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。
16、jvm有哪些调优工具?
17、常用的 jvm 调优的参数都有哪些?
18、介绍一下 CMS 垃圾回收器?
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
19、简述分代垃圾回收器是怎么工作的?
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是8:1:1,它的执行流程如下:
把 Eden + From Survivor 存活的对象放入 To Survivor 区;清空 Eden 和 From Survivor 分区;From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。
未完待续