1.jvm内存模型
jvm内存模型主要有运行时期模型和非运行时期两部分组成,通常说的jvm内存模型是指运行时期内存模型。
图中运行时期内存模型主要分两大块,蓝色部分属于堆级别是所有线程共享的,绿色部分属于线程栈级别是线程私有的。
元空间:元空间主要是存放类加载的类的元数据信息
堆:堆主要存放对象实例以及常量,这块内存占比是最大的,通常说的jvm调优是指对堆进行调优
虚拟机栈:虚拟机栈是线程级别的,线程结束虚拟机栈也消失,虚拟机栈主要是存放局部变量,操作数栈,动态链接,方法出口等。
本地方法栈:本地方法栈主要是服务native修饰的方法
2.GC有哪几种?
堆中的对象是有分代的,包括年轻代,老年代
a.对象在年轻代被回收是minor gc,当新生代的内存不足时就会触发minor gc。edge区s0区s1区中的对象会在此时清理,清理的过程是edge区依然存放的对象进入s0区,s0区依然存活的对象进入s1区,s1区依然存活的对象清理次数未到15在回到s0如此反复,知道s区的对象清理次数大于15次依然存活则进入老年代。
b.对象在老年代被回收是major gc,老年代内存不足就会触发major gc。
c.full gc同时清理新生代和老年代两个区域,常见的触发方式一种是手动触发System.gc(),还有一种就是年代晋升失败,比如eden区的存活对象晋升到S区放不下,又尝试直 接晋升到Old区又放不下。还有一种跟CMS垃圾收集器有关,CMS的垃圾清理和引用线程是并行进行的,如果在并行清理的过程中老年代的空间不足以容纳从年轻代晋升过来的对象(本质上其实也是年代晋升失败)
3.jvm中有几种类加载器?
a.引导类加载器或者叫启动加载器,主要是加载jre中lib目录下的核心类包,比如rt.jar我们用的Object类String类等都在这里面。
b.扩展类加载器,主要加载jre中lib目录下的扩展类包,比如jre ext目录下的类包
c.应用系统类加载器,主要是加载我们程序员自己开发的工程里面的类包和代码,说白了就是classPath路径下的类包
d.用户自定义类加载器,通自己实现的自定义类加载器来加载指定目录的class文件,或者实现打破双亲委派机制等逻辑。
为什么有这么多类加载器呢,一个是各自类加载器负责各自的职责,还有一个也是为了实现委派模式达到安全的目的。
4.什么是双亲委派机制?
类加载器在加载的过程中,先向上委托父加载器去加载,父加载器如果还有它的父加载器就继续向上委托,直到引导类加载器没有它的父加载器,父加载器先开始加载对应的类包,加载完了在到子类,子类如果发现父类已加载就不加载,父类未加载子类就尝试加载。这就是双亲委派机制。之所以这样设计,主要是安全方面考虑,如果自己的程序写了java.lang.String的类如果子加载器加载成功了,那引导类加载就加载不到jdk里面我们真正需要的String或者说jdk里面的String类被篡改了。还有一个是避免重复加载,如果父类已经加载过了子加载器就不重复加载。
5.如何理解jvm中的线程?
jvm中的线程主要是java层面的线程对象,jvm运行程序并发执行多个线程,jvm中会维护java线程对象和原生操作系统中的映射关系。当线程的本地工作区准备完毕包括栈,程序计算器,缓冲区等就会在原生操作系统创建一个系统级别的原生线程。操作系统负责调度所有线程,并将它们分配到可用的cpu上执行,原生线程初始化完毕就会开始调用java线程对象的run方法。当线程结束时就会释放原生线程和java线程的所有资源。
6.如何确定jvm中的对象为垃圾对象?
a.引用计数法
java中对象和引用是相关联的,一个对象要想被使用它的地址值肯定是有被引用的,如果没有引用那这个对象一般是没有被使用的,简单的说就是一个对象如果没有被使用就说明没有任何引用,那它的引用计数就是0,说明是可以回收的对象。
b.可达性分析
引用计数法有个缺陷就是如果两个对象互相引用,但其实这两个对象都没有被其他使用此时引用计数法就没办法将它们定位可回收对象,所以就有了可达性分析法,可达性分析就是从根路径对象也就是GC ROOT开始,分析到另外一个对象有没有对应的引用路径,如果没有就对该对象进行标记,一般标记两次之后就说明这是一个可回收的对象。
7.你知道几种垃圾回收算法?
a.标记清除算法
该算法主要分两个阶段,标记阶段标记出可回收的对象,清除阶段回收标记为垃圾的对象。标记清除算法的缺点就是标记的对象在内存中是不规则分布的,虽然也回收了,但是内存中空闲的部分不是连续的,导致以后如果有大的对象进来就没地方存放。
如图:灰色是存活对象,黑色是标记的要回收的对象,绿色是空闲部分,黑色部分是有可能分散的不连贯的。
b.复制算法
标记清楚算法容易造成内存碎片,复制算法可以避免这个问题,因为它是将内存平均分成了两块,每次都是使用其中一块,当正在使用的这块满了之后,存活的对象复制到另外一块并且顺序存储,在将当前使用的这块内存回收,将另一块内存开启使用。
这种算法,虽然避免了内存碎片化,但是内存的使用率却降低了一半,且如果存活的对象多的话复制起来也是效率很低的。
c.标记整理算法
标记整理算法,能有效的避免内存碎片化和内存使用率低的问题。它也是先标记哪些对象是存活的,哪些对象是可回收的,将存活的对象统一有序的在内存的一端开始存放,顺序存放,可回收的对象进行回收,这样存活的对象在内存的一端,空闲的内存也能连续的在另一端。
d.分代收集算法
目前jvm中用的主要垃圾收集算法。主要做法是将对象的不同生命周期分到不同的区域,也就是新生代或老年代。新生代每次会有大量的垃圾对象被回收,老生代每次回收的对象量较小,所以针对不同的代算法有不同的策略。
新生代:复制算法
因为新生代每次都要回收大量的垃圾对象,所以存活的对象比较少,进行复制效率还是比较高的,只不过它不是将内存平均分成两块,而是分成Eden区和s0区s1区,而且默认的分配比例是8:1:1。当minor gc的时候会将Eden区和s区的存活对象复制到其中一个s区。
老年代:标记整理算法
因为老年代每次回收少量的垃圾对象,所以将存活的对象用标记的方式有序的存放内存一端,剩下的一端就是空闲的内存。
e.分区收集算法
分区算法是将整个堆空间划分成若干个小区间,这样做的好处是每个区间内的空间独立管理和使用,这样回收的时候就只回收一个或几个小区间,而不是整个堆空间,减少GC带来的停顿时间。
8.java中的引用类型有哪些?
java中的引用类型主要有强引用,弱引用,软引用,虚引用。
a.强引用,就是常见的对象赋值给一个变量,被强引用的对象它是处于可达状态的,是不会被回收的,一般发生内存泄漏都是强引用引起的。
b.弱引用,要用SoftReference来实现,弱引用的情况,当jvm中内存空间足够时是不会被回收的,一旦内存空间不足就会被回收,通常是用在对内存敏感的程序中。
c.软引用,要用WeakReference来实现,它比弱引用的生存期更短,一旦jvm中发生GC软引用就会被回收,不管内存空间是否足够。
d.虚引用,要用PhantomReference来实现,它不能单独使用,要和引用队列联合使用,它主要是用来跟踪对象被垃圾回收的状态。
9.说说jvm类加载有哪些步骤?
jvm类加载主要步骤有加载、验证、准备、解析和初始化。
a.加载,从classPath或其他路径加载编译好的class文件到jvm的元空间区
b.验证,验证加载到的字节码文件是否符合jvm的规范
c.准备,准备阶段主要对类中的变量分配地址值和默认值,比如int类型默认是0,boolen类型默认是false
d.解析,解析阶段主要是将常量池中的符号引用改为直接引用,因为jvm中加载出来的一些字面量是会放入常量池的,将这种符号引用改为用常量池中的地址进行引用叫直接引用
e.初始化,初始化阶段就是真正开始字节码中的指令,比如int i = 1;准备阶段默认是0,到了初始化阶段就是将1赋值给i。