- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
首先面试官会询问你在进行JVM调优之前,是否了解JVM内存模型的基础知识。这是一个重要的入门问题。JVM内存模型主要包括程序计数器、堆、本地方法栈、Java栈和方法区(1.7之后更改为元空间,并直接使用系统内存).
正常堆内存又分为年轻代和老年代。在Java虚拟机中,年轻代用于存放新创建的对象,而老年代则用于存放生命周期较长的对象。具体而言,根据默认设置,年轻代和老年代的比例通常为1:2。也就是说,年轻代占整个堆内存的1/3,而老年代占2/3。这样的比例设置可以更好地适应不同类型的对象的内存需求,提高垃圾回收效率,从而优化程序的性能。具体默认比例如下:
APPClassLoader->ExtClassLoader->BooStrapClassLoader; 。
具体获取类加载的代码示例如下:
public class ClassLoaderExample {
public static void main(String[] args) {
// 获取当前类的类加载器(APPClassLoader)
ClassLoader currentClassLoader = ClassLoaderExample.class.getClassLoader();
System.out.println("Current ClassLoader: " + currentClassLoader);
// 获取扩展类加载器(ExtClassLoader)
ClassLoader extensionClassLoader = currentClassLoader.getParent();
System.out.println("Extension ClassLoader: " + extensionClassLoader);
// 获取引导类加载器(Bootstrap ClassLoader)
ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
System.out.println("Bootstrap ClassLoader: " + bootstrapClassLoader);
}
}
想知道双亲委派机制肯定需要对源码有一些了解,否则只能靠背,具体源码如下:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
简单来说,双亲委派机制指的是当一个类需要被加载时,它的加载请求会被委托给它的父类加载器,父类加载器会先尝试加载这个类,如果加载成功就返回,如果加载失败则会将加载请求再委托给它的父类加载器,直到最顶层的启动类加载器(Bootstrap ClassLoader)。只有当最顶层的启动类加载器也无法加载时,才会由当前类加载器自己来进行加载.
使用双亲委派机制来加载类的好处是可以确保类的加载是由低层次的加载器向高层次的加载器进行委托,从而保证了类的唯一性和安全性。这样做可以避免出现java本地类被底层加载器加载的情况.
分为三大部分: 加载 -》 连接 -》 初始化 。
加载(Loading)是指将类的字节码文件加载到内存中,并在方法区创建一个代表该类的Class对象。加载过程由类加载器完成.
连接(Linking)分为三个阶段:验证(Verification)、准备(Preparation)和解析(Resolution).
验证(Verification):验证阶段主要对类的字节码进行验证,确保字节码的结构和语义是合法的。这个阶段主要包括以下几个方面的验证:文件格式验证、元数据验证、字节码验证和符号引用验证.
准备(Preparation):准备阶段是为类的静态变量分配内存空间,并设置默认初始值。这个阶段不会执行任何Java代码,只是简单地分配内存.
解析(Resolution):解析阶段是将类的符号引用替换为直接引用的过程。符号引用指的是用一组符号来描述所引用的目标,而直接引用是直接指向目标的指针、句柄或偏移量。解析阶段主要完成虚拟机对类、接口、字段和方法的解析.
初始化(Initialization)是类加载的最后一个阶段,主要是对类的静态变量进行赋值和执行静态代码块(实际上就是我们写的代码块)。初始化阶段是类加载的重要阶段,只有在初始化阶段才会真正执行类中的Java代码。初始化阶段由虚拟机自动触发,主要有两种情况:主动引用和被动引用。主动引用是指对类的主动使用,例如创建类的实例、访问类的静态变量和静态方法等。被动引用则是指对类的被动使用,不会触发类的初始化,例如通过子类引用父类的静态变量.
当一个对象从加载到JVM,再到被GC清除,它经历了以下过程:
加载:对象的类文件被加载到JVM中的方法区(也称为永久代或元空间),并在方法区中创建一个代表该类的Class对象.
申请空间:在对象生成之前,对象在堆内存中申请一块空间,对象的实例变量会被赋予默认初始值.
初始化:对象属性进行初始化.
连接:对象和栈中的引用建立连接,使得该对象可以被访问.
年龄划分:对象被分配到新生代的Eden区,并初始年龄为1。每个对象的年龄由对象头中的年龄标识位(通常是4位)表示,所以一个对象的最大年龄为15.
Minor GC:当新生代的Eden区空间不足时,会触发Minor GC。在Minor GC中,存活的对象会被复制到Survivor区域(通常是from区和to区),同时年龄会增加。经过多次复制和年龄增加后,对象会进入老年代.
Full GC:当老年代空间不足或者进行整体内存回收时,会触发Full GC。Full GC会对整个堆内存进行回收,包括新生代和老年代.
对象回收:经过GC后,不再被引用的对象会被GC清除,释放内存空间.
需要注意的是,当前方法结束,栈中的指针会先移除掉,当发生Full GC时,如果一个对象被回收,它的内存分配将会被清除,即该对象所占用的内存将被释放.
有两种确认方法,一是引用计数法,二是根可达性算法; 。
引用计数法:每当一个对象被引用一次,它的引用计数就会加1,直到引用计数变为0时,该对象就被判定为垃圾对象。这是JDK 1.4之前使用的算法,但它存在一个明显的问题,即当两个对象相互引用时,它们的引用计数永远不会变为0,导致无法回收这些对象,进而可能导致内存泄漏和内存溢出问题.
根可达性算法:根可达性算法是目前主要使用的算法。它基于一个简单的概念,即从一组称为"GC Roots"的根对象开始,通过一系列引用关系来判断对象是否可达。如果一个对象无法通过任何引用关系与GC Roots相连,那么该对象就被判定为垃圾对象。一旦确定了没有连接到GC Roots的对象,垃圾收集器就会回收这些对象.
GC Roots包括类的静态变量、常量池、class类以及方法栈中的变量。这些对象被认为是程序的起始点,通过它们可以追溯到所有其他对象的引用关系.
MarkSweep:标记清除算法,目的是将垃圾标记后,直接清楚垃圾,这样会导致产生过多的内存碎片,当分配大对象时,可能会导致full gc,又或者直接内存溢出.
Copying:拷贝算法,拷贝算法(Copying)牺牲了一半的内存空间,只使用其中一半进行分配。在标记存活对象后,将对象整体迁移至另一半内存空间,减少内存碎片,但牺牲了可使用空间.
MarkCompack:标记压缩算法,为了解决拷贝算法的缺陷,就提出了标记压缩算法。这种算法在标记阶段跟标记清除算法是一样的,但是在完成标记之后,不是直接清理垃圾内存,而是将存活对象往一端移动,然后将端边界以外的所有内存直接清除.
Serial: 单线程垃圾回收器,使用复制算法。主要适用于小型应用程序和单核处理器.
Serial Old: 老年代单线程垃圾回收器,使用标记-整理算法。适用于较小的应用程序和单核处理器,对于大型应用程序可能会导致停顿时间较长.
ParNew: 年轻代多线程垃圾回收器,使用复制算法。与Serial相比,ParNew可以利用多个线程进行垃圾回收,提高回收效率.
Parallel Scavenge: 年轻代多线程垃圾回收器,使用复制算法。目标是尽可能地减少垃圾收集的停顿时间,适用于对系统吞吐量要求较高的应用程序.
Parallel Old: 老年代多线程垃圾回收器,使用标记整理算法。与Serial Old相比,Parallel Old可以利用多个线程进行垃圾回收,提高回收效率.
CMS: 老年代多线程并发垃圾回收器,默认使用标记清除算法,可配置标记整理算法。CMS的目标是减少垃圾收集的停顿时间,适用于对响应时间要求较高的应用程序.
G1: 基于分代的垃圾回收器,已去除物理上的年轻代和老年代概念。使用region块来保存和分配内存,整体上使用标记整理算法,微观上使用复制算法。G1的目标是在有限的时间内获得可控制的停顿时间,适用于大型应用程序和对响应时间要求较高的应用程序.
STW(Stop The World)是指在垃圾回收过程中,所有应用程序的线程都会被暂停,只有垃圾回收线程在执行垃圾回收操作。这意味着在STW期间,应用程序无法继续执行任何任务,可能会导致一些延迟和性能问题.
减少STW时间是垃圾回收优化的一个重要目标。JVM的垃圾回收器会不断进行优化,以减少STW时间,使应用程序的暂停时间尽可能短。不同的垃圾回收器有不同的优化策略和算法,以满足不同场景下的需求.
抛开单线程和多线程单一停顿时间不看,只看下CMS和G1垃圾回收器 。
CMS:共分为初始标记,并发标记,重新标记,并发回收四个阶段;其中初始标记和重新标记将会进行STW,但是拉开了STW的战线,所以总的停顿时间缩小了,但是由于他是在跟工作线程同时进行回收,所以肯定会产生浮动垃圾; 。
G1:共分为初始标记,并发标记,重新标记,筛选回收四个阶段;和CMS逻辑相同,但是筛选回收将会进行计算,jvm会判断回收成本并执行回收计划,来优先回收哪些对象 。
三色标记是指将对象分为三个不同的颜色:白色、灰色和黑色。是CMS(Concurrent Mark Sweep)的标记算法 。
白色:表示对象未被访问过,也就是未被标记为存活对象.
灰色:表示对象已经被访问过,但它引用的其他对象还未被标记.
黑色:表示对象已经被访问过,并且它引用的其他对象也都被标记.
在并行标记阶段,CMS会先将根节点标记为灰色,然后并行地遍历对象引用,将引用的对象标记为灰色,并将其加入标记队列。当标记队列为空时,标记阶段结束.
然而,由于并行标记与应用程序执行是同时进行的,可能会导致在标记阶段结束后,仍然存在引用发生变化的情况,比如引用删除或引用转变。为了解决这个问题,CMS需要进行重新标记的过程。重新标记会遍历所有的灰色对象,并将它们标记为黑色。这样可以确保所有的引用关系都被正确地标记,并且不会错误地回收正在使用的对象.
JVM调优主要就是通过定制JVM运行参数来提高JAVA应用程度的运行数据 。
JVM参数大致可以分为三类:
JVM调优通常需要借助一些开发者工具来辅助。阿里开源的Arthas就是一款非常强大的Java诊断工具,它可以帮助开发人员进行实时的性能分析和问题排查.
Arthas具有丰富的功能,比如查看Java虚拟机的运行状态、监控方法执行时的参数和返回值、查看线程状态和运行时间、查看类加载和字节码等。它还支持在运行时修改类的方法体和实例状态,以及记录方法调用堆栈等功能.
使用Arthas,开发人员可以方便地发现性能瓶颈和问题,并进行针对性的优化。它在Java开发中非常受欢迎,尤其是在分布式系统和微服务架构中的性能调优中发挥了重要作用.
当然,除了Arthas,还有其他一些常用的JVM调优工具,比如VisualVM、JConsole、JProfiler等,开发人员可以根据自己的需要选择适合自己的工具来进行JVM调优.
官方文档地址: https://arthas.aliyun.com/doc/ 。
JVM调优确实不像开发中常见的可视化界面工具那样直观,而更多地需要基于底层的知识和经验来解决问题。JVM调优的确没有固定的定性规则,但可以根据一些常见的性能问题和优化思路来进行思考和回答.
在面试时,如果遇到JVM调优相关的问题,可以按照以下思路来回答:
首先,了解JVM的基本架构和垃圾回收机制。这包括堆、栈、方法区等内存结构,以及各种垃圾回收器的特点和工作原理.
掌握常见的性能问题和优化手段。例如,内存泄漏、频繁的Full GC、长时间的STW等问题,可以结合具体情况提出相应的解决方案.
熟悉一些性能监控和分析工具。如前面提到的Arthas、VisualVM、JConsole等,可以介绍自己使用过的工具,并举例说明如何利用这些工具进行性能分析和问题排查.
强调实践经验和解决问题的思路。虽然没有固定的定性规则,但可以根据自己的实践经验和理解,提出一些常见的优化思路和原则,比如减少对象的创建和销毁、合理配置内存参数、优化算法和数据结构等.
总之,在回答JVM调优相关的面试题时,除了记住一些常见的问题和解决方案,更重要的是展示出自己的思考和解决问题的能力.
最后此篇关于JVM调优篇:探索Java性能优化的必备种子面试题的文章就讲到这里了,如果你想了解更多关于JVM调优篇:探索Java性能优化的必备种子面试题的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
Sequelize 中有没有办法添加另一列,然后用另一列的内容填充它? 最佳答案 您可以使用迁移来做到这一点。 就像是 queryInterface.addColumn( 'MyAwesomeTa
如何计算info_hash参数?又名对应于信息字典的哈希?? 来自官方规范: info_hash The 20 byte sha1 hash of the bencoded form of the i
是否可以直接从数据库或服务等将 URL 获取到 Nutch。我对从数据库或服务获取数据并将其写入 Seed.txt 的方式不感兴趣. 最佳答案 没有。这不能直接使用默认的 nutch 代码库来完成。需
MessageDigest 类实现了 SHA-1 算法(以及许多其他算法)。 SHA-1 算法允许使用不同的“种子”或初始摘要。参见 SHA-1 Psuedocode 算法初始化变量,或种子: Ini
我想创建一个应用程序,其中登录密码可以作为伪随机数生成器的种子以重新创建加密 key 。然后,该加密 key 将用于加密发送到应用程序数据库和从应用程序数据库发送的所有数据,使用户数据甚至主机都无法访
这个问题在这里已经有了答案: Recommended way to initialize srand? (15 个答案) 关闭 8 年前。 使用 srand(time(NULL))似乎过于确定性。例
我在获取要在我的自定义数据库初始值设定项上调用的 Seed 方法时遇到问题。我正在使用 EF 5.0 并具有以下代码: public static class MyDatabase { pub
是否可以像在 Rails 中那样“播种”数据库?我想将种子与图像对象管理器结合使用,以便我可以按标题获取记录。 最佳答案 根据您对 Ingo 的回答留下的评论,您想将 requireDefaultRe
我现在设置了一个应用程序来使用 EF6 代码优先迁移。我使用 Add-Migration 的标准工作流程,然后在控制台中使用 Update-Database。我在本地以及我们的开发环境中使用 Migr
如果 Name 返回然后删除 first name Name john Age 30 Name Alice Name Travis Age 12 Name Monty Name Hannah 期望的输
在迁移完成后,是否可以在我的迁移中放入一些东西来自动为表播种测试数据? 或者您必须单独播种? 最佳答案 您可以使用 --seed 选项调用 migrate:refresh 以在迁移完成后自动播种: p
我正在尝试使用不同的种子生成 scipy.stats.pareto.rvs(b, loc=0, scale=1, size=1)。 在 numpy 中,我们可以使用 numpy.random.seed
我的种子有问题。这是我的表结构: 1.Complaints: Schema::create('complaints', function (Blueprint $table) {
我在使用数据库初始化程序时遇到问题 - 从未调用过种子方法。类似的代码在另一个项目中工作,所以我很困惑为什么他们这次不工作。 这是我的代码: RecipeContext.cs public c
我正在尝试做一些我认为非常简单的事情,只需使用 RAND 创建 0-1 之间的随机数,并将其分配给十进制变量。但每次我在 MySQL 中运行代码时,它都会返回零! 参见下面的代码: DELIMITER
我有一个问题...... 这里我们得到了一个二维字节数组: byte[][] duengonMap = new byte[500][500]; 因为我想将它从客户端发送到服务器或者相反,我需要将其放入
我尝试在我的计算机上运行 Angular-seed(Windows 10,上次更新)https://github.com/angular/angular-seed 。网络工作正常,但我的 Protra
我有一个随机过程的分布式过程。因此,我使用 numpy.random.RandomState 来播种数字。问题是我必须在包装器中使用另一个 numpy.random 函数。现在我失去了种子的再现性,因
我需要确保我程序中的所有随机性都是完全可复制的。我应该在哪里调用 random.seed()? 我认为它应该在我的 main.py 模块中,但它导入了碰巧使用随机函数的其他模块。 我可以仔细浏览我的导
首先尝试使用 Entity Framework 和代码在 ASP.NET 网络应用程序中植入数据。我将这段代码放在 Configuration.cs 文件的 Seed() 方法中。现在,我正在处理解决
我是一名优秀的程序员,十分优秀!