出名的网站建设软件,无锡常规网络营销是什么,wordpress数据库连接时错误,做选择的网站基础题
能不能给我讲一下JVM完整的GC流程
我们先从Minor GC说起吧#xff0c;当对象分配到Eden区发现Eden区空间满了#xff0c;此时就会触发Minor GC#xff0c;将非存活对象回收#xff0c;再将存活对象放到From区(S1区)#xff0c;再将新创建的对象放到Eden区。 随着…基础题
能不能给我讲一下JVM完整的GC流程
我们先从Minor GC说起吧当对象分配到Eden区发现Eden区空间满了此时就会触发Minor GC将非存活对象回收再将存活对象放到From区(S1区)再将新创建的对象放到Eden区。 随着时间推移Eden区再次满了此时再次触发Minor GC将非存活对象清理存活对象放到S2区。 然后我们再来说说对象升级到老年代的4种情况:
经过Minor GC后S区的toSpace区无法容纳的存活的对象。大对象直接进入老年代而这个大对象大对象可以根据参数PretenureSizeThreshold知晓值的大小。长期存活的对象直接进入老年代JVM默认设置为15(即在年轻代经过15次Minor GC)还有一种情况如果在From空间中相同年龄所有对象的大小总和大于From和To空间总和的一半那么年龄大于等于该年龄的对象就会被移动到老年代而不用等到15岁(默认)
接下来就是Full GC了
因为上述原因需要将对象放到老年代但是老年代的空间不够存放对象时就会触发Full GC,如果Full GC完还是无法容纳新对象就会报OOM的异常。
为确保Minor GC后有足够的空间可以容纳依旧存活的对象JVM还提出了空间分配担保机制:
在发生Minor GC前JVM会检查老年代最大连续可用空间是否大于年轻代所有对象的总空间:
若大于年轻代总空间则说明本次垃圾回收是安全继续执行Minor GC。若小于年轻代的总空间且这个版本在在JDK 6 Update24之前他们则会查看则看参数HandlerPromotionFailure的配置值
1. 若配置为true,则会再次检查每次晋升到老年代的平均大小若老年代空间大于这个值则无视风险直接进行Minor GC若小于则说明当前老年代空间确实不太够了直接进行FULL GC。
2. 若配置为false则直接Full GC。在JDK 6 Update24之后HandlerPromotionFailure这个参数就被作废了在进行Minor GC前的空间分配担保检查的就是老年代的连续空间是否大于新生代的对象的总大小或者每次晋升的平均大小符合任意条件则直接进行Minor GC反之就是FULL GC。
能不能说一下JVM内存区域的分配
内存区域有堆区、虚拟机栈、本地方法栈、程序计数器、方法区。
其中方法区和堆区为线程共享的。其余都是线程隔离的。
而各个组成部分的作用为:
程序计数器Program Counter Register也被称为 PC 寄存器是一块较小的内存空间。它可以看作是当前线程所执行的字节码的行号指示器。它指向当前线程要执行的下一条指令的地址。Java 虚拟机栈Java Virtual Machine Stack:也是线程私有的它的生命周期与线程相同。Java 虚拟机栈描述的是 Java 方法执行的线程内存模型,方法执行时JVM 会同步创建一个栈帧用来存储局部变量表、操作数栈、动态连接等。本地方法栈Native Method Stacks:与虚拟机栈所发挥的作用是非常相似的其区别只是虚拟机栈为虚拟机执行 Java 方法也就是字节码服务而本地方法栈则是为虚拟机使用到的本地Native方法服务。对于 Java 应用程序来说Java 堆Java Heap是虚拟机所管理的内存中最大的一块。Java 堆是所有线程共享的一块内存区域在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例Java 里“几乎”所有的对象实例都在这里分配内存。 Java 堆是垃圾收集器管理的内存区域因此一些资料中它也被称作“GC 堆”Garbage Collected Heap。从回收内存的角度看由于现代垃圾收集器大部分都是基于分代收集理论设计的所以 Java 堆中经常会出现新生代、老年代、Eden空间、From Survivor空间、To Survivor空间等名词需要注意的是这种划分只是根据垃圾回收机制来进行的划分不是 Java 虚拟机规范本身制定的。方法区:是比较特别的一块区域和堆类似它也是各个线程共享的内存区域用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。它特别在 Java 虚拟机规范对它的约束非常宽松所以方法区的具体实现历经了许多变迁例如 jdk1.7 之前使用永久代作为方法区的实现。
说一下 JDK1.6、1.7、1.8 内存区域的变化
JDK6使用永久代作为方法区。JDK7将字符串常量池、静态变量放到堆区而类常量池、运行时常量池仍然存放在方法区中。 JDK8则是用元数据区实现作为方法区的实现。去掉了永久代这么个东西而元数据区存放的仍然是运行时常量池和类常量池。 请你介绍一下JVM方法区
答: 方法区主要是用于存储类信息、静态变量以及常量信息的。是各个线程共享的一个区域。我们都知道JVM中有个区域叫堆区所以有时候人们也会称方法区为Non-Heap非堆。
在JDK8之前方法区存放在一个叫永久代的空间里。 在JDK8之后由于HotSpot 和JRockit 的合并所以方法区就被作为元数据区了。
那你知道方法区和永久代的关系吗
答: 其实方法区并不是一个实际的区域他不过是JVM定义的一个规范而已。在HotSpot 实现方法区的方式就在JVM内存中划分一个区域作为永久代来存放这些数据。
在JDK8之前我们可以用下面的参数来调整永久代的大小
-XX:PermSizeN //方法区 (永久代) 初始大小
-XX:MaxPermSizeN //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen那么你来说说为什么JDK8之后要把永久代 (PermGen)换成元数据区(MetaSpace) ?
答 将数据放在永久代固然没问题但是随着时间的推移方法区使用的空间可能会逐渐变大若我们分配大小不当很可能造成线上OOM问题所以设计者们就在方法区移动到本地内存中通过本地内存来存放数据。并且元数据区默认分配值为unlimited(我们也可以通过-XXMetaspaceSize来动态调整)理论上是没有明确大小是可以动态分配空间的这样一来由于元数据区就不会受到JVM内存分配的约束了所以理论上发生OOM的概率会小于永久代。
请你介绍一下运行时常量池
首先我们需要了解一下类常量池
类常量池:主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References)。答: 我们都知道Class文件包含字段描述符、方法描述符、接口等描述信息还有编译器生成的字面量和符号引用都会被存放到JVM方法区的运行时常量池中。
在JDK7之前运行时常量池包括字符串常量池都存放在永久代。JDK7将字符串常量池移动到了堆区。而其他数据依然保留在方法区中即可永久代区。在JDK8则将永久代改为元数据区这就意味着运行时常量池就被存放到永久代的元数据区了。
JVM 常量池中存储的是对象还是引用呢
答: runtime constant pool而不是interned string pool / StringTable之类的其他东西的话其中的引用类型常量例如CONSTANT_String、CONSTANT_Class、CONSTANT_MethodHandle、CONSTANT_MethodType之类都存的是引用实际的对象还是存在Java heap上的。
说说元空间的直接内存的概念吧
答: 在JDK1.4中 NIO(New Input/Output) 类提供的一个名为MappedByteBuffer的内存映射文件的方式直接调用Native操作本机内存通过这种方式避免操作数据从JVM堆到Native堆的开销从而提高程序执行效率。这就意味着这种这个操作会受到本机内存大小以及处理器寻址空间的限制。
说一下Java对象的创建过程
答: 整体过程大抵是一下几个步骤:
类加载检查: 在JVM收到new命令后就会先去常量池查看是否有这个类的符号引用若有则再查看这个类是否被加载、解析、初始化过。若没有则进行类加载。分配内存空间 在堆区划出一个空间将为对象分配空间。设置零值: 完成对象空间的分配之后就需要将对象中的字段都赋为初始值(除了对象头)。设置对象头: 完成上述步骤我们就需要为对象头设置哈希码、对象的GC分代信息、元数据信息、以及是否使用偏向锁等都放到对象头中。执行init方法: new方法最终一步调用init完成对象的创建。
能说一下对象内存布局嘛
宏观来说有这么几个模块大抵可以分为:
对象头实例数据对齐填充
先来说说对象头它由两个部分组成第一个部分则是记录自身信息的包含哈希码、gc分代年龄、锁状态标志、线程持有的锁、偏向锁id、偏向时间戳等它也叫markword。需要补充的是如果这个是属于数组类型的话 第二个部分则是类型指针指向对象的类元数据类型这个类型指针的存在使得我们可以知晓它是哪个类。
实例数据用来存储对象中各自类型的字段内容即使是从父类继承来的它也会记录。
对齐填充不是必须的仅仅作为占位符使用的。
上文提到的分配内存空间这一步你知道内存分配的两种算法吗
答 一种是指针碰撞、还有一种是空闲列表。
指针碰撞使用是堆区空间规整的情况下例如你使用复制算法、或者标记-整理算法时堆区空间就是规整的。而空闲列表则适用于空间不完整的情况例如标记-清除算法。
了解过Java内存对象多线程并发分配问题吗
答 在分配空间时JVM首先会预先为Eden区为每个线程分配一个TLAB空间。每次线程都只能操作自己的TLAB区以及读取其他线程的TLAB区(但是不能操作)若TLAB空间满了或者不够分配当前对象时则基于CAS失败重试在堆区其他空间尝试分配空间。
知道对象访问定位的两种访问方式吗
答 有两种方式一种是使用句柄一种是直接指针。句柄方式则将对象地址、以及对象对应的类的地址信息存放到一个句柄中引用直接通过句柄得到对象的实际地址进而去操作要访问的对象。 直接指针方式则是引用中直接记录对象的地址我们直接通过引用的地址得到对象的地址进而直接操作对象。而对象类型数据信息则都存储在堆中的对象头里。 所以前者优势是稳定即引用无需因为对象的移动而改变则动态修改。后者优势则是略去了访问句柄的一步效率更高一些。
请你说说堆内存分配的基本策略
答 对象优先会被分配在eden区如果是大对象直接分配到老年区避免分配担保机制的负担当对象存活时间达到-XX:MaxTenuringThreshold的值时也会到老年区。
什么是内存分配担保机制
答 如果我们创建了一个大对象Eden区不足以分配该对象就会将Eden区的对象移到Survivor区然后将大对象放到Eden区。
内存溢出和内存泄露了解过嘛
内存泄露指的无用的对象未能实时的清除导致堆区内存被一些无用的垃圾占用。而导致内存泄漏的大概会有以下几个原因:
静态集合类静态集合声明周期和JVM一致的所以它不可能释放掉代码如下所示
public class OOM {static List list new ArrayList();public void oomTests(){Object obj new Object();list.add(obj);}
}
单例模式单例但模式和静态集合类原因差不多如果这个单例模式是大对象且未能及时销毁很可能导致内存泄漏问题。IO等连接资源未能及时释放ThreadLocal变量ThreadLocal 的弱引用导致内存泄漏也是个老生常谈的话题了使用完 ThreadLocal 一定要记得使用 remove 方法来进行清除。hash值改变:对象 Hash 值改变使用 HashMap、HashSet 等容器中时候由于对象修改之后的 Hah 值和存储进容器时的 Hash 值不同所以无法找到存入的对象自然也无法单独删除了这也会造成内存泄漏。说句题外话这也是为什么 String 类型被设置成了不可变类型。变量作用域过大
public class Simple {Object object;public void method1(){object new Object();//...其他代码//由于作用域原因method1执行完成之后object 对象所分配的内存不会马上释放object null;}
}
而内存溢出则是当前堆区空间无法容纳新对象导致OOM问题。代码如下所示
/*** VM参数 -Xms20m -Xmx20m -XX:HeapDumpOnOutOfMemoryError*/
public class HeapOOM {static class OOMObject {}public static void main(String[] args) {ListOOMObject list new ArrayListOOMObject();while (true) {list.add(new OOMObject());}}
}
可以说内存泄漏会导致内存溢出。
Minor GC/Young GC、Major GC/Old GC、Mixed GC、Full GC 都是什么意思
Minor GC/Young GC指的是年轻代的垃圾收集。Major GC/Old GC指的是老年代的GC目前只有CMS收集器会有单独收集老年代的行为。Mixed GC混合收集指的是新生代和老年代的垃圾收集目前只有G1收集器会有这种行为。Full GC:收集整个Java堆和方法去的垃圾。
Minor GC什么时候触发
当年轻代空间不足的时候就会触发Minor GC
什么时候会触发 Full GC
minor GC前检查老年代发现空间不足:在要进行 Young GC 的时候发现老年代可用的连续内存空间 新生代历次Young GC后升入老年代的对象总和的平均大小说明本次 Young GC 后可能升入老年代的对象大小可能超过了老年代当前可用内存空间,那就会触发 Full GC。Minor gc后老年代空间不足:执行 Young GC 之后有一批对象需要放入老年代此时老年代就是没有足够的内存空间存放这些对象了此时必须立即触发一次 Full GC调用system.gc()空间分配担保失败新生代的 To 区放不下从 Eden 和 From 拷贝过来对象或者新生代对象 GC 年龄到达阈值需要晋升这两种情况老年代如果放不下的话都会触发 Full GC。老年代空间不足:老年代内存使用率过高达到一定比例也会触发 Full GC。方法去内存空间不足:如果方法区由永久代实现永久代空间不足 Full GC。
对象什么时候会进入老年代
长时间存活的对象:在对象的对象头信息中存储着对象的迭代年龄,迭代年龄会在每次 YoungGC 之后对象的移区操作中增加,每一次移区年龄加一.当这个年龄达到 15(默认)之后,这个对象将会被移入老年代。
这个可以通过下面这个参数进行设置
- XX:MaxTenuringThreshold
大对象直接进入老年代:有一些占用大量连续内存空间的对象在被加载就会直接进入老年代.这样的大对象一般是一些数组,长字符串之类的对。大对象的阈值可以通过这个参数进行设置
-XXPretenureSizeThreshold
动态对象年龄判断:为了能更好地适应不同程序的内存状况HotSpot 虚拟机并不是永远要求对象的年龄必须达到- XXMaxTenuringThreshold 才能晋升老年代如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半年龄大于或等于该年龄的对象就可以直接进入老年代。空间分配担保:假如在 Young GC 之后新生代仍然有大量对象存活就需要老年代进行分配担保把 Survivor 无法容纳的对象直接送入老年代。
能不能简单介绍一下强引用、弱引用、软引用、虚引用?
答: 被强引用(StrongReference)指向的对象无论如何都不会被垃圾回收器回收宁可被OOM也不会被回收。软引用(SoftReference)相较于强引用地位低一些当内存空间不足的时候它就会被直接回收一旦它引用的对象被回收他就被存放到一个与之关联的引用队列中。弱引用(WeakReference)地位比强引用更低只要被垃圾回收器线程扫描到就会被直接回收,一旦被回收该引用也被被存放到与之关联的一个队列中。虚引用(PhantomReference)没有任何地位任何时间段都能够被回收。 当然在平时工作中弱引用和虚引用使用的就不是很多更多是使用软引用因为软引用不会在没必要的时候被回收只有内存不足时才能回收这样对于JVM性能开销来说回更节约一些。
如何判断一个常量是废弃常量?
答 我们以字符串为例如果字符串常量没有任何引用指向的话那么在垃圾回收阶段这个常量就会被回收。
如何判断一个类是无用的类?
答: 这里说到的是类吧判断类是否无用大概是从以下3点判断: 1. 这个类的所有实例都被垃圾回收器回收了也就是Java堆中没有任何该类的实例。2. ClassLoader 被回收了。3. java.lang.Class类没有被被引用无法通过任何地方完成反射操作了。如果符合上述三点就说明这个类可以被回收了注意仅仅是可以不代表真的就要被回收了。
HotSpot 为什么要分为新生代和老年代
答 主要是为了提高GC效率对年轻代和老年代采用不同的回收算法利用好每个内存区域空间。
Class 的作用了解么
答: class文件即字节码文件是面向虚拟机的一种文件它解决传统解释器语言效率低的问题。也正是由于它是面向虚拟机的文件所以Java代码只需编译一次即可在任何有虚拟机的平台使用。
请你介绍一下类加载过程
答 加载连接(验证、准备、解析、初始化)、初始化
大抵分为以下三步:
通过全类名获取这个类的二进制字节流。将这个静态字节流转换为方法区运行时数据结构。在方法区生成Class 对象作为访问这些数据的入口。
知道那些类加载器嘛
答知道大概有以下三个:
BootstrapClassLoader(启动类加载器):负责%JAVA_HOME%/lib目录下的 jar 包和类或者或被 -Xbootclasspath参数指定的路径中的所有类。ExtensionClassLoader(扩展类加载器) :主要负责加载目录 %JRE_HOME%/lib/ext 目录下的 jar 包和类或被 java.ext.dirs 系统变量所指定的路径下的 jar 包。AppClassLoader(应用程序类加载器) : 负责加载当前应用程序classpath下的所有jar包。
双亲委派模型的源码简单分析一下
答: 如下所示从源码中我们就可以了解双亲委派机制了可以看到loadClass方法会先去查看方法区中是否有该类的加载信息。若没有则会先让根加载器先尝试加载若没有则再找扩展类加载器最后才是应用程序类加载器。
private final ClassLoader parent;
protected Class? loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// 首先检查请求的类是否已经被加载过Class? c findLoadedClass(name);if (c null) {long t0 System.nanoTime();try {if (parent ! null) {//父加载器不为空调用父加载器loadClass()方法处理c parent.loadClass(name, false);} else {//父加载器为空使用启动类加载器 BootstrapClassLoader 加载c findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {//抛出异常说明父类加载器无法完成加载请求}if (c null) {long t1 System.nanoTime();//自己尝试加载c findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}双亲委派模型有什么好处
答: 有两点好处:
避免重复加载相同的类。避免用户编写的类破坏核心API。
如果不想使用双亲委派模型怎么办
答 如果想自定义类加载器的话继承ClassLoader 类就好了。如果想破坏双亲委派机制的话就重写我们上文所说的loadClass方法。
静态变量在堆区还是在原空间?它是否会被GC回收
jdk8之后静态成员变量存储在哪有说存在元数据区有说迁移到堆中希望大佬能给个详细的解答多谢 - 红尘修行的回答 - 知乎 https://www.zhihu.com/question/324306038/answer/688264413
jdk8之后静态成员变量存储在哪有说存在元数据区有说迁移到堆中希望大佬能给个详细的解答多谢 - 红尘修行的回答 - 知乎 https://www.zhihu.com/question/324306038/answer/688264413
java static GC 回收问题:https://blog.csdn.net/kangojian/article/details/5186530
参考文献
JVM内存分配担保机制:https://blog.csdn.net/kavito/article/details/82292035
运行时常量池JVM06:https://zhuanlan.zhihu.com/p/353663613
剖析面试最常见问题之JVM(下):https://xiaozhuanlan.com/topic/3621504987#section1class
深入理解Java虚拟机第3版:https://book.douban.com/subject/34907497/