废话不多说,我肝了大家篇尾见。
1、Java的内存结构?
方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序计数器是运行时线程私有的内存区域。
1)Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
2)方法区(Method Area),方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类 型的卸载 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。该区域是被线程共享的。方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。
3)程序计数器(Program Counter Register),程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
4)JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。虚拟机栈是线程私有的,它的生命周期与线程相同。局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指 向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定
5)本地方法栈(Native Method Stacks),本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
Java 堆的结构是什么样子的?
JVM 的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在 JVM 启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些 对象回收掉之前,他们会一直占据堆内存空间。
新生代
新生代主要用来存放新生的对象。一般占据堆空间的1/3。在新生代中,保存着大量的刚刚创建的对象,但是在新生代中会频繁地进行MinorGC,进行垃圾回收。新生代又细分为三个区:Eden区、SurvivorFrom、ServivorTo区,三个区的默认比例为:8:1:1。
Eden区:Java新创建的对象绝大部分会分配在Eden区(如果对象太大,则直接分配到老年代)。当Eden区内存不够的时候,就会触发MinorGC(新生代采用的是复制算法),对新生代进行一次垃圾回收。
SurvivorFrom区和To区:在GC开始的时候,对象只会存在于Eden区和名为From的Survivor区,To区是空的,一次MinorGc过后,Eden区和SurvivorFrom区存活的对象会移动到SurvivorTo区中,然后会清空Eden区和SurvivorFrom区,并对存活的对象的年龄+1,如果对象的年龄达到15,则直接分配到老年代。MinorGC完成后,SurvivorFrom区和SurvivorTo区的功能进行互换。下一次MinorGC时,会把SurvivorTo区和Eden区存活的对象放入SurvivorFrom区中,并计算对象存活的年龄。
老年代
老年代主要存放应用中生命周期长的内存对象。老年代比较稳定,不会频繁地进行MajorGC。而在MaiorGC之前才会先进行一次MinorGc,使得新生的对象进入老年代而导致空间不够才会触发。当无法找到足够大的连续空间分配给新创建的较大对象也会提前触发一次MajorGC进行垃圾回收腾出空间。
在老年代中,MajorGC采用了标记—清除算法:首先扫描一次所有老年代里的对象,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长。因为要扫描再回收。MajorGC会产生内存碎片,当老年代也没有内存分配给新来的对象的时候,就会抛出OOM(Out of Memory)异常。
永久代
永久代指的是永久保存区域。主要存放Class和Meta(元数据)的信息。Classic在被加载的时候被放入永久区域,它和存放的实例的区域不同,在Java8中,“元数据区”(元空间)的区域。元空间和永久代类似,都是对JVM中规范中方法的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存的限制。类的元数据放入native memory,字符串池和类的静态变量放入java堆中。这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。
采用元空间而不用永久代的原因:
为了解决永久代的OOM问题,元数据和class对象存放在永久代中,容易出现性能问题和内存溢出。
类及方法的信息等比较难确定其大小,因此对于永久代大小指定比较困难,大小容易出现永久代溢出,太大容易导致老年代溢出(堆内存不变,此消彼长)。
永久代会为GC带来不必要的复杂度,并且回收效率偏低。
怎么获取 Java 程序使用的内存?堆使用的百分比?
可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。
Runtime.freeMemory() 方法返回剩余空间的字节数
Runtime.totalMemory()方法返回总内存的字节数
Runtime.maxMemory() 方法返回最大内存的字节数
2、类加载器/类加载机制
image.png
1)启动类加载器:Bootstrap ClassLoader 负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库
2)扩展类加载器:Extension ClassLoader 该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\ jre\ lib\ ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
3)应用程序类加载器:Application ClassLoader 该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器
3、Java类加载过程
Java 类加载需要经历以下 7 个过程:
1)加载加载是类加载的第一个过程,在这个阶段,将完成以下三件事情: 通过一个类的全限定名获取该类的二进制流。 将该二进制流中的静态存储结构转化为方法去运行时数据结构。 在内存中生成该类的 Class 对象,作为该类的数据访问入口
2)验证验证的目的是为了确保 Class 文件的字节流中的信息不会危害到 虚拟机.在该阶段主要完成以下四种验证: 文件格式验证:验证字节流是否符合 Class 文件的规范,如 主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.
- 元数据验证: 对字节码描述的信息进行语义分析,如这个类是 否有父类,是否集成了不被继承的类等。
- 字节码验证: 是整个验证过程中最复杂的一个阶段,通过验 证数据流和控制流的分析,确定程序语义是否正确,主要针 对方法体的验证。如:方法中的类型转换是否正确,跳转指 令是否正确等。符号引用验证:这个动作在后面的解析过程中发生,主要是 为了确保解析动作能正确执行。
3)准备准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的 内存,实例变量将会在对象实例化时随着对象一起分配在 Java 堆 中。
4)解析 该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。
初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段 用户应用程序可以通过自定义类加载器参与。
5)初始化 初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java 程序代码。
6)使用 使用类对象
7)卸载 垃圾回收回收垃圾对象
4、JVM中对象的创建过程
image.png
1)拿到内存创建指令 当虚拟机遇到内存创建的指令的时候(new 类名),来到了方法区,找 根据new的参数在常量池中定位 一个类的符号引用。
2、检查符号引 检查该符号引用有没有被加载、解析和初始化过,如果没有则执行类加载过程,否则直接准备为新的对 象分配内存
3)分配内存 虚拟机为对象分配内存(堆)分配内存分为指针碰撞和空闲列表两种方式;分配内存还要保证并发安全,有两种方式:
1)指针碰撞 所有的存储空间分为两部分,一部分是空闲,一部分是占用,需要分配空间的时候,只需要计算指针移动的长度即可。
2)空闲列表 虚拟机维护了一个空闲列表,需要分配空间的时候去查该空闲列表进行分配并对空闲列表做更新。可以看出,内存分配方式是由java堆是否规整决定的,java堆的规整是由垃圾回收机制来决定的
4)初始化 分配完内存后要对对象的头(Object Header)进行初始化,这新信息包括:该对象对应类的元数据、该对象的GC代、对象的哈希码。抽象数据类型默认初始化为null,基本数据类型为0,布尔值为false…
5)调用对象的初始化方法 也就是执行构造方法。
5、类的生命周期
类的生命周期包括这几个部分,加载、连接、初始化、使用和卸载,其中前三部是类的加载的过程,如下图;
1)加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
2)连接,连接又包含三块内容:验证、准备、初始化。
a) 验证,文件格式、元数据、字节码、符号引用验证;
b)准备,为类的静态变量分配内存,并将其初始化为默认值;3)解析,把类中的符号引用转换为直接引用
3) 初始化,为类的静态变量赋予正确的初始值
4)使用,new出对象程序中使用 5、卸载,执行垃圾回收
6、如何判断对象是不是垃圾对象
在堆里面存放着Java世界中几乎所有的对象实例, 垃圾收集器在对堆进行回收前, 第一件事就是判断哪些对象已死(可回收). 2种方式判断
1) 引用计数法
在JDK1.2之前,使用的是引用计数器算法。在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就+1,当引用失效的时候,计数器的值就-1,当引用计数器被减为零的时候,标志着这个对象已经没有引用了,可以回收了问题:如果在A类中调用B类的方法,B类中调用A类的方法,这样当其他所有的引用都消失了之后,A和B还有一个相互的引用,也就是说两个对象的引用计数器各为1,而实际上这两个对象都已经没有额外的引用,已经是垃圾了。但是该算法并不会计算出该类型的垃圾。
2) 可达性分析法
在主流商用语言(如Java、C#)的主流实现中, 都是通过可达性分析算法来判定对象是否存活的: 通过一系列的称为 GC Roots 的对象作为起点, 然后向下搜索; 搜索所走过的路径称为引用链/Reference Chain, 当一个对象到 GC Roots 没有任何引用链相连时, 即该对象不可达, 也就说明此对象是不可用的, 如下图:虽然E和F相互关联, 但它们到GC Roots是不可达的, 因此也会被判定为可回收的对象。
即使在可达性分析算法中不可达的对象, VM也并不是马上对其回收, 因为要真正宣告一个对象死亡, 至少要经历两次标记过程: 第一次是在可达性分析后发现没有与GC Roots相连接的引用链, 第二次是GC对在F-Queue执行队列中的对象进行的小规模标记(对象需要覆盖finalize()方法且没被调用过)
7、Java的四种引用,强弱软虚
1) 强引用 强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收,使用方式:
String str = new String("str");
2) 软引用 软引用在程序内存不足时,会被回收,使用方式:
// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的, // 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T SoftReference wrf = new SoftReference(new String("str"));
可用场景:创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象。
3) 弱引用 弱引用就是只要JVM垃圾回收器发现了它,就会将之回收,使用方式:
WeakReference wrf = new WeakReference(str);
可用场景:Java源码中的 java.util.WeakHashMap 中的 key 就是使用弱引用,我的理解就是,一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其它操作。
4) 虚引用 虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入 ReferenceQueue 中。注意哦,其它引用是被JVM回收后才被传入 ReferenceQueue 中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有 ReferenceQueue ,使用例子:
PhantomReference prf = new PhantomReference(new String("str"), new ReferenceQueue<>());
可用场景:对象销毁前的一些操作,比如说资源释放等。Object.finalize() 虽然也可以做这类动作,但是这个方式即不安全又低效
8、Java中会存在内存泄漏,栈溢出
1)什么是内存泄漏?
所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。java中有垃圾回收机制,它可以保证当对象不再被引用的时候,对象将自动被垃圾回收器从内存中清除掉。由于Java使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达,那么GC也是可以回收它们的。java中的内存泄露的情况长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
2)栈溢出?
栈的大小可以通过-Xss参数进行设置,当递归层次太深的时候,就会发生栈溢出。比如循环调用,递归等。
9、JVM垃圾处理方法
1) 标记-清除算法(老年代)
该算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象(可达性分析), 在标记完成后统一清理掉所有被标记的对象.该算法会有两个问题:
- 效率问题: 标记和清除效率不高。
- 空间问题: 标记清除后会产生大量不连续的内存碎片, 空间碎片太多可能会导致在运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集。所以它一般用于"垃圾不太多的区域,比如老年代"。
2) 复制算法(新生代)
该算法的核心是将可用内存按容量划分为大小相等的两块, 每次只用其中一块, 当这一块的内存用完, 就将还存活的对象(非垃圾)复制到另外一块上面, 然后把已使用过的内存空间一次清理掉。
优点:不用考虑碎片问题,方法简单高效。缺点:内存浪费严重。
现代商用VM的新生代均采用复制算法,但由于新生代中的98%的对象都是生存周期极短的,因此并不完全按照1∶1的比例划分新生代空间,而是将新生代划分为一块较大的Eden区和两块较小的Survivor区(HotSpot默认Eden和Survivor的大小比例为8∶1), 每次只用Eden和其中一块Survivor。当发生MinorGC时,将Eden和Survivor中还存活着的对象一次性地拷贝到另外一块Survivor上, 最后清理掉Eden和刚才用过的Survivor的空间。当Survivor空间不够用(不足以保存尚存活的对象)时,需要依赖老年代进行空间分配担保机制,这部分内存直接进入老年代。复制算法的空间分配担保: 在执行Minor GC前, VM会首先检查老年代是否有足够的空间存放新生代尚存活对象, 由于新生代使用复制收集算法, 为了提升内存利用率, 只使用了其中一个Survivor作为轮换备份, 因此当出现大量对象在Minor GC后仍然存活的情况时, 就需要老年代进行分配担保, 让Survivor无法容纳的对象直接进入老年代,但前提是老年代需要有足够的空间容纳这些存活对象.但存活对象的大小在实际完成GC前是无法明确知道的, 因此Minor GC前, VM会先首先检查老年代连续空间是否大于新生代对象总大小或历次晋升的平均大小, 如果条件成立, 则进行Minor GC, 否则进行FullGC(让老年代腾出更多空间). 然而取历次晋升的对象的平均大小也是有一定风险的, 如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然可能导致担保失败(Handle Promotion Failure, 老年代也无法存放这些对象了), 此时就只好在失败后重新发起一次Full GC(让老年代腾出更多空间)
3)标记-整理算法(老年代)
标记清除算法会产生内存碎片问题, 而复制算法需要有额外的内存担保空间, 于是针对老年代的特点, 又有了标记整理算法. 标记整理算法的标记过程与标记清除算法相同, 但后续步骤不再对可回收对象直接清理,而是让所有存活的对象都向一端移动,然后清理掉端边界以外的内存.
常见的垃圾收集器
1)Serial
Serial收集器是Hotspot运行在Client模式下的默认新生代收集器, 它在进行垃圾收集时,会暂停所有的工作进程,用一个线程去完成GC工作特点:简单高效,适合jvm管理内存不大的情况(十兆到百兆)。
2)Parnew
ParNew收集器其实是Serial的多线程版本,回收策略完全一样,但是他们又有着不同。我们说了Parnew是多线程gc收集,所以它配合多核心的cpu效果更好,如果是一个cpu,他俩效果就差不多。(可用-XX:ParallelGCThreads参数控制GC线程数)
3)CMS
CMS(Concurrent Mark Sweep)收集器是一款具有划时代意义的收集器, 一款真正意义上的并发收集器,虽然现在已经有了理论意义上表现更好的G1收集器, 但现在主流互联网企业线上选用的仍是CMS(如Taobao),又称多并发低暂停的收集器。由他的英文组成可以看出,它是基于标记-清除算法实现的。整个过程分4个步骤:
1)初始标记(CMS initial mark):仅只标记一下GC Roots能直接关联到的对象, 速度很快
2)并发标记(CMS concurrent mark: GC Roots Tracing过程)
3)重新标记(CMS remark):修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录
4)并发清除(CMS concurrent sweep: 已死对象将会就地释放)
可以看到,初始标记、重新标记需要STW(stop the world 即:挂起用户线程)操作。因为最耗时的操作是并发标记和并发清除。所以总体上我们认为CMS的GC与用户线程是并发运行的。优点:并发收集、低停顿 缺点:CMS默认启动的回收线程数=(CPU数目+3)*4 当CPU数>4时, GC线程最多占用不超过25%的CPU资源, 但是当CPU数<=4时, GC线程可能就会过多的占用用户CPU资源, 从而导致应用程序变慢, 总吞吐量降低.
无法清除浮动垃圾(GC运行到并发清除阶段时用户线程产生的垃圾),因为用户线程是需要内存的,如果浮动垃圾施放不及时,很可能就造成内存溢出,所以CMS不能像别的垃圾收集器那样等老年代几乎满了才触发,CMS提供了参数 -XX:CMSInitiatingOccupancyFraction 来设置GC触发百分比(1.6后默认92%),当然我们还得设置启用该策略 -XX:+UseCMSInitiatingOccupancyOnly
因为CMS采用标记-清除算法,所以可能会带来很多的碎片,如果碎片太多没有清理,jvm会因为无法分配大对象内存而触发GC,因此CMS提供了 -XX:+UseCMSCompactAtFullCollection 参数,它会在GC执行完后接着进行碎片整理,但是又会有个问题,碎片整理不能并发,所以必须单线程去处理,所以如果每次GC完都整理用户线程stop的时间累积会很长,所以XX:CMSFullGCsBeforeCompaction 参数设置隔几次GC进行一次碎片整理(默认为0)。
4)G1
同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS。G1最大的特点是引入分区的思路,弱化分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷因为每个区都有E、S、O代,所以在G1中,不需要对整个Eden等代进行回收,而是寻找可回收对象比较多的区,然后进行回收(虽然也需要STW操作,但是花费的时间是很少的),保证高效率。
新生代收集 G1的新生代收集跟ParNew类似,如果存活时间超过某个阈值,就会被转移到S/O区。年轻代内存由一组不连续的heap区组成, 这种方法使得可以动态调整各代区域的大小
老年代收集 分为以下几个阶段:
- 初始标记 (Initial Mark: Stop the World Event)在G1中, 该操作附着一次年轻代GC, 以标记Survivor中有可能引用到老年代对象的Regions.
- 扫描根区域 (Root Region Scanning: 与应用程序并发执行)扫描Survivor中能够引用到老年代的references. 但必须在Minor GC触发前执行完
- 并发标记 (Concurrent Marking : 与应用程序并发执行)在整个堆中查找存活对象, 但该阶段可能会被Minor GC中断
- 重新标记 (Remark : Stop the World Event)完成堆内存中存活对象的标记. 使用snapshot-at-the-beginning(SATB, 起始快照)算法, 比CMS所用算法要快得多(空Region直接被移除并回收, 并计算所有区域的活跃度).
- 清理 (Cleanup : Stop the World Event and Concurrent)在含有存活对象和完全空闲的区域上进行统计(STW)、擦除Remembered Sets(使用RememberedSet来避免扫描全堆,每个区都有对应一个Set用来记录引用信息、读写操作记录)(STW)、重置空regions并将他们返还给空闲列表(free list)(Concurrent)
10、简单描述一下(分代)垃圾回收的过程
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:当年轻代中的Eden区分配满的时候,就会触发年轻代的GC(Minor GC)。具体过程如下:
1)在Eden区执行了第一次GC之后,存活的对象会被移动到其中一个Survivor分区(以下简称from)
2)Eden区再次GC,这时会采用复制算法,将Eden和from区一起清理。存活的对象会被复制到to区。接下来,只需要清空from区就可以了
11、用过G1垃圾回收器的哪几个重要参数
最重要的是MaxGCPauseMillis,可以通过它设定G1的目标停顿时间,它会尽量的去达成这个目标。G1HeapRegionSize可以设置小堆区的大小,一般是2的次幂。InitiatingHeapOccupancyPercent,启动并发GC时的堆内存占用百分比。G1用它来触发并发GC周期, 基于整个堆的使用率,而不只是某一代内存的使用比例,默认是45%。
12、JVM性能调优
设定堆内存大小 -Xmx:堆内存最大限制。
设定新生代大小。新生代不宜太小,否则会有大量对象涌入老年代
-XX:NewSize:新生代大小
-XX:NewRatio 新生代和老生代占比
-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比 设定垃圾回收器年轻代用
-XX:+UseParNewGC 年老代用 -XX:+UseConcMarkSweepGC
13、OOM你遇到过哪些情况,SOF你遇到过哪些情况
OOM:
1)OutOfMemoryError异常 除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能。Java Heap 溢出:一般的异常信息:java.lang.OutOfMemoryError:Java heap spacess
2)虚拟机栈和本地方法栈溢出 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常这里需要注意当栈的大小越大可分配的线程数就越少。
3)运行时常量池溢出 异常信息:java.lang.OutOfMemoryError:PermGenspace 如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。
4)方法区溢出 方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。异常信息:java.lang.OutOfMemoryError:PermGenspace方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。
SOF(堆栈溢出StackOverflow):
StackOverflowError 的定义:当应用程序递归太深而发生堆栈溢出时,抛出该错误。因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。栈溢出的原因:递归调用,大量循环或死循环,全局变量是否过多,数组、List、map数据过大。
14、Java的双亲委托机制是什么
它的意思是,除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都会委派给它的父加载器进行加载。这样一层层向上传递,直到祖先们都无法胜任,它才会真正的加载。Java默认是这种行为。当然Java中也有很多打破双亲行为的骚操作,比如SPI(JDBC驱动加载),OSGI等。
有哪些打破了双亲委托机制的案例
1)Tomcat可以加载自己目录下的class文件,并不会传递给父类的加载器。
2)Java的SPI,发起者是BootstrapClassLoader,BootstrapClassLoader已经是最上层的了。它直接获取了AppClassLoader进行驱动加载,和双亲委派是相反的。
15、掌握情况自查
分类 | 问题 | 掌握 |
类加载机制模型 | 双亲委派机制 | |
类加载过程 | ||
内存模型 | 堆 | |
栈(局部变量、方法出口、操作数栈、动态链接) | ||
方法区(类信息、常量、静态变量、) | ||
对象创建过程 | ||
垃圾收集算法 | 复制算法 | |
标记清除、标记整理 | ||
垃圾收集器 | G1 | |
CMS |
转载地址:https://blog.csdn.net/xiaobaijun_ge/article/details/126031226