-
2021-03-06 23:03:19
1 年轻代只设置一个Eden区行不行?
如果只设置一个Eden区,那么没进行一次MinorGC,存活的对象就会被送入老年代,老年代很快被填满,就会触发MajorGC,因为MajorGC之前会先进行一次MinorGC,所以也可以看做是发生了Full GC,Full GC消耗的时间要远远大于Minor GC,这样会增加系统停顿时间。那么,你们也许会想,可以增加老年代的空间,减少Full GC的频率,可以是频率虽然降低了,但是由于老年代存储的对象太多,一旦发生Full GC,单次GC的时间增加了,系统停顿时间依然很长。反之,单次GC时间减少,但是GC频率增加了,结果还是一样的。
所以,我们减少Full GC的方案只有一个:减少往老年代发送的对象,进而更慢地触发Full GC。所以,我们才需要Survivor区来进行筛选,只要经历了16次Minor GC还能在年轻代存活的对象才有资格送往老年代。2 设置一个Eden区和一个Survivor区行不行?
我们已经知道了必须需要设置Survivor区,如果只要一个Survivor区,MinorGC流程是:
- 把new对象放在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会移动到Survivor区,同时Eden区被清空。
- 然后继续new对象放在Eden区中,一旦Eden满了,又触发一个Minor GC,此时,Eden和Survivor中各有一些存活的对象,尤其是Survivor区发生了GC后,剩余存活的对象肯定不是紧密排列的,如果此时把Eden区中存活对象强行移动到Survivor区中,由于这Survivor区域的对象不是连续的,所以会产生内存碎片。
所以,我们建立两个Survivor区看看如何。
3 建立两个Survivor区怎么样?
建立两个Survivor区,分别是S0和S1,MinorGC流程如下:
- 把new的对象放在Eden区中,一旦Eden满了,触发一次Minor GC,Eden区中存活的对象会送到S0中,Eden被清空;
- 再次new对象放入Eden区中,Eden又满了,触发Minor GC,Eden和S0中存活的对象放入到S1中,然后S0和Eden清空,然后S0和S1交换角色,S1变成了S0,作为下一次GC的主要目标,S0变成了S1,存放下一次GC存活的对象。
- 如此循环往复,对象头的分代年龄达到16次,则会被送到老年代中。
有两个Survivor的好处就是:永远有一个S区是空的,另外一个S区无碎片。如果Survivor细分为更多块,每一块的空间会比较小,很容易导致Survivor满,对象的分代年龄增加的越快,导致送往老年代的对象越多,所以两个S区是经过权衡后的最佳方案。
更多相关内容 -
JVM理论知识面试大全
2021-01-02 20:37:03文章目录1.1 JVM组成1.2 Java程序运行过程1.3 JVM的内存区域1.4 JVM的运行时内存新生代:Eden区、ServivorTo区和ServivorFrom区老年代永久代1.5 Java中常用的垃圾回收算法标记清除复制算法标记整理分代收集算法1.6 ...文章目录
1.1 JVM组成
-
字节码指令集
-
程序寄存器
-
虚拟机栈
-
虚拟机堆
-
方法区
-
垃圾回收器
1.2 Java程序运行过程
(1)Java源文件被编译器编译成字节码文件
(2)JVM将字节码文件编译成相应操作系统的机器码。
(3)机器码调用相应操作系统的本地方法库执行相应的方法。
1.3 JVM的内存区域
-
线程私有区域
- 程序计数器:该方法的程序计数器记录的是实时虚拟机字节码指令的地址
- 虚拟机栈
- 本地方法区
-
线程共享区域
- 方法区:方法区也被称为永久代,用于存储常量、静态变量、类信息、即时编译器编译后的机器码、运行时常量池等数据
- 堆
-
直接内存
1.4 JVM的运行时内存
JVM的运行时内存也叫作JVM堆,从GC的角度可以将JVM堆分为新生代、老年代和永久代。其中新生代默认占 1/3堆空间,老年代默认占 2/3堆空间,永久代占非常少的堆空间。新生代又分为Eden区、ServivorFrom区和ServivorTo区,Eden区默认占8/10新生代空间,ServivorFrom区和ServivorTo区默认分别占 1/10新生代空间
新生代:Eden区、ServivorTo区和ServivorFrom区
JVM新创建的对象(除了大对象外)会被存放在新生代,默认占 1/3堆内存空间。由于JVM会频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。新生代又分为Eden区、ServivorTo区和ServivorFrom区,如下所述。
(1)Eden区:Java新创建的对象首先会被存放在Eden区,如果新创建的对象属于大对象,则直接将其分配到老年代。大对象的定义和具体的JVM版本、堆大小和垃圾回收策略有关,一般为 2KB~128KB,可通过XX:PretenureSizeThreshold设置其大小。在Eden区的内存空间不足时会触发MinorGC,对新生代进行一次垃圾回收。
(2)ServivorTo区:保留上一次MinorGC时的幸存者。
(3)ServivorFrom区:将上一次MinorGC时的幸存者作为这一次MinorGC的被扫描者。
新生代的GC过程叫作MinorGC,采用复制算法实现,具体过程如下。
(1)把在Eden区和ServivorFrom区中存活的对象复制到ServivorTo区。如果某对象的年龄达到老年代的标准(对象晋升老年代的标准由XX:MaxTenuringThreshold设置,默认为 15),则将其复制到老年代,同时把这些对象的年龄加 1;如果ServivorTo区的内存空间不够,则也直接将其复制到老年代;如果对象属于大对象(大小为 2KB~128KB的对象属于大对象,例如通过XX:PretenureSizeThreshold=2097152设置大对象为 2MB,1024×1024×2Byte=2097152Byte=2MB),则也直接将其复制到老年代。
(2)清空Eden区和ServivorFrom区中的对象。
(3)将ServivorTo区和ServivorFrom区互换,原来的ServivorTo区成为下一次GC时的ServivorFrom区。
老年代
老年代主要存放有长生命周期的对象和大对象。老年代的GC过程叫作MajorGC。在老年代,对象比较稳定,MajorGC不会被频繁触发。在进行MajorGC前,JVM会进行一次MinorGC,在MinorGC过后仍然出现老年代空间不足或无法找到足够大的连续空间分配给新创建的大对象时,会触发MajorGC进行垃圾回收,释放JVM的内存空间。
MajorGC采用标记清除算法,该算法首先会扫描所有对象并标记存活的对象,然后回收未被标记的对象,并释放内存空间。
因为要先扫描老年代的所有对象再回收,所以MajorGC的耗时较长。MajorGC的标记清除算法容易产生内存碎片。在老年代没有内存空间可分配时,会抛出Out Of Memory异常。
永久代
永久代指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。Class在类加载时被放入永久代。永久代和老年代、新生代不同,GC不会在程序运行期间对永久代的内存进行清理,这也导致了永久代的内存会随着加载的Class文件的增加而增加,在加载的Class文件过多时会抛出Out Of Memory异常,比如Tomcat引用Jar文件过多导致JVM内存不足而无法启动。
需要注意的是,在Java 8中永久代已经被元数据区(也叫作元空间)取代。元数据区的作用和永久代类似,二者最大的区别在于:元数据区并没有使用虚拟机的内存,而是直接使用操作系统的本地内存。因此,元空间的大小不受JVM内存的限制,只和操作系统的内存有关。
在Java 8中,JVM将类的元数据放入本地内存(Native Memory)中,将常量池和类的静态变量放入Java堆中,这样JVM能够加载多少元数据信息就不再由JVM的最大可用内存(MaxPermSize)空间决定,而由操作系统的实际可用内存空间决定。
如何确定垃圾
- 引用计数法
- 可达性分析
1.5 Java中常用的垃圾回收算法
标记清除
其过程分为标记和清除两个阶段。在标记阶段标记所有需要回收的对象,在清除阶段清除可回收的对象并释放其所占用的内存空间。
缺点:没有重新整理可用的内存空间,因此如果内存中可被回收的小对象居多,则会引起内存碎片化的问题
复制算法
复制算法首先将内存划分为两块大小相等的内存区域,即区域 1和区域 2,新生成的对象都被存放在区域 1中,在区域 1内的对象存储满后会对区域 1进行一次标记,并将标记后仍然存活的对象全部复制到区域 2中,这时区域 1将不存在任何存活的对象,直接清理整个区域 1的内存即可
缺点:浪费内存
标记整理
标记整理算法结合了标记清除算法和复制算法的优点,其标记阶段和标记清除算法的标记阶段相同,在标记完成后将存活的对象移到内存的另一端,然后清除该端的对象并释放内存
分代收集算法
无论是标记清除算法、复制算法还是标记整理算法,都无法对所有类型(长生命周期、短生命周期、大对象、小对象)的对象都进行垃圾回收。因此,针对不同的对象类型,JVM采用了不同的垃圾回收算法,该算法被称为分代收集算法。
新生代主要存放新生成的对象,其特点是对象数量多但是生命周期短,在每次进行垃圾回收时都有大量的对象被回收;采用复制算法
老年代主要存放大对象和生命周期长的对象,因此可回收的对象相对较少。老年代主要存放生命周期较长的对象和大对象,因而每次只有少量非存活的对象被回收,因而在老年代采用标记清除算法。
1.6 Java中的4种引用类型
(1)强引用
(2)软引用
(3)弱引用
(4)虚引用
1.7 分代收集算法和分区收集算法
新生代与复制算法
新生代主要存储短生命周期的对象,因此在垃圾回收的标记阶段会标记大量已死亡的对象及少量存活的对象,因此只需选用复制算法将少量存活的对象复制到内存的另一端并清理原区域的内存即可。
老年代与标记整理算法
老年代主要存放长生命周期的对象和大对象,可回收的对象一般较少,因此JVM采用标记整理算法进行垃圾回收,直接释放死亡状态的对象所占用的内存空间即可。
1.8 垃圾收集器
Serial垃圾收集器:单线程,复制算法
Serial垃圾收集器基于复制算法实现,它是一个单线程收集器,在它正在进行垃圾收集时,必须暂停其他所有工作线程,直到垃圾收集结束。
Serial垃圾收集器采用了复制算法,简单、高效,对于单CPU运行环境来说,没有线程交互开销,可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器是Java虚拟机运行在Client模式下的新生代的默认垃圾收集器。
ParNew垃圾收集器:多线程,复制算法
ParNew垃圾收集器是Serial垃圾收集器的多线程实现,同样采用了复制算法,它采用多线程模式工作,除此之外和Serial收集器几乎一样。ParNew垃圾收集器在垃圾收集过程中会暂停所有其他工作线程,是Java虚拟机运行在Server模式下的新生代的默认垃圾收集器。
ParNew垃圾收集器默认开启与CPU同等数量的线程进行垃圾回收,在Java应用启动时可通过-XX:ParallelGCThreads参数调节ParNew垃圾收集器的工作线程数。
Parallel Scavenge垃圾收集器:多线程,复制算法
Parallel Scavenge收集器是为提高新生代垃圾收集效率而设计的垃圾收集器,基于多线程复制算法实现,在系统吞吐量上有很大的优化,可以更高效地利用CPU尽快完成垃圾回收任务。
Parallel Scavenge通过自适应调节策略提高系统吞吐量,提供了三个参数用于调节、控制垃圾回收的停顿时间及吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数,控制吞吐量大小的-XX:GCTimeRatio参数和控制自适应调节策略开启与否的UseAdaptiveSizePolicy参数。
Serial Old垃圾收集器:单线程,标记整理算法
Serial Old垃圾收集器是Serial垃圾收集器的老年代实现,同Serial一样采用单线程执行,不同的是,Serial Old针对老年代长生命周期的特点基于标记整理算法实现。Serial Old垃圾收集器是JVM运行在Client模式下的老年代的默认垃圾收集器。
新生代的Serial垃圾收集器和老年代的Serial Old垃圾收集器可搭配使用,分别针对JVM的新生代和老年代进行垃圾回收,其垃圾收集过程如图 1-15所示。在新生代采用Serial垃圾收集器基于复制算法进行垃圾回收,未被其回收的对象在老年代被Serial Old垃圾收集器基于标记整理算法进行垃圾回收。
图1-15
Parallel Old垃圾收集器:多线程,标记整理算法
Parallel Old垃圾收集器采用多线程并发进行垃圾回收,它根据老年代长生命周期的特点,基于多线程的标记整理算法实现。Parallel Old垃圾收集器在设计上优先考虑系统吞吐量,其次考虑停顿时间等因素,如果系统对吞吐量的要求较高,则可以优先考虑新生代的Parallel Scavenge垃圾收集器和老年代的Parallel Old垃圾收集器的配合使用。
新生代的Parallel Scavenge垃圾收集器和老年代的Parallel Old垃圾收集器的搭配运行过程如图 1-16所示。新生代基于Parallel Scavenge垃圾收集器的复制算法进行垃圾回收,老年代基于Parallel Old垃圾收集器的标记整理算法进行垃圾回收。
CMS垃圾收集器
CMS(Concurrent Mark Sweep)垃圾收集器是为老年代设计的垃圾收集器,其主要目的是达到最短的垃圾回收停顿时间,基于线程的标记清除算法实现,以便在多线程并发环境下以最短的垃圾收集停顿时间提高系统的稳定性。
CMS的工作机制相对复杂,垃圾回收过程包含如下4个步骤。
(1)初始标记:只标记和GC Roots直接关联的对象,速度很快,需要暂停所有工作线程。
(2)并发标记:和用户线程一起工作,执行GC Roots跟踪标记过程,不需要暂停工作线程。
(3)重新标记:在并发标记过程中用户线程继续运行,导致在垃圾回收过程中部分对象的状态发生变化,为了确保这部分对象的状态正确性,需要对其重新标记并暂停工作线程。
(4)并发清除:和用户线程一起工作,执行清除GC Roots不可达对象的任务,不需要暂停工作线程。
CMS垃圾收集器在和用户线程一起工作时(并发标记和并发清除)不需要暂停用户线程,有效缩短了垃圾回收时系统的停顿时间,同时由于CMS垃圾收集器和用户线程一起工作,因此其并行度和效率也有很大提升。CMS收集器的工作流程如图1-17所示。
G1垃圾收集器
G1(Garbage First)垃圾收集器为了避免全区域垃圾收集引起的系统停顿,将堆内存划分为大小固定的几个独立区域,独立使用这些区域的内存资源并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,在垃圾回收过程中根据系统允许的最长垃圾收集时间,优先回收垃圾最多的区域。G1垃圾收集器通过内存区域独立划分使用和根据不同优先级回收各区域垃圾的机制,确保了G1垃圾收集器在有限时间内获得最高的垃圾收集效率。相对于CMS收集器,G1垃圾收集器两个突出的改进。
◎ 基于标记整理算法,不产生内存碎片。
◎ 可以精确地控制停顿时间,在不牺牲吞吐量的前提下实现短停顿垃圾回收。
1.9 JVM的类加载过程
加载
指JVM读取Class文件,并且根据Class文件描述创建java.lang.Class对象的过程。类加载过程主要包含将Class文件读取到运行时区域的方法区内,在堆中创建java.lang.Class对象,并封装类在方法区的数据结构的过程,在读取Class文件时既可以通过文件的形式读取,也可以通过jar包、war包读取,还可以通过代理自动生成Class或其他方式读取。
验证
主要用于确保Class文件符合当前虚拟机的要求,保障虚拟机自身的安全,只有通过验证的Class文件才能被JVM加载。
准备
主要工作是在方法区中为类变量分配内存空间并设置类中变量的初始值。初始值指不同数据类型的默认值,这里需要注意final类型的变量和非final类型的变量在准备阶段的数据初始化过程不同。
解析
JVM会将常量池中的符号引用替换为直接引用。
初始化
主要通过执行类构造器的方法为类进行初始化。方法是在编译阶段由编译器自动收集类中静态语句块和变量的赋值操作组成的。JVM规定,只有在父类的方法都执行成功后,子类中的方法才可以被执行。在一个类中既没有静态变量赋值操作也没有静态语句块时,编译器不会为该类生成方法。
在发生以下几种情况时,JVM不会执行类的初始化流程。
◎ 常量在编译时会将其常量值存入使用该常量的类的常量池中,该过程不需要调用常量所在的类,因此不会触发该常量类的初始化。
◎ 在子类引用父类的静态字段时,不会触发子类的初始化,只会触发父类的初始化。
◎ 定义对象数组,不会触发该类的初始化。
◎ 在使用类名获取Class对象时不会触发类的初始化。
◎ 在使用Class.forName加载指定的类时,可以通过initialize参数设置是否需要对类进行初始化。
◎ 在使用ClassLoader默认的loadClass方法加载类时不会触发该类的初始化。
1.10 类加载器
(1)启动类加载器:负责加载Java_HOME/lib目录中的类库,或通过-Xbootclasspath参数指定路径中被虚拟机认可的类库。
(2)扩展类加载器:负责加载Java_HOME/lib/ext目录中的类库,或通过java.ext.dirs系统变量加载指定路径中的类库。
(3)应用程序类加载器:负责加载用户路径(classpath)上的类库。
除了上述 3种类加载器,我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。
1.11 JVM类加载机制-双亲委派机制
JVM通过双亲委派机制对类进行加载。双亲委派机制指一个类在收到类加载请求后不会尝试自己加载这个类,而是把该类加载请求向上委派给其父类去完成,其父类在接收到该类加载请求后又会将其委派给自己的父类,以此类推,这样所有的类加载请求都被向上委派到启动类加载器中。若父类加载器在接收到类加载请求后发现自己也无法加载该类(通常原因是该类的Class文件在父类的类加载路径中不存在),则父类会将该信息反馈给子类并向下委派子类加载器加载该类,直到该类被成功加载,若找不到该类,则JVM会抛出ClassNotFoud异常。
双亲委派类加载机制的类加载流程如下,如图1-22所示。
(1)将自定义加载器挂载到应用程序类加载器。
(2)应用程序类加载器将类加载请求委托给扩展类加载器。
(3)扩展类加载器将类加载请求委托给启动类加载器。
(4)启动类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由扩展类加载器加载。
(5)扩展类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由应用程序类加载器加载。
(6)应用程序类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由自定义加载器加载。
(7)在自定义加载器下查找并加载用户指定目录下的Class文件,如果在自定义加载路径下未找到目标Class文件,则抛出ClassNotFoud异常。
图1-22
双亲委派机制的核心是保障类的唯一性和安全性。例如在加载rt.jar包中的java.lang.Object类时,无论是哪个类加载器加载这个类,最终都将类加载请求委托给启动类加载器加载,这样就保证了类加载的唯一性。如果在JVM中存在包名和类名相同的两个类,则该类将无法被加载,JVM也无法完成类加载流程。
-
-
面试重点:Java虚拟机常见问题详解
2018-02-28 07:29:11(2)Sun HotSpot虚拟机把方法区叫做永久代(Permanent Generation),方法区中最终要的部分是运行时常量池。 三、Java对象在内存中的状态: 可达的/可触及的: Java对象被创建后,如果被一个或多个变量引用,那...作者:高效码农来源:头条科技
转自:http://news.51cto.com/art/201802/566081.htm
一、Java引用的四种状态:
强引用:
用的最广。我们平时写代码时,new一个Object存放在堆内存,然后用一个引用指向它,这就是强引用。
如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
软引用:
如果一个对象只具有软引用,则内存空间足够时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。(备注:如果内存不足,随时有可能被回收。)
只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
弱引用:
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。
每次执行GC的时候,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
虚引用:
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。
二、Java中的内存划分:
Java程序在运行时,需要在内存中的分配空间。为了提高运算效率,就对数据进行了不同空间的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。
1、程序计数器:(线程私有)
每个线程拥有一个程序计数器,在线程创建时创建,
指向下一条指令的地址
执行本地方法时,其值为undefined
2、虚拟机栈:(线程私有)
每个方法被调用的时候都会创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息。局部变量表存放的是:编译期可知的基本数据类型、对象引用类型。
每个方法被调用直到执行完成的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。
在Java虚拟机规范中,对这个区域规定了两种异常情况:
(1)如果线程请求的栈深度太深,超出了虚拟机所允许的深度,就会出现StackOverFlowError(比如无限递归。因为每一层栈帧都占用一定空间,而 Xss 规定了栈的最大空间,超出这个值就会报错)
(2)虚拟机栈可以动态扩展,如果扩展到无法申请足够的内存空间,会出现OOM
3、本地方法栈:
(1)本地方法栈与java虚拟机栈作用非常类似,其区别是:java虚拟机栈是为虚拟机执行java方法服务的,而本地方法栈则为虚拟机执使用到的Native方法服务。
(2)Java虚拟机没有对本地方法栈的使用和数据结构做强制规定,Sun HotSpot虚拟机就把java虚拟机栈和本地方法栈合二为一。
(3)本地方法栈也会抛出StackOverFlowError和OutOfMemoryError。
4、堆:即堆内存(线程共享)
(1)堆是java虚拟机所管理的内存区域中最大的一块,java堆是被所有线程共享的内存区域,在java虚拟机启动时创建,堆内存的唯一目的就是存放对象实例几乎所有的对象实例都在堆内存分配。
(2)堆是GC管理的主要区域,从垃圾回收的角度看,由于现在的垃圾收集器都是采用的分代收集算法,因此java堆还可以初步细分为新生代和老年代。
(3)Java虚拟机规定,堆可以处于物理上不连续的内存空间中,只要逻辑上连续的即可。在实现上既可以是固定的,也可以是可动态扩展的。如果在堆内存没有完成实例分配,并且堆大小也无法扩展,就会抛出OutOfMemoryError异常。
5、方法区:(线程共享)
(1)用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
(2)Sun HotSpot虚拟机把方法区叫做永久代(Permanent Generation),方法区中最终要的部分是运行时常量池。
三、Java对象在内存中的状态:
可达的/可触及的:
Java对象被创建后,如果被一个或多个变量引用,那就是可达的。即从根节点可以触及到这个对象。
其实就是从根节点扫描,只要这个对象在引用链中,那就是可触及的。
可恢复的:
Java对象不再被任何变量引用就进入了可恢复状态。
在回收该对象之前,该对象的finalize()方法进行资源清理。如果在finalize()方法中重新让变量引用该对象,则该对象再次变为可达状态,否则该对象进入不可达状态
不可达的:
Java对象不被任何变量引用,且系统在调用对象的finalize()方法后依然没有使该对象变成可达状态(该对象依然没有被变量引用),那么该对象将变成不可达状态。
当Java对象处于不可达状态时,系统才会真正回收该对象所占有的资源。
四、判断对象死亡的两种常用算法:
1、引用计数算法:
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
但是,主流的java虚拟机并没有选用引用计数算法来管理内存,其中最主要的原因是:它很难解决对象之间相互循环引用的问题。
2、根搜索算法:(jvm采用的算法)
设立若干种根对象,当任何一个根对象(GC Root)到某一个对象均不可达时,则认为这个对象是可以被回收的。
五、垃圾回收算法
1、标记-清除算法:
标记阶段:先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象;
清除阶段:清除所有未被标记的对象。
2、复制算法:(新生代的GC)
将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,然后清除正在使用的内存块中的所有对象。
3、标记-整理算法:(老年代的GC)
标记阶段:先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象
整理阶段:将将所有的存活对象压缩到内存的一端;之后,清理边界外所有的空间
4、分代收集算法:
存活率低:少量对象存活,适合复制算法:在新生代中,每次GC时都发现有大批对象死去,只有少量存活(新生代中98%的对象都是“朝生夕死”),那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成GC。
存活率高:大量对象存活,适合用标记-清理/标记-整理:在老年代中,因为对象存活率高、没有额外空间对他进行分配担保,就必须使用“标记-清理”/“标记-整理”算法进行GC。
六、垃圾收集器
1、Serial收集器:(串行收集器)
这个收集器是一个单线程的收集器,但它的单线程的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程(Stop-The-World:将用户正常工作的线程全部暂停掉),直到它收集结束。
2、ParNew收集器:Serial收集器的多线程版本(使用多条线程进行GC)
ParNew收集器是Serial收集器的多线程版本。
它是运行在server模式下的首选新生代收集器,除了Serial收集器外,目前只有它能与CMS收集器配合工作。CMS收集器是一个被认为具有划时代意义的并发收集器,因此如果有一个垃圾收集器能和它一起搭配使用让其更加完美,那这个收集器必然也是一个不可或缺的部分了。
3、ParNew Scanvenge收集器
类似ParNew,但更加关注吞吐量。目标是:达到一个可控制吞吐量的收集器。
停顿时间和吞吐量不可能同时调优。我们一方买希望停顿时间少,另外一方面希望吞吐量高,其实这是矛盾的。因为:在GC的时候,垃圾回收的工作总量是不变的,如果将停顿时间减少,那频率就会提高;既然频率提高了,说明就会频繁的进行GC,那吞吐量就会减少,性能就会降低。
吞吐量:CPU用于用户代码的时间/CPU总消耗时间的比值,即=运行用户代码的时间/(运行用户代码时间+垃圾收集时间)。比如,虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
4、G1收集器:
是当今收集器发展的最前言成果之一,直到jdk1.7,sun公司才认为它达到了足够成熟的商用程度。
5、CMS收集器:(老年代收集器)
CMS收集器(Concurrent Mark Sweep:并发标记清除)是一种以获取最短回收停顿时间为目标的收集器。适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。
七、Java堆内存划分:
Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。
在 Java 中,堆被划分成两个不同的区域:年轻代 ( Young )、老年代 ( Tenured)。年轻代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。 这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
1.年轻代
年轻代用来存放新近创建的对象,尺寸随堆大小的增大和减小而相应的变化,默认值是保持为堆大小的1/15,可以通过 -Xmn 参数设置年轻代为固定大小,也可以通过 -XX:NewRatio 来设置年轻代与年老代的大小比例,年青代的特点是对象更新速度快,在短时间内产生大量的“死亡对象”。
年轻代的特点是产生大量的死亡对象,并且要是产生连续可用的空间, 所以使用复制清除算法和并行收集器进行垃圾回收.对年轻代的垃圾回收称作初级回收 (minor gc)。
2.老年代
Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。
现实的生活中,老年代的人通常会比新生代的人 “早死”。堆内存中的老年代(Old)不同于这个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。 另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。
3.永久代
永久代是Hotspot虚拟机特有的概念,是方法区的一种实现,别的JVM都没有这个东西。在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间。
永久代或者“Perm Gen”包含了JVM需要的应用元数据,这些元数据描述了在应用里使用的类和方法。注意,永久代不是Java堆内存的一部分。永久代存放JVM运行时使用的类。永久代同样包含了Java SE库的类和方法。永久代的对象在full GC时进行垃圾收集。
八、类加载机制:
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
对应常见笔试题
注意:子类初始化问题:满足主动调用,即父类访问子类中的静态变量、方法,子类才会初始化;否则仅父类初始化。
注意:访问类或接口的静态变量(特例:如果是用static final修饰的常量,那就不会对类进行显式初始化。static final 修改的变量则会做显式初始化)
上面的运行效果显示,由于c是final static修饰的静态常量,所以根本就没有调用静态代码块里面的内容,也就是说,没有对这个类进行显式初始化。
-
JVM运行时数据区原理解析
2020-08-25 17:12:39主要介绍了JVM运行时数据区原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
关于JVM中Eden区、Survivor from区和Survivor to区的理解
2018-09-03 10:09:29首先明确新生代都是分配于Eden区的,所以Eden区是最重要也是内存回收最重要的管理区域,同时也是最频繁的内存替换区域。我们知道JVM将内存根据分代策略将内存分为三层,新生代所占据的内存、老年...本文主要根据《深入理解JVM》中内存回收策略,主要关注如下五个方面:
1:Eden区分配
2:大对象直接进入老年代
3:长期存活的对象直接进入老年代
4:动态对象年龄判定
5:空间分配担保
首先明确新生代都是分配于Eden区的,所以Eden区是最重要也是内存回收最重要的管理区域,同时也是最频繁的内存替换区域。我们知道JVM将内存根据分代策略将内存分为三层,新生代所占据的内存、老年代所占据的内存以及永久代,我们这里不关注永久代,因为永久代是属于方法区内存的部分,而新生代和老年代都是属于堆内存区域的。
新生代中又继续分为三个子块,Eden区、Survivor from区、Survivor to区,实际上分为三个区的原因是为了方便采用复制-清除(详情请参考深入理解JVM中内存回收策略)策略而采用的策略,复制策略就是将原来存在的内存分为两个相等的区,使用一块进行新生代的内存分配,当要GC时,则将存活的对象复制进入另一块空闲的内存,然后将使用的内存进行清除,从而又有一个空闲区和一个使用区,并且不会有碎片问题。实际上并不需要两个1:1的分区比例,因为一般存活的对象很少,所以JVM聪明的讲新生代占据的总内存分为Eden:Survivor from:Survivor to = 8:1:1三部分,其中Eden就用来分配新的对象内存,Survivor from则用于GC时的复制,那为什么需要两个Survivor区呢,因为复制后Survivor from区虽然现在很整齐,没有碎片,当下一次进行回收时,Eden区和Survivor from区里都存在需要回收的对象,则Survivor from区也会出现碎片。
那么现在,我们看一下上述的五个部分:所有的新生代首先会在Eden区进行内存分配,当Eden区满时会进行一次Minor GC操作,将Eden区进行回收,此时判断存活的对象会被复制进入Survivor from区(年龄加1),对于大对象直接进入老年代,实际上是为了保证Eden区具有充足的空间可用的一种策略,采用-XX:PretenureSizeThreshold参数可以设置多大的对象可以直接进入老年代内存区域。对于长期存活的对象直接进入老年代,实际上时对Eden区到Survivor区过度的一种策略,是为了保证Eden区到Survivor区不会频繁的进行复制一直存活的对象且对Survivor区也能保证不会具有太多的一直占据的内存,采用-XX:MaxTenuringThreshold=数字 参数可以设置对象在经过多少次GC后会被放入老年代(年龄达到设置值,默认为15)。对于动态对象年龄判断,实际上是对Survivor区的一种策略,是为了保证Survivor区具有充足的空间用于分配,动态对象年龄只判断Survivor区是否存在相等对象年龄的对象是否超过Survivor from/to的一半时,直接将超过的对象放入老年代。对于空间分配担保实际上是针对老年代,为了保证老年代的内存区域具有充足的空间,不至于内存溢出的情况出现,在发生MinorGC之前,JVM会判断之前每次晋升到老年代的平均大小是否大于老年代剩余空间的大小,若大于则进行full GC(即回收所有区域),若小于,则还需要查看一个参数HandlePromotionFailure,即是否允许担保失败,因为实际上进入老年代的对象大小在GC前是未知的,这也是为什么采用之前晋升的平均值来进行判断担保,也就是说只是一种预测,并不能代表真实就是有这么多对象晋升,所以若不允许担保失败,即保守的人为一定会有超过剩余老年代区域的对象存入,则还是进行Full GC,否则,进行Minor GC。
-
(面经总结)一篇文章带你整理面试过程中关于 JVM 的运行内存划分、垃圾回收算法和 4种引用类型的相关知识...
2022-04-30 22:31:08文章目录一、JVM的运行时内存1. 新生代2. 老年代3. 永久代二、垃圾回收与算法1. 如何确定垃圾对象(1)引用计数法(2)可达性分析2. Java中常用的垃圾回收算法(1)标记清除算法(2)复制算法(3)标记整理算法(4)... -
Java面试核心知识点1——JVM
2020-07-19 22:04:08我们知道JVM是用于运行Java字节码的虚拟机,主要包括一套字节码指令集、一组程序计数器、一个虚拟机栈、一个虚拟机堆、一个方法区和一个垃圾回收器。JVM运行在操作系统之上,不与硬件设备直接交互。 Java源文件(.... -
offer来了-JVM
2020-12-27 23:23:27JVM(Java Virtual Machine)是用于运行Java字节码的虚拟机,包括一套字节码指令集、一组程序寄存器、一个虚拟机栈、一个虚拟机堆、一个方法区和一个垃圾回收器。 JVM运行在操作系统之上,不与硬件设备直接交互。... -
Java虚拟机运行机制与相关概念
2020-11-27 21:33:55直接内存程序计数器:线程私有,无内存溢出问题虚拟机栈:线程私有,描述Java方法的执行过程本地方法区:线程私有堆:也叫做运行时数据区,线程共享方法区:线程共享JVM的运行内存1.新生代2.MinorGC:新生代的gc过程... -
JVM的入门知识
2020-08-11 22:17:00新生代又分为Eden区、ServivorFrom区和ServivorTo区,Eden区默认占8/10新生代空间,ServivorFrom区和ServivorTo区默认分别占1/10新生代空间。 4.1 新生代 JVM新创建的对象(除了大对象外)会被存放在新生代,默认占1... -
【面试】JVM知识点总结
2019-07-07 09:52:20所有对象都创建在新生代的Eden区,当Eden区满后触发Minor GC,将Eden区和ServivorFrom去存活的对象复制到ServivorTo区中,并且将对象的“年龄”+1,如果达到进入老年代的条件(ServivorTo区空间不足或者年龄达到进入... -
Java程序员面试冲刺day01
2021-06-03 15:38:40jvm为java虚拟机,是用于运行java字节码的虚拟机,jvm主要包括一套字节码指令集、一组程序寄存器、一个虚拟机栈、一个虚拟机堆、一个方法区和一个垃圾回收器。jvm是运行在操作系统上的,与硬件设备之间也是靠操作... -
Java面试核心知识点整理1——JVM
2020-03-23 15:11:54Java虚拟机包括一个类加载子系统、运行时数据区、执行引擎和本地接口库。本地接口库通过调用本地方法库与操作系统交互。 类加载器子系统用于将编译好的.class文件加载到JVM中。 运行时数据区用于存储在JVM运行过程... -
JAVA架构知识库整理 JAVA架构知识 Java架构师 Java 架构师必备
2022-07-15 10:22:18ServivorTo.2.3.1.4. MinorGC 的过程(复制->清空->互换)1: eden, servicorFrom 制到 ServicorTo, 年龄+1..2:清空eden, servicorFrom..3: ServicorTo 和 ServicorFrom互换,2.3.2. 老年代.2.3.3. 永久代2.3.3.1. JAVA... -
Java8 面试基础知识宝典【主要以问题的形式记录,SE,EE,锁机制,框架,数据库等】
2020-10-13 23:02:03目录JavaSE 8基础语法 9Q1:简单说说Java有哪些数据类型Q2:float number=3.4;有没有问题?为什么?默认类型【int、double】Q3:字符串拼接的方式以及效率?Q4:简述final,finally和finalize区别Q5:==和equals有... -
Java JVM区域划分、运行时内存、GC
2021-12-21 10:11:42基本划分:程序计数器/PC寄存器、虚拟机栈、本地方法栈、方法区、堆、直接内存 线程私有区域包含(即生命周期与线程相同,依赖用户线程的启动/结束,而创建/销毁JVM内):程序计数器/PC寄存器、虚拟机栈、本地方法栈... -
JVM知识汇总
2021-12-26 20:39:21Eden、ServivorFrom复制到ServivorTo区,年龄+1 首先,把Eden和ServivorFrom区域中存活的对象复制到ServivorTo区域,同时把这些对象的年龄+1。在复制的过程中,有两种情况会放入到老年区:有对象的年龄达到了老年的... -
计网+操作系统+多线程+JVM
2022-06-22 00:20:43答:网络的七层架构从下到上主要包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。①物理层:物理层主要定义物理设备标准,主要作用是传输比特流,具体做法是在发送端将1、0转化为电流强弱来进行... -
redis+jdbc+多线程+JVM
2022-06-26 17:03:35JDBC介绍 JDBC(Java DataBase Connectivity)是Java和数据库之间的一个桥梁,是一个规范而不是一个实现,能够执行SQL语句。它由一组用Java语言编写的类和接口组成。各种不同类型的数据库都有相应的实现,本文中的... -
【JVM内存区域】
2021-11-30 08:45:38我是廖志伟,一名Java开发...文章目录JVM内存区域程序计数器(线程私有)虚拟机栈(线程私有)本地方法区(线程私有)堆(Heap-线程共享)-运行时数据区新生代Eden区ServivorFrom区ServivorTo区MinorGC 的过程(复制->清. -
读书笔记--运行时数据区
2019-03-15 16:32:03运行时数据区 程序计数器(线程私有) 定义:是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号显示器。 该空间为各线程私有空间,为保证线程切换后可以恢复到正确的执行位置。 若执行的是Native方法,... -
JVM总览
2021-03-17 14:47:222.4 堆:线程共享,也称运行时数据区 2.5 方法区:线程共享 3 JVM运行时内存解析 3.0 概述 3.1 新生代:Eden区、ServivorFrom区和ServivorTo区 3.2 老年代 3.3 永久代 4 垃圾回收与算法 4.1 如何确定垃圾 4.1.1 引用... -
JVM基本知识(五) 面试题总结
2021-04-06 14:36:59新生代又分为Eden区、ServivorFrom区和ServivorTo区,Eden区默认占8/10新生代空间,ServivorFrom区和ServivorTo区默认分别占1/10新生代空间(8:1:1)。 新生代:JVM新创建的对象(除了大对象外)会被存放在新生代,... -
JVM知识梳理
2022-01-11 11:07:19新生代分为Eden区、ServivorFrom、 ServivorTo区。 1.Eden区 Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收... -
JVM内功心法-JVM内存模型之内存区域
2022-05-02 22:35:46ServivorFrom(S1):存放的是上一次MinorGC的幸存者,在上一次中他是ServivorTo区,他在这一次中充当被扫描者的角色 。 (2)老年代 老年代主要存放的就是长生命周期的对象和大对象,不会频繁出发GC了,老年代的GC... -
JVM面试高频问题(JVM)
2022-07-28 11:26:15新生代又细分为三个区:Eden区、SurvivorFrom、ServivorTo区,三个区的默认比例为:8:1:1。 Eden区:Java新创建的对象绝大部分会分配在Eden区(如果对象太大,则直接分配到老年代)。当Eden区内存不够的时候,就会... -
【JVM】
2022-01-11 18:55:09— 文章目录 HotspotJVM后台运行的系统线程 JVM内存区域 程序计数器(线程私有) 虚拟机栈(线程私有) 本地方法区(线程私有) 堆(Heap-线程共享)-运行时数据区 新生代 Eden区 ServivorFrom区 ServivorTo区 MinorGC 的... -
JVM分析(附GC的有关知识)
2022-05-16 17:02:57复制:将Eden区和SurvivorFrom区的新生对象复制到SurvivorTo区,同时给这些对象年龄+1,如果对象年龄达到了老年标准,默认年龄15,则复制到老年代区,如果ServivorTo区内存不够则放到老年区; 2.清空:将Eden区和... -
jvm初探
2022-08-03 23:17:56堆在逻辑上划分为新生代(EdenSpace 区、ServivorFrom区、ServivorTo区)和老年代; jdk1.8,字符串常量池和静态变量在堆中 栈:线程私有,每个方法的执行都会产生一个栈帧,用于存储局部变量表,操作数栈,动态链接...