面试题1-jvm

1.jvm

jvm就是Java虚拟机 ,它就是编译后 .class文件和硬件系统之间的接口 它的主要特征就是平台无关性

原理:

编译后的程序不直接在硬件系统的cpu执行 ,而是在jvm上执行,它屏蔽了具体平台的相关信息,它只需要Java程序生成字节码文件就能直接在jvm上运行 不用修改任何东西。

JVM = 类加载器 classloader + 执行引擎 execution engine + 运行时数据区域 runtime data area

类加载器

classloader 把硬盘上的class 文件加载到JVM中的运行时数据区域, 但是不负责这个类文件能否执行,而这个是 执行引擎
负责的。将class装载到jvm 一般需要用到一个关键字 叫 classLoader 类加载器
它的作用就是装载.class文件,它的加载过程就是先在自己加载过的类中去查看是否以及加载了这个类,,如果加载就直接返回原来加载过的类
没有就直接加载并放入缓存中

有两种方式 显示和隐式

隐式方式就是:运行过程中,碰到new方式生成的对象,隐式调用classLoader 到JVM
显式:通过class.forname()去动态加载

类加载器分为4类

  1. Bootstrap Classloader   :   启动类加载器,用来加载 %JAVA_HOME%/jre/lib 下的, 如 rt.jar中的class文件 或者 xbootclasspath选项指定的jar包
  2. Extension Classloader :     扩展类加载器 , 用来加载 %JAVA_HOME%/jre/ext 中的class文件 或者 -Djava.ext.dirs指定目录下的jar包
  3. Application Classloader  :  应用类加载器 , 用来加载classpath下的class文件
  4. Custom  Classloader : 用户自定义类加载器,用来加载自定义内容.此加载器需要用户自己继承Classloader类

执行引擎的作用就是执行字节码 或者执行本地方法

类的生命周期:加载 连接 初始化 使用 卸载

1.加载:就是将源文件的class文件找到类的信息将其加载到方法区中,然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口。
2.连接:分为 验证 准备 解析
2.1验证就是为了保证jvm能执行
2.2 准备:为类的静态变量分配内存,并将其初始化为默认值
2.3解析:把类中的符号引用转换为直接引用
3.初始化:(为类的静态变量赋予正确的初始值 ),并且只执行一次
4.使用:对象实例化、垃圾收集、对象终结
5.类卸载:程序中不再有该类的引用,该类也就会被JVM执行垃圾回收,从此生命结束…

运行时数据区指的就是jvm内存空间的划分和分配 有5个区域

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

当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,线程恢复等都是依赖计数器来完成。
Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线程切换能恢复到正确的位置,每条线程都需要一个独立的程序计数器,所以它是线程私有的。

2.堆:

堆被所有线程共享区域,在虚拟机启动时创建,唯一目的存放对象实例。
堆区是gc的主要区域,通常情况下分为两个区块年轻代和年老代。更细一点年轻代又分为Eden区最要放新创建对象,From survivor 和
To survivor 保存gc后幸存下的对象,默认情况下各自占比 8:1:1

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

本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。

4.虚拟机栈(线程私有)

虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,Java虚拟机栈,它是线程私有的,生命周期与线程相同。
每个方法执行都会创建一个栈帧,用于存放局部变量表,操作栈,动态链接,方法出口等。每个方法从被调用,直到被执行完。对应着一个栈帧在虚拟机中从入栈到出栈的过程

扫描二维码关注公众号,回复: 12632976 查看本文章

5.方法区:

被所有线程共享区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代(permanment
generation) 垃圾回收很少光顾这个区域,不过也是需要回收的,主要针对常量池回收,类型卸载。
常量池用于存放编译期生成的各种字节码和符号引用,常量池具有一定的动态性,里面可以存放编译期生成的常量;运行期间的常量也可以添加进入常量池中,比如string的intern()方法。

双亲委派

双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是使用组合(Composition)关系来复用父加载器的代码。

类加载器的双亲委派模型在JDK1.2期间被引入并广泛用于之后几乎所有的Java程序中,但它并不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载实现方式。

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

双亲委派模型优点

Java类随着它的类加载器一起具备了一种带有优先级的层次关系,例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都会委派给出于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类(该类具有系统的Object类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM中,那就热闹了),并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也就无法保证了,应用程序也将变得一片混乱。

GC

垃圾回收机制 他是jvm中的一个低优先级的线程,一般是不会执行的,只有在虚拟机空闲
或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

GC常用算法

引用计数器算法

引用计数算法很简单,它实际上是通过在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加一,如果删除对该对象的引用,那么它的引用计数就减一,当该对象的引用计数为0时,那么该对象就会被回收。

引用计数垃圾收集机制不一样,它只是在引用计数变化为0时即刻发生,而且只针对某一个对象以及它所依赖的其它对象。所以,我们一般也称呼引用计数垃圾收集为直接的垃圾收集机制

但是这种引用计数算法有一个比较大的问题,那就是它不能处理环形数据 -
即如果有两个对象相互引用,那么这两个对象就不能被回收,因为它们的引用计数始终为1。这也就是我们常说的“内存泄漏”问题

算法特点

  1. 需要单独的字段存储计数器,增加了存储空间的开销;

  2. 每次赋值都需要更新计数器,增加了时间开销;

  3. 垃圾对象便于辨识,只要计数器为0,就可作为垃圾回收;

  4. 及时回收垃圾,没有延迟性;

  5. 不能解决循环引用的问题;

可达性分析算法

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

这个算法的基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC
Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。

那么问题又来了,如何选取GCRoots对象呢?在Java语言中,可以作为GCRoots的对象包括下面几种:

(1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。

(2). 方法区中的类静态属性引用的对象。

(3). 方法区中常量引用的对象。

(4). 本地方法栈中JNI(Native方法)引用的对象。

java中垃圾收集

标记-清除:
这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次GC动作。

复制算法:
为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那份内存交Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中。(java堆又分为新生代和老年代)

标记-整理
该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。

分代收集
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理
或者 标记-清除。

java中存在的四种引用

1.强引用:例如string s=”abc”只要强引用存在,就不会发生垃圾回收
2.软引用:内存不足就回收,内存足够就不回收,通常和引用队列一起使用,如果软引用对象被垃圾回收,jvm会把这个软引用对象加入与之关联的引用队列中

在这里插入图片描述

GC收集器有哪些

串行垃圾回收器、并行垃圾回收器、并发标记扫描垃圾回收器、G1垃圾回收器

Jvm调优

对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full
GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注Full GC,因为它会对整个堆进行整理
1.设置新生代大小 不能太大,也不能太小 太小GC就会非常频繁 太大会导致 老生代过小,从而诱发Full GC 一般说来新生代占整个堆1/3比较合适
2.设置堆内存的大小
3.设定垃圾回收器,年轻代用-xx:+UserparNewGc 年老代用-XX:+UserConCMarkSweepGc

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zyf_fly66/article/details/114011267