为了能让您更加方便的阅读
本文全部的面试题目均已整理至小程序《面试手册》
能够经过微信扫描(或长按)下图的二维码享受更好的阅读体验!java
目录android
1. 有时候也成为永久代,在该区内不多发生垃圾回收,可是并不表明不发生 GC,在这里进行的 GC 主要是对方法区里的常量池和对类型的卸载 2. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。 3. 该区域是被线程共享的。 4. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具备动态性,也就是说常量并不必定是编译时肯定,运行时生成的常量也会存在这个常量池中。
虚拟机栈也就是咱们日常所称的栈内存,它为 java 方法服务,每一个方法在执行的时候都会建立一个栈帧,用于存储局部变量表、操做数栈、动态连接和方法出口等信息。程序员
虚拟机栈是线程私有的,它的生命周期与线程相同。面试
局部变量表里存储的是基本数据类型、returnAddress 类型(指向一条字节码指令的地址)和对象引用,这个对象引用有多是指向对象起始地址的一个指针,也有多是表明对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间肯定算法
操做数栈的做用主要用来存储运算结果以及运算的操做数,它不一样于局部变量表经过索引来访问,而是压栈和出栈的方式编程
每一个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程当中的动态链接.动态连接就是将常量池中的符号引用在运行期转化为直接引用。小程序
本地方法栈和虚拟机栈相似,只不过本地方法栈为 Native 方法服务。数组
java 堆是全部线程所共享的一块内存,在虚拟机启动时建立,几乎全部的对象实例都在这里建立,所以该区域常常发生垃圾回收操做。浏览器
内存空间小,字节码解释器工做时经过改变这个计数值能够选取下一条须要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都须要依赖这个计数器完成。该内存区域是惟一一个 java 虚拟机规范没有规定任何 OOM 状况的区域。缓存
Java程序须要经过 JVM 栈上的引用访问堆中的具体对象。对象的访问方式取决于 JVM 虚拟机的实现。目前主流的访问方式有 句柄 和 直接指针 两种方式。
Java堆中划分出一块内存来做为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息,具体构造以下图所示:
优点:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是很是广泛的行为)时只会改变句柄中的实例数据指针,而引用自己不须要修改。
若是使用直接指针访问,引用 中存储的直接就是对象地址,那么Java堆对象内部的布局中就必须考虑如何放置访问类型数据的相关信息。
优点:速度更快,节省了一次指针定位的时间开销。因为对象的访问在Java中很是频繁,所以这类开销聚沙成塔后也是很是可观的执行成本。HotSpot 中采用的就是这种方式。
java 内存模型(JMM)是线程间通讯的控制机制.JMM 定义了主内存和线程之间抽象关系。线程之间的共享变量存储在主内存(main memory)中,每一个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其余的硬件和编译器优化。Java 内存模型的抽象示意图以下:
线程 A 与线程 B 之间如要通讯的话,必需要经历下面 2 个步骤:
垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法;
finalize是Object类的一个方法,该方法在Object类中的声明protected void finalize() throws Throwable { }
在垃圾回收器执行时会调用被回收对象的finalize()方法,能够覆盖此方法来实现对其资源的回收。注意:一旦垃圾回收器准备释放对象占用的内存,将首先调用该对象的finalize()方法,而且下一次垃圾回收动做发生时,才真正回收对象占用的内存空间
GC原本就是内存回收了,应用还须要在finalization作什么呢? 答案是大部分时候,什么都不用作(也就是不须要重载)。只有在某些很特殊的状况下,好比你调用了一些native的方法(通常是C写的),能够要在finaliztion里去调用C的释放函数。
浅拷贝(shallowCopy)只是增长了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增长了一个指针而且申请了一个新的内存,使这个增长的指针指向这个新的内存,
使用深拷贝的状况下,释放内存的时候不会由于出现浅拷贝时释放同一个内存的错误。
浅复制:仅仅是指向被复制的内存地址,若是原地址发生改变,那么浅复制出来的对象也会相应的改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
物理地址
堆的物理地址分配对对象是不连续的。所以性能慢些。在GC的时候也要考虑到不连续的分配,因此有各类算法。好比,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩)
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。因此性能快。
内存分别
存放的内容
程序的可见度
队列和栈都是被用来预存储数据的。
内存泄漏是指再也不被使用的对象或者变量一直被占据在内存中。理论上来讲,Java是有GC垃圾回收机制的,也就是说,再也不被使用的对象,会被GC自动回收掉,自动从内存中清除。
可是,即便这样,Java也仍是存在着内存泄漏的状况,java致使内存泄露的缘由很明确:长生命周期的对象持有短生命周期对象的引用就极可能发生内存泄露,尽管短生命周期对象已经再也不须要,可是由于长生命周期对象持有它的引用而致使不能被回收,这就是java中内存泄露的发生场景。
Java对象由三个部分组成:对象头、实例数据、对齐填充。
对象头由两部分组成
实例数据用来存储对象真正的有效信息(包括父类继承下来的和本身定义的)
对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)
判断一个对象是否存活有两种方法:
所谓引用计数法就是给每个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收.
引用计数法有一个缺陷就是没法解决循环引用问题,也就是说当对象 A 引用对象 B,对象 B 又引用者对象 A,那么此时 A,B 对象的引用计数器都不为零,也就形成没法完成垃圾回收,因此主流的虚拟机都没有采用这种算法。
该算法的思想是:从一个被称为 GC Roots 的对象开始向下搜索,若是一个对象到 GC Roots 没有任何引用链相连时,则说明此对象不可用。在 java 中能够做为 GC Roots 的对象有如下几种:
虽然这些算法能够断定一个对象是否能被回收,可是当知足上述条件时,一个对象不必定会被回收。当一个对象不可达 GC Root 时,这个对象并不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收须要经历两次标记
若是对象在可达性分析中没有与 GC Root 的引用链,那么此时就会被第一次标记而且进行一次筛选,筛选的条件是是否有必要执行 finalize()方法。当对象没有覆盖 finalize()方法或者已被虚拟机调用过,那么就认为是不必的。
若是该对象有必要执行 finalize()方法,那么这个对象将会放在一个称为 F-Queue 的对队列中,虚拟机会触发一个 Finalize()线程去执行,此线程是低优先级的,而且虚拟机不会承诺一直等待它运行完,这是由于若是 finalize()执行缓慢或者发生了死锁,那么就会形成 F-Queue 队列一直等待,形成了内存回收系统的崩溃。GC 对处于 F-Queue 中的对象进行第二次被标记,这时,该对象将被移除”即将回收”集合,等待回收。
在 java 中,程序员是不须要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM 中,有一个垃圾回收线程,它是低优先级的,在正常状况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
垃圾回收是在内存中存在没有引用的对象或超过做用域的对象时进行的。
垃圾回收的目的是识别而且丢弃应用再也不使用的对象来释放和重用资源。
不会,在下一个垃圾回调周期中,这个对象将是被可回收的。
也就是说并不会当即被垃圾收集器马上回收,而是在下一次垃圾回收时才会释放其占用的内存。
GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会致使程序或系统的不稳定甚至崩溃,Java提供的GC功能能够自动监测对象是否超过做用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操做方法。Java程序员不用担忧内存管理,由于垃圾收集器会自动进行管理。要请求垃圾收集,能够调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM能够屏蔽掉显示的垃圾回收调用。 垃圾回收能够有效的防止内存泄露,有效的使用可使用的内存。垃圾回收器一般是做为一个单独的低优先级的线程运行,不可预知的状况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或全部对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,由于服务器端的编程须要有效的防止内存泄露问题,然而时过境迁,现在Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户一般以为iOS的系统比Android系统有更好的用户体验,其中一个深层次的缘由就在于android系统中垃圾回收的不可预知性。
补充:垃圾回收机制有不少种,包括:分代复制垃圾回收、标记垃圾回收、增量垃圾回收等方式。标准的Java进程既有栈又有堆。栈保存了原始型局部变量,堆保存了要建立的对象。Java平台对堆内存回收和再利用的基本算法被称为标记和清除,可是Java对其进行了改进,采用“分代式垃圾收集”。这种方法会跟Java对象的生命周期将堆内存划分为不一样的区域,在垃圾收集过程当中,可能会将对象移动到不一样区域:
-Xms / -Xmx:堆的初始大小 / 堆的最大大小
-Xmn:堆中年轻代的大小
-XX:-DisableExplicitGC:让System.gc()不产生任何做用
-XX:+PrintGCDetails:打印GC的细节
-XX:+PrintGCDateStamps:打印GC操做的时间戳
-XX:NewSize / XX:MaxNewSize: 设置新生代大小/新生代最大大小
-XX:NewRatio :能够设置老生代和新生代的比例
-XX:PrintTenuringDistribution :设置每次新生代GC后输出幸存者乐园中对象年龄的分布
-XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:设置老年代阀值的初始值和最大值
-XX:TargetSurvivorRatio:设置幸存区的目标使用率
对于GC来讲,当程序员建立对象时,GC就开始监控这个对象的地址、大小以及使用状况。
不会;一般,GC采用有向图的方式记录和管理堆(heap)中的全部对象。经过这种方式肯定哪些对象是"可达的",哪些对象是"不可达的"。当GC肯定一些对象为"不可达"时,GC就有责任回收这些内存空间。而这个回收操做时达到必定阈值或者条件以后才会触发回收;并非实时的。
能够。程序员能够手动执行System.gc(),通知GC运行,可是Java语言规范并不保证GC必定会执行。
垃圾回收不会发生在永久代,若是永久代满了或者是超过了临界值,会触发彻底垃圾回收(Full GC)。若是你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为何正确的永久代大小对避免Full GC是很是重要的缘由。
java 8中,永久代被移除,取而代之的为元空间。
标记-清除:
这是垃圾收集算法中最基础的,根据名字就能够知道,它的思想就是标记哪些要被回收的对象,而后统一回收。这种方法很简单,可是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,致使之后程序在分配较大的对象时,因为没有充足的连续内存而提早触发一次 GC 动做。
复制算法:
为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,而后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,而后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。可是这种方式,内存的代价过高,每次基本上都要浪费通常的内存。
因而将该算法进行了改进,内存区域再也不是按照 1:1 去划分,而是将内存划分为 8:1:1 三部分,较大那分内存交 Eden 区,其他是两块较小的内存区叫 Survior 区。每次都会优先使用 Eden 区,若 Eden 区满,就将对象复制到第二块内存区上,而后清除 Eden 区,若是此时存活的对象太多,以致于 Survivor 不够时,会将这些对象经过分配担保机制复制到老年代中。(java 堆又分为新生代和老年代)
标记-整理
该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不一样之处就是在清除对象的时候现将可回收对象移动到一端,而后清除掉端边界之外的对象,这样就不会产生内存碎片了。
分代收集
如今的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,因为对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,因此可使用标记-整理 或者 标记-清除。
标记无用对象,而后进行清除回收。
标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段:
标记-清除算法之因此是基础的,是由于后面讲到的垃圾收集算法都是在此算法的基础上进行改进的。
优势:实现简单,不须要对象进行移动。
缺点:标记、清除过程效率低,产生大量不连续的内存碎片,提升了垃圾回收的频率。
标记-清除算法的执行的过程以下图所示
为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另一个区域中,最后将当前使用的区域的可回收的对象进行回收。
优势:
按顺序分配内存便可,实现简单、运行高效,不用考虑内存碎片。
缺点:
可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。
复制算法的执行过程以下图所示
在新生代中可使用复制算法,可是在老年代就不能选择复制算法了,由于老年代的对象存活率会较高,这样会有较多的复制操做,致使效率变低。标记-清除算法能够应用在老年代中,可是它效率不高,在内存回收后容易产生大量内存碎片。所以就出现了一种标记-整理算法(Mark-Compact)算法,与标记-整理算法不一样的是,在标记可回收的对象后将全部存活的对象压缩到内存的一端,使他们紧凑的排列在一块儿,而后对端边界之外的内存进行回收。回收后,已用和未用的内存都各自一边。
优势:
解决了标记-清理算法存在的内存碎片问题。
缺点:
仍须要进行局部对象移动,必定程度上下降了效率。
标记-整理算法的执行过程以下图所示
当前商业虚拟机都采用分代收集的垃圾收集算法。分代收集算法,顾名思义是根据对象的存活周期将内存划分为几块。通常包括年轻代、老年代 和 永久代
如图所示:
若是说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展现了7种做用于不一样分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不一样收集器之间的连线表示它们能够搭配使用。
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来得到最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器很是适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
CMS 使用的是标记-清除的算法实现的,因此在 gc 的时候回产生大量的内存碎片,当剩余内存不能知足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被下降。
新生代垃圾回收器通常采用的是复制算法,复制算法的优势是效率高,缺点是内存利用率低;老年代回收器通常采用的是标记-整理的算法进行垃圾回收。
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程以下:
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。
老生代当空间占用到达某个值以后就会触发全局垃圾收回,通常使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的总体执行流程。
5种场景会触发类加载:
遇到new,getstatic,putstatic或invokestatic这四条字节码指令时,若是类没有进行过初始化,则须要先触发初始化
使用java.lang.reflect包的方法对类进行反射调用的时候,若是类没有进行过初始化,则须要先触发其初始化,
当初始化一个类的时候,若是发现其父类尚未进行过初始化,则须要先触发其父类的初始化
当虚拟机启动时,用户须要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个类
当时用,JDK1.7的动态语言支持时,若是一个java.lang.invoke.MethodHandle实例后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且这个方法句柄对应的类尚未进行过初始化,则须要先触发其初始化
类从被加载到虚拟机内存开始,到卸载出内存为止,整个生命周期包括:加载,验证,准备,解析,初始化,使用和卸载
加载是类加载过程的一个阶段,在加载阶段虚拟机须要完成三件事
验证就是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全
准备阶段是正式为类静态变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都在方法区中进行分配
public static int value = 123 //在准备阶段 value的值是 0 并非123 public static final int value = 123 // 准备阶段value 的值为123
若是属性有Constant Value 属性,那么在准备阶段变量就会被初始化为所指定的值
这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中
这里所设置的初始值一般状况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
直接引用
直接引用可使直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄
符号引用
符号引用以一组符号来描述所引用的目标,符号能够是任何形式的字面量,只要使用时能无歧义的定位到目标便可
主要包含:
类或接口的解析
字段解析
类方法解析
接口方法解析
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
JVM初始化步骤:
类的初始化
Java中的全部类,都须要由类加载器装载到JVM中才能运行。类加载器自己也是一个类,而它的工做就是把class文件从硬盘读取到内存中。在写程序的时候,咱们几乎不须要关心类的加载,由于这些都是隐式装载的,除非咱们有特殊的用法,像是反射,就须要显式的加载所须要的类。
类装载方式,有两种 :
隐式装载
程序在运行过程当中当碰到经过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
显式装载,
经过class.forname()等方法,显式加载须要的类
Java类的加载是动态的,它并不会一次性将全部类所有加载后再运行,而是保证程序运行的基础类(像是基类)彻底加载到jvm中,至于其余类,则在须要的时候才加载。这固然就是为了节省内存开销。
实现经过类的权限定名获取该类的二进制字节流的代码块叫作类加载器。
主要有一下四种类加载器:
在介绍双亲委派模型以前先说下类加载器。对于任意一个类,都须要由加载它的类加载器和这个类自己一同确立在 JVM 中的惟一性,每个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,而后再转化为 class 对象。
类加载器分类:
双亲委派模型:若是一个类加载器收到了类加载的请求,它首先不会本身去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样全部的加载请求都会被传送到顶层的启动类加载器中,只有当父加载没法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
当一个类收到了类加载请求时,不会本身先去加载这个类,而是将其委派给父类,由父类去加载,若是此时父类不能加载,反馈给子类,由子类去完成类的加载。
经常使用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。
摘录GC日志一部分(前部分为年轻代gc回收;后部分为full gc回收):
20xx-0x-0xT10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs] 20xx-0x-0xT10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]
经过上面日志分析得出,PSYoungGen、ParOldGen、PSPermGen属于Parallel收集器。其中PSYoungGen表示gc回收先后年轻代的内存变化;ParOldGen表示gc回收先后老年代的内存变化;PSPermGen表示gc回收先后永久区的内存变化。young gc 主要是针对年轻代进行内存回收比较频繁,耗时短;full gc 会对整个堆内存进行回城,耗时长,所以通常尽可能减小full gc的次数
Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo
设定堆内存大小
-Xmx:堆内存最大限制。
设定新生代大小。 新生代不宜过小,不然会有大量对象涌入老年代
-XX:NewSize:新生代大小
-XX:NewRatio 新生代和老生代占比
-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比
设定垃圾回收器
年轻代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC
感谢您的点赞、评论、关注;
您还能够扫码关注“公众号”获取粉丝福利。