JVM

1.    JVM基本概念

JVM是可运行Java代码的假象计算机,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收、堆和一个存储方法域。JVM是运行在操作系统之上的它与硬件没有直接的交互。

2.    Jvm原理

运行期环境代表着Java平台,开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件),再然后字节码被装入内存,一旦字节码进入虚拟机,他就会被解释执行或者是被即时代码发生器有选择的转换成机器码执行。

JVM在它的生命周期中的任务就是运行Java程序,因此当Java程序启动的时候就产生JVM的一个实例,当程序运行结束的时候该实例也就消失了。JVM是程序与底层操作系统和硬件无关的关键。

3.    JVM体系结构

(1)    Class Loader类加载器

负责加载.class文件,class文件在文件开头有特定的文件标识,并且ClassLoader负责class文件的加载等,至于它是否可以运行则由Execution Engine(执行引擎)决定。作用有定位和导入二进制class文件;验证导入类的正确性;为类分配初始化内存;帮助解析符号引用。

(2)    Native Interface本地接口

本地接口的作用是融合不同的编程语言为Java所用。

(3)   Execution Engine执行引擎

     执行包在装载类的方法中的指令,也就是方法。

(4)   Runtime data area运行数据区

     虚拟机内存或者Jvm内存在整个计算机内存中开辟一块内存存储Jvm需要用到的对象变量等,运行区又分很多小区,分别为:方法区,虚拟机栈,本地方法栈,堆,程序计数器。

4.    JVM数据运行区详解(栈管运行,堆管存储)

说明:JVM调优主要是优化Heap堆和Method Area方法区。

 

(1)    Native Method Stack本地方法栈

它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。当线程调用java方法时,虚拟机会创建一个新的栈帧并压入Java栈,然而当它调用的是本地方法时,虚拟机会保持Java栈不变,不再有线程的Java栈中压入新的帧,虚拟机只会简单的动态链接并直接调用指定的本地方法。例如某个虚拟机实习的本地方法接口时使用C连接模型的话,那么它的本地方法就是C栈。

(2)    PC Register程序计数器

每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码(下一个要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略。

(3)    Method Area方法区

方法区是被所有线程共享,所有字段和字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说所有定义的方法的信息都保存在该区域,此区域属于共享区间。静态变量、常量、类信息、运行时常量存在方法区中,实例变量存在堆内存中。

(4)    Stack栈

栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是随着线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题。基本类型的变量和对象的引用变量都是在函数的栈内存中分配。栈主要存储3类数据:本地变量,输入参数和输出参数以及方法内的变量;虚拟机只会对栈进行两种操作,记录出栈、入栈的操作;栈帧数据,包括类文件、方法等。

栈中数据都是以栈帧的格式存在,栈帧是一个内存区域是一个数据集,是一个有关方法和运行期数据的数据集,当一个方法被调用时就产生了一个栈帧被压入到栈中。

(5)    Heap堆

堆这块区域是JVM中最大的,应用的对象和数据都存在这个区域,这块区域也是线程共享的,是gc主要的回收区,一个JVM实例只存在一个堆类存,堆的大小是可以调节的。类加载期读取了类文件后,需要把类、方法、常变量放到堆内存中。堆内存分为三部分:

新生区:新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集结束生命。新生区又分为两部分:伊甸区和幸存区,所有的类在伊甸区被new出来。幸存区有两个区0区和1区。当伊甸区的空间用完时,程序又需要创建对象,JVM的垃圾回收器对伊甸区进行垃圾回收,将伊甸区中剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。如果1区也满了再移动到养老区。若养老区也满了,那么这个时候将产生Major GC(FullFC),进行养老区内存清理。若养老区执行FullGC之后发现依然无法进行对象的保持,就会产生OOM(OutOfMemoryError)异常。

如果出现java.lang.OutOfMemoryError.java heapspace异常,说明Java虚拟机的堆内存不够。原因有二:a.Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。B.代码中创建了大量的对象,并且长时间不能被垃圾收集器收集(存在被引用)。

养老区:养老区用于保存从新生区筛选出来的Java对象,一般池对象都在这个区域活跃。

永久区:永久存储区是一个常驻内存区域,用于存放JDK自身携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域占用的内存。

如果出现java.lang.OutOfMemoryError:PermGenspace,说明Java虚拟机对永久代Perm内存设置不够。原因有二:a.程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。b.大量动态反射生成的类不断被加载,最终导致Perm区被占满。

方法区和堆内存的异议:实际而言,方法区和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息、普通常量、静态常量、编译器编译后的代码等。JVM规范将方法区描述为堆的一个逻辑部分。

堆跟栈的区别:堆存的是对象和数组所以堆的大小不是固定的是运行时确定,栈存的是局部变量和操作数所以栈的大小是固定的在编译期确定。

5.    Java GC垃圾回收机制

GC工作目的:在堆中,找到已经无用的对象,并把这些对象占用的空间回收使其可以重新利用。在SUN的文档中,对JVM堆的新生区是采用coping算法,该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收,它开始时把堆分成一个对象面和多个空闲面,程序从对象面分配空间,当对象面满了,基于coping算法的垃圾回收集就从根集中扫描活动对象,并将每个活动对象复制到空闲面,这样空闲面就变成了对象面,原来的对象面就变成了空闲面,程序会在新的对象面中分配内存。

对于新生成的对象都放在伊甸区,当伊甸区充满时,GC开始工作,首先停止应用程序的运行,开始收集垃圾,把所有可找到的对象复制到幸存0区,一旦幸存0区充满,GC就把幸存0区中可以找到的对象都复制到幸存1区(会覆盖原有的存储对象),当幸存1区充满时,GC就把幸存1区中可找到的对象复制到幸存0区中,幸存0区和幸存1区在此时互换角色。Copying算法的最理想状态是所有移出伊甸园的对象都会被收集。

6.    Java中基本变量、对象、对象引用等在堆与栈中存储、按值传递机制、栈中对象共享机制

Java中只有一种参数传递方式那就是按值传递,即Java中传递任何东西都是传值。如果传入方法的是基本类型你就得到基本类型的一份拷贝。如果是传递引用就得到引用的拷贝。Java的堆是一个运行时数据区,类的对象从中分配空间。堆是由垃圾回收负责的,堆的优势是可以动态分配内存大小。栈的优势是存取速度比堆要快,仅次于寄存器,栈数据可以共享,但缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。例子:int a = 3; int b = 3;编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到就将3这个值存放进来然后将a指向3。接着处理int b = 3;在创建完b的引用变量后因为在栈中已经有3这个值,便将b指向3。这样a和b同时指向3。

7.    GC常用算法

引用计数算法:此对象有一个引用则+1,删除一个引用则-1,只用收集计数为0的对象。缺点是a.无法处理循环引用的问题。B.引用计数的方法需要编译器的配合,编译器需要为此对象生成额外的代码。

根搜索算法:建立若干中根对象,当任何一个根对象到某一个对象均不可达时则认为这个对象是可以被回收的。根对象一般为虚拟机栈中的引用的对象,方法区中的类静态属性引用的对象,方法区中的常量引用的对象,本地方法栈中的引用对象。

在根搜索算法的基础上,现代虚拟机的实现当中垃圾搜集算法主要有三种,分别是标记-清除算法、复制算法、标记-整理算法。

标记-清除算法:当堆中的有效内存空间被耗尽的时候,就会停止整个程序,然后进行两项工作,第一项是标记,第二项是清除。标记的过程就是遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。缺点是效率低(递归与全堆对象遍历),清理出来的空间不连续,在进行GC的时候要停止应用程序。

复制算法:将内存分为两个区间,在任意时间点所有动态分配的对象都只能分配在其中一个区间。当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程,将活动区间的存活对象全部复制到空闲空间,且严格按照内存地址一次排列,与此同时,GC线程将更新存货对象的内存引用地址指向新的内存地址。此时空闲空间与活动区间交换。缺点是浪费了一般内存,如果对象的存活率很高,将很浪费。

标记-整理算法:当堆中的有效内存空间被耗尽的时候,就会停止整个程序,然后进行两项工作,第一项是标记,第二项是整理。标记的过程就是遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。整理的过程就是移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。

分代搜集算法:

8.     强软弱虚—强引用、软引用、弱引用、虚引用

强引用:例如Object object = new Object();那么object就是一个强引用,垃圾回收器绝不会回收它,当内存不足时java虚拟机宁愿抛出OutOfMemoryEoor错误,也不会回收强引用对象来解决内存不足的问题。

软引用:如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

弱引用:弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

9.    类加载器与双亲委派模型

类加载器是Java语言的一项创新,也是Java流行的一个重要原因。在类加载的第一个阶段“加载”过程中,需要通过一个类的全限定名来获取此类的二进制字节流,完成这个动作的代码块就是类加载器。对于任意的一个类都需要由加载它的类加载器和这个类本身共同确立其在Java虚拟机中的唯一性。

从Java虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机中),是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都有Java语言实现,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader。

从开发者的角度,类加载器可以细分为:

启动(Bootstrap)类加载器:负责将 Java_Home/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将Java_Home/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。

应用程序(Application)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般称为系统(System)加载器。

除此之外,还有自定义的类加载器,它们之间的层次关系被称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)。

双亲委派模型过程:

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务就成功返回;只有父类加载器无法完成此加载任务时,才会自己区加载。使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如加载java.lang.object这个类,它存在rt.jar无论哪个加载器都要加载这个类,最终都是委派给处于模型最顶端的BootstrapClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。

双亲委派模型的系统实现:

     在Java.lang.ClassLoader的loadClass()方法中,先检查是否被加载过,若没有加载则调用父类加载器的loadClass()方法,若父类加载器为空则默认启动类加载作为父类加载器。如果父加载失败,则抛出ClassNotFoundException异常后再调用自己的findClass()方法进行加载。

10.  类的实例化顺序(https://blog.csdn.net/wzw9353/article/details/74892149)

先静态,先父后子;先静态:父静态>子静态;优先级:父类>子类静态代码块>非静态代码块>构造函数。

a.    父类中的static代码块,当前类的static代码块(注意代码块并不指静态方法);b.顺序执行父类的普通代码块;c.父类的构造函数;d.子类普通代码块;e.子类的构造函数,按顺序执行;f.子类方法的执行。静态代码块只执行一次无论你创建几次对象。

11.  JVM中一次完整的GC流程是怎样的?

对象优先在新生代区中分配,若没有足够空间,Minor GC;大对象(需要大量连续内存空间)直接进入老年态;长期存活的对象进入老年态。如果对象在新生代出生并经过第一次MGC后仍然存货年龄+1,若年龄超过一定限制(15)则被晋升到老年态。

12.  你知道几种垃圾收集器,各自优缺点。

13.  Classloader,双亲委派模型:

ClassLoader:类加载器用来加载Java类到Java虚拟机中。Java源程序在经过Java编译器编译之后就被转换成Java字节码(.class文件)。类加载器负责读取Java字节码代码,并转换成java.lang.Class类的一个实例。

双亲委派模型:某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,一次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时才自己去加载。

14.  对Java内存模型的理解,以及其在并发中的应用。

Java线程之间的通信由Java内存模型控制。

所有变量的存储在主内存,每个线程还都有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作必须在工作内存完成,而不能直接读取内存中的变量。不同的线程直接无法访问对方工作内存中的变量,线程间变量的传递需要通过主内存来完成。

15.  堆内存溢出:

a.    java.lang.OutOfMemoryError:Javaheap space:Java堆内存不够,一个原因是真不够,一个原因是程序中有死循环。

b.    java.lang.OutOfMemoryError:GCoverhead limit exceeded:当GC为释放很小空间占用大量时间时抛出;一般是因为堆太小,没有足够的内存。

c.    java.lang.OutOfMemoryError:PermGenspace:这种是P区内存不够,可通过调整JVM的配置

JVM的Perm区主要用于存放Class和Meta信息,Class在被Loader时就会被放到PermGen space,这个区域称为年老代,GC在主程序运行期间不会对年老区进行清理,默认是64M大小,当程序需要加载的对象比较多时,超过64M就会报这部分内存溢出了,需要加大内存分配,一般128M足够。

d.    java.lang.StackOverflowError:线程栈的溢出,要么是方法调用层次过多,要么是线程栈太小。通过-Xss参数增加线程栈的大小。

16.  Java异常

a.    Throwable:

Throwable是Java语言中所有错误或异常的超类。

Throwable包含两个子类:Error和Exception。它们通常用于指示发生了异常情况。

Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。

b.    Exception:(被检查的异常)

Exception及其子类是Throwable的一种形式,它指出了合理的应用程序想要捕获的条件。Java编译器会检查它,此类异常要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。

c.    RuntimeException(运行时异常)

那些可能在Java虚拟机正常运行期间抛出的异常的超类。如果代码会发生RuntimeException异常,则需要通过修改代码进行避免。例如除数为零。Java编译器不会检查它,也就是出现这类异常时,倘若没有通过throws声明抛出它也没有try-catch捕获它还是会编译通过。虽然java编译器不会检查运行时异常但是我们也可以通过throws进行声明抛出。

d.    Error

用于指示合理的程序不应该视图捕获的严重问题。编译器不会对错误进行检查。程序本身无法修复这些错误。

17.  说一下强引用、软引用、弱引用、虚引用以及它们之间和gc的关系。

强引用:new出的对象之类的引用,只要引用还在永远不会回收。

软引用:引用但非必须的对象,内存溢出异常之前回收。

弱引用:非必须的对象,对象能生存到下一次垃圾收集发生之前。

虚引用:对生存时间无影响,在垃圾回收时得到通知。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值