1.JVM内存结构
1. 方法区(Method Area)
方法区是JVM内存结构的一部分,用于存放类的相关信息,包括:
- 类的结构(字段、方法、常量池等)。
- 字段和方法的描述,如名称、类型、访问修饰符等。
- 静态变量。
- Java虚拟机在运行时加载的类的信息。
方法区通常在堆区的外部,与堆区是分开的。因为主要存放的是类的信息和静态数据,所以这一部分数据的生命周期与类本身相同。
2. 堆(Heap)
堆是JVM中用于动态分配内存的区域,是对象和数组的存储区。堆内存是JVM运行过程中最大的内存区域,所有对象实例和数组都是在这个区域中分配的。堆区又可以分为几个部分:
- 新生代(Young Generation):用于存放新创建的对象,年轻代又可以分为三个部分:Eden空间和两个Survivor空间。新创建的对象首先在Eden区分配,经过垃圾回收后存活下来的对象会被移动到Survivor区。
- 老年代(Old Generation):用于存放长期存活的对象。经过多次垃圾回收,仍然存活的对象会被转移到老年代。
- 持久代(PermGen)/元空间(Metaspace):在旧版本的JVM中,方法区使用持久代来存储类元信息。在Java 8及其之后的版本中,持久代被元空间替代,主要用于存储类的元数据,不再属于Java虚拟机的堆内存,而是使用本地内存。
3. 虚拟机栈(VM Stack)
虚拟机栈用于存储局部变量、操作数栈、动态链接等信息。每个线程都有自己的虚拟机栈。栈是先进后出(LIFO)的结构,栈中的每一个栈帧(Stack Frame)对应一个方法的调用。在方法调用时,相关的局部变量、参数值以及方法返回地址等都会被推入栈中,而在方法返回时,栈帧会被弹出。
4. 本地方法栈(Native Method Stack)
本地方法栈用于支持JVM调用本地(Native)方法,类似于虚拟机栈。它存储的是本地方法的栈帧。不同的JVM实现可能会有所不同,但其功能主要是协助执行 Java 与其他语言(如C、C++)交互的本地方法。
5. 程序计数器(Program Counter Register)
程序计数器是一个较小的内存区域,保存着当前线程所执行的字节码的地址。它是线程私有的,每个线程都有一个程序计数器,以便在多线程环境中跟踪每个线程的执行点。可以理解为一个指针,指向当前正在执行的指令。
2.垃圾回收机制(GC)
垃圾回收算法
垃圾回收算法有多种,常见的有:
-
标记-清除(Mark-and-Sweep):
- 该算法分为两个阶段。第一阶段“标记”会遍历所有可达对象并标记它们,第二阶段“清除”会删除没有被标记的对象。尽管这个方法简单有效,但它可能导致内存碎片。
-
复制(Copying):
- 该算法将内存分为两个空间(通常称为“From”和“To”区域),在进行垃圾回收时复制所有活跃对象到另一个区域。复制后,原来的区域会被清空。该方法避免了内存碎片,但需要更多内存。
-
标记-整理(Mark-and-Compact):
- 该算法与标记-清除结合。首先,它会标记所有可达对象,然后将它们移动到一端,以消除内存碎片,并清理未使用的内存。
-
分代收集(Generational Collection):
- 根据对象的生命周期将内存分为若干代(通常为年轻代和老年代)。新创建的对象通常会在年轻代中变化较快,因此通过频繁回收年轻代来提高效率。而老年代中的对象较少变化,因此不需要频繁检查和回收。
JVM的垃圾回收机制:GC,是Java提供的对于内存自动回收的机制。
GC(Garbage Collection)是Java虚拟机(JVM)中的一项重要功能,用于自动管理堆内存中不再使用的对象,释放其占用的内存空间。GC通过标记和回收无效对象来实现内存的回收和释放,以避免内存泄漏和溢出。
下面是GC的主要工作原理和过程:
1、对象的标记:GC首先标记出所有活动对象,即仍然被引用或可达的对象。它从一组根对象开始,逐步遍历对象图,将可达的对象标记为活动对象,未标记的对象则被认为是无效的。
2、垃圾回收:在标记完成后,GC会对未标记的对象进行回收。具体的回收算法可以是不同的,常见的算法包括标记-复制算法、三色标记算法等。
3、内存压缩和整理:某些垃圾回收算法在回收完对象后,可能会产生内存碎片。为了优化内存使用,GC可能会进行内存压缩和整理操作,使得分配的对象在内存中连续存放,减少内存碎片的影响。
GC的优点是可以自动管理内存,减少了手动内存管理的复杂性,避免了内存泄漏和溢出的问题。但是,GC也会带来一定的性能开销。因此,在开发Java应用程序时,需要合理配置GC的参数和调整垃圾回收策略,以平衡性能和内存的使用。
3.JVM调优的方法?
1. 内存管理调优
-
堆内存大小设置:可以通过 (初始堆大小)和 (最大堆大小)参数来设置堆内存的大小。例如:。根据应用程序的需求来调整堆的大小。
-Xms
-Xmx
-Xms512m -Xmx2048m
-
选择合适的垃圾回收器:不同的垃圾回收器适用于不同的场景,例如:
- G1垃圾回收器():适合大堆内存和低延迟的应用。
-XX:+UseG1GC
- Parallel垃圾回收器():适合高吞吐量的应用。
-XX:+UseParallelGC
- CMS垃圾回收器():适合需要最小停顿的应用。
-XX:+UseConcMarkSweepGC
- ZGC和Shenandoah:适合需要极低延迟的应用,但需要JVM版本支持。
- G1垃圾回收器():适合大堆内存和低延迟的应用。
-
调整新生代和老年代的比例:可以通过 或 参数来调整新生代与老年代的比例,以优化对象的存活率和垃圾回收频率。
-XX:NewRatio
-XX:SurvivorRatio
2. 垃圾回收调优
-
影响GC频率和停顿时间的参数:
-XX:MaxGCPauseMillis
:设置最大GC停顿时间。GC会尝试满足这个时间限制。-XX:GCTimeRatio
:调整应用可用时间与GC时间的比例。
-
监控和分析:使用JVM提供的工具(如JVisualVM、JConsole、GC日志)来监控和分析垃圾回收的情况,识别GC问题。
3. JIT编译优化
-
开启Tiered Compilation:使用,可以提高启动性能。
-XX:+TieredCompilation
-
调整JIT编译行为:通过设置编译阈值,以控制何时将方法编译为机器代码。
-XX:CompileThreshold
4. 线程调优
-
线程栈大小:可以用参数设置每个线程的栈大小。例如:。适当调整以减少栈溢出和内存使用。
-Xss
-Xss1m
-
线程池管理:如果应用使用线程池,确保合理配置线程数,避免因过多线程上下文切换引起的性能问题。
5. 其他优化选项
-
类加载与内存优化:
- 使用 可保持对象引用的压缩,有助于减少堆内存的使用。
-XX:+UseCompressedOops
- 使用 可保持对象引用的压缩,有助于减少堆内存的使用。
-
禁用或调整assertions:在生产环境中,可以禁用不必要的断言以提高性能:。
-da
6. 性能监控与分析
-
使用JVM监控工具:如Java Mission Control(JMC)和Flight Recorder来分析应用性能,查找瓶颈。
-
Profiling:使用工具(如YourKit、VisualVM等)进行代码剖析,找到性能开销较大的热点代码。
7. 配置文件与启动参数
- 合理配置启动参数:根据具体的应用场景和服务器配置调整JVM的启动参数,比如开启JIT、选择合适的GC、调整堆内存大小等。
4.堆和栈的区别?
1. 结构和用途:
-
栈(Stack):
- 栈是一种先进后出(LIFO, Last In First Out)的数据结构。
- 用于存储局部变量、函数参数和函数调用信息等。在函数调用时,函数的局部变量会压入栈中,函数返回时会将这些变量从栈中弹出。
- 每个线程都有自己的栈,用于跟踪函数调用和局部变量。
-
堆(Heap):
- 堆是一种基于动态存储分配的内存区域,没有特定的结构(可以认为是自由存储区)。
- 用于存储动态分配的对象和数据,如使用 关键字在C++中分配的对象,或在Java中创建的对象。
new
- 堆内存可以由程序员直接管理,程序员需要手动申请和释放内存,或者由垃圾回收机制(如Java、C#)自动管理。
2. 存储管理:
-
栈:
- 存储在栈内存中的数据管理简单,入栈和出栈操作的时间复杂度都是O(1)。
- 由于栈的大小是固定的(通常由操作系统限定),栈溢出(Stack Overflow)可能导致程序崩溃。
-
堆:
- 堆内存的大小通常只受到系统内存的限制,算法的复杂性和速度依赖于内存分配和回收的实现。
- 堆内存分配需要更多的时间,且容易出现内存泄漏或内存碎片问题。
3. 生命周期:
-
栈:
- 数据的生命周期由函数调用决定。函数结束后,栈上的数据会自动释放。
-
堆:
- 数据的生命周期由程序员控制,只有在不再需要对象时,才能显式地释放空间。如果没有释放,则会造成内存泄漏。
4. 访问速度:
-
栈:
- 由于其结构简单,内存访问速度较快,通常会比堆更快。
-
堆:
- 由于堆内存的管理要复杂得多,非顺序访问可能导致较慢的访问速度。
5.JVM使用命令
jvm 的一些命令_jvm命令-CSDN博客
jinfo
描述:输出给定 java 进程所有的配置信息。包括 java 系统属性和 jvm 命令行标记等。
jstat
jstat -gcutil <pid> <interval>
查看java进程的gc情况
以百分比显示每个区域的内存使用情况;
参数interval表示每多少毫秒刷新一次
6.class.forName和Classload的区别
1.相同点
两者都可以对类进行加载。
对于任意一个类/对象,我们都能够通过反射能够调用这个类的所有属性方法。
2.不同点
抽象类ClassLoader中实现的方法loadClass,loadClass只是加载,不会解析更不会初始化所反射的类。常用于做懒加载,提高加载速度,使用的时候再通过.newInstance()真正去初始化类。
7.谈谈JDK1.8特性有哪些
Lambda表达式 类似于ES6中的箭头函数
接口的默认方法和静态方法
新增方法引用格式
新增Stream类
新的日期API,Datetime,更方便对日期的操作
引入Optional,在SpringData中使用较多,然后再通过get获取值,主要用于防止NPE。
支持Base64
注解相关的改变
支持并行(parallel)数组
对并发类(Concurrency)的扩展。
JavaFX。
JDK1.8常用新特性_jdk1.8的新特性-CSDN博客
8.反射获取类中的所有方法和获取类中的所有属性
9.懒汉和饿汉模式的区别?口述两种模式。
在饿汉式单例模式中,“饿” 体现的是一种急切的状态。就好像一个很饿的人,在看到食物(这里类比于单例对象)的时候,会迫不及待地先把食物拿到手(创建单例对象)。在这个模式下,单例对象在类加载阶段就被创建出来,而不是等到真正需要使用这个对象的时候才去创建。这种方式比较急切,所以被称为 “饿汉模式”。
在懒汉模式下,实例在第一次使用时才进行创建,因此称为“懒汉”,在需要被用的时候被创建,突出一个字“懒”
10.如何保证单例模式在多线程中的线程安全
私有构造函数:通过将构造函数设置为私有,我们禁止了外部类直接实例化Singleton类。这确保了只能通过getInstance()方法来获取实例。
volatile关键字:volatile关键字确保了instance变量的可见性。当一个线程修改了一个volatile变量的值时,其他线程能够立即看到这个变化。这样可以避免线程之间的缓存不一致问题。
双重检查锁定(Double-Checked Locking):为了减少同步开销,我们在第一次检查instance是否为空后,才进入同步块。这是因为大多数情况下,实例已经被创建,不需要进入同步块。只有在第一次创建实例时才会需要同步。
同步块:在同步块内部,我们再次检查instance是否为空,以确保只有一个线程能够创建实例。这是为了防止多个线程同时通过了第一个空检查并尝试创建多个实例。