**前言:**JVM一般不会进行调优,性能不够时一般选择在集群中加机器或优化代码,此次培训会讲一些JVM的基础知识,及通过了解JVM的运行机制,结合后续的性能测试调整常用JVM参数、规范代码中全局变量、大对象、线程池等的使用,避免出现内存溢出的问题。
一、JVM概述
二、性能优化涉及的知识点
三、内存的分配和回收
1、内存分配的过程简述
对于Person person = new Person();:
**内存分配:**一旦 Person 类加载完成,JVM会在堆内存中为 Person 对象分配内存空间。这个内存空间的大小取决于 Person 对象的成员变量所占用的空间。//memory = allocate();
**对象初始化:**分配内存后,JVM会调用 Person 类的构造函数(如果有的话)来初始化 Person 对象。构造函数会对 Person 对象进行初始化,例如设置对象的初始状态和属性值。//instance(memory)
**对象引用:**最后,JVM会返回一个指向新创建的 Person 对象的引用,并将其赋给 person 变量。通过这个引用,我们可以通过 person 变量来访问和操作 Person 对象的属性和方法。//instance = memory
2、垃圾回收是什么?
Java虚拟机(JVM)的垃圾回收是一种自动管理内存的机制,用于回收不再使用的对象并释放它们占用的内存空间,从而避免内存泄漏和内存溢出问题。垃圾回收的主要目标是优化内存利用,提高系统性能,以及减少开发人员手动管理内存的工作量。
在Java中,垃圾回收器负责扫描堆内存中的对象,标记出不再被引用的对象,然后释放这些对象占用的内存空间。垃圾回收过程分为以下几个步骤:
标记(Marking):首先,垃圾回收器会从根对象(如全局变量、栈中的引用等)开始,递归地遍历所有可达对象,并标记为存活对象。
清除(Sweeping):标记完成后,垃圾回收器会扫描整个堆内存,清除所有未被标记的对象,即被标记为垃圾的对象。
回收(Collection):在清除阶段之后,垃圾回收器会将清除的内存空间进行整理,以便后续的内存分配。
3、回收哪些对象?
堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。
1)、引用计数器
给对象添加一个引用计数器,每当有一个地方引用它,计数器就加一,
当引用失效,计数器就减一,
任何时候计数器为0的对象就是不可能再被使用的。
这种方式实现简单,效率也高、但是目前主流的虚拟机中并没有选择这种算法,其主要原因是因为它很难解决对象之间相互循环引用的问题。
所谓的对象之间相互引用问题,可以参考下面代码来了解;
除了对象objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为0,于是引用计数算法无法通知 GC 回收器回收他们。
public class ReferenceCountingGc {
Object instance = null;
public static void main(String[] args) {
ReferenceCountingGc objA = new ReferenceCountingGc();
ReferenceCountingGc objB = new ReferenceCountingGc();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
}
}
2)、可达性分析算法
将“GC Roots” 对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象
4、回收的优先级?
强引用(Strong Reference):
-
强引用是最常见的引用类型,通常以 Object obj = new Object(); 的形式创建。
-
当存在强引用指向一个对象时,垃圾回收器不会回收该对象,即使内存不足也不会回收。只有当所有强引用都被移除时,对象才会被垃圾回收器回收。
软引用(Soft Reference):
-
软引用是比强引用弱一些的引用类型。可以通过 SoftReference 类来创建软引用。
-
当系统内存不足时,垃圾回收器会根据一定的策略来回收软引用指向的对象,以释放内存。但是,它会在释放之前尽量保证软引用指向的对象可以存活一段时间,这样就可以用于实现内存敏感的缓存。
弱引用(Weak Reference):
-
弱引用比软引用更弱一些,可以通过 WeakReference 类来创建。
-
垃圾回收器会在下一次垃圾回收时,无论内存是否充足,都会回收弱引用指向的对象。弱引用通常用于实现缓存或者监视对象是否已经被回收。
虚引用(Phantom Reference):
-
虚引用是最弱的引用类型,创建时需要传入一个引用队列(ReferenceQueue)。
-
虚引用无法通过 get() 方法获取引用的对象,主要用于跟踪对象被垃圾回收器回收的情况。当对象被回收时,虚引用会被放入引用队列中,通过监视引用队列可以知道对象何时被回收。
import java.lang.ref.*;
public class ReferenceExample {
public static void main(String[] args) {
// 创建对象
Object obj = new Object();
// 强引用
Object strongRef = obj;
// 软引用
SoftReference<Object> softRef = new SoftReference<>(obj);
// 弱引用
WeakReference<Object> weakRef = new WeakReference<>(obj);
// 虚引用
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, referenceQueue);
// 打印引用对象
System.out.println("Strong reference: " + strongRef);
System.out.println("Soft reference: " + softRef.get());
System.out.println("Weak reference: " + weakRef.get());
System.out.println("Phantom reference: " + phantomRef.get());
// 移除强引用
obj = null;
// 手动执行垃圾回收
System.gc();
// 检查引用队列中是否有虚引用
Reference<?> refFromQueue = referenceQueue.poll();
System.out.println("Reference from queue: " + refFromQueue);
}
}
##5、何时进行垃圾回收
**内存不足:**当堆内存中的对象达到一定阈值,且无法再分配新对象时(GC日志会有体现),垃圾回收器会启动垃圾回收来释放内存空间,以便给新对象分配内存。
**系统空闲时间:**当系统空闲时间足够长时,垃圾回收器可能会选择在这段时间内进行垃圾回收,以减少对系统性能的影响。
**显式调用:**虽然一般情况下不需要手动触发垃圾回收,但是可以通过调用System.gc()或Runtime.getRuntime().gc()来请求系统执行垃圾回收。但是,这只是一个建议,垃圾回收器是否真正执行垃圾回收取决于具体的实现和条件。
6、STW
STW(Stop-The-World)机制是指在垃圾回收过程中,Java应用程序的所有线程都会被暂停,以便垃圾回收器能够安全地执行其工作。STW机制的主要目的是确保垃圾回收器能够正确地管理内存,并且不会因为并发执行而导致数据不一致或错误。
STW机制通常发生在以下情况下:
Minor GC(新生代垃圾回收): 在新生代进行垃圾回收时,如果存活对象占用的空间超过了新生代的一定比例,就会触发Minor GC。在Minor GC期间,所有工作线程都会暂停,垃圾回收器会扫描新生代中的对象,并将不再使用的对象清除掉。
**Major GC/Full GC(老年代垃圾回收): **在老年代进行垃圾回收时,通常会触发Major GC或者Full GC。在Major GC或Full GC期间,除了新生代的对象,还会对整个堆内存进行回收。这时,所有工作线程都会被暂停,以确保堆内存中的所有对象都被垃圾回收器正确处理。
STW机制的实现对于垃圾回收器的正确执行至关重要,但它也会对应用程序的性能产生影响,特别是在处理大型内存或频繁发生垃圾回收的情况下。因此,优化垃圾回收器的性能以减少STW时间是提高应用程序整体性能的重要方面之一。
问题:为什么桌面程序不使用java编写?
##7、垃圾回收算法
详细可以参考:常见的几种垃圾回收算法
Java虚拟机中的垃圾回收器通常根据其算法和实现方式的不同,可以分为不同类型,例如:
新生代垃圾回收器:针对年轻代(新生代)进行垃圾回收,通常采用复制算法或者标记-复制算法。
老年代垃圾回收器:针对老年代进行垃圾回收,通常采用标记-清除算法或者标记-整理算法。
混合垃圾回收器:结合了新生代和老年代的垃圾回收策略,以实现更好的性能和效果。
标记-清除(Mark-Sweep)算法:
标记-清除算法是最基本的垃圾回收算法之一。它分为两个阶段:标记阶段和清除阶段。在标记阶段,垃圾收集器标记所有活动对象;在清除阶段,垃圾收集器清除所有未标记的对象。这种算法的缺点是会产生内存碎片。
复制(Copying)算法:
复制算法将堆内存划分为两个区域,一部分是活动对象存活的From空间,另一部分是To空间。当From空间满时,垃圾收集器将活动对象复制到To空间,并清除From空间中的所有对象。这种算法减少了内存碎片,但需要额外的空间用于复制对象。
标记-整理(Mark-Compact)算法:
标记-整理算法是标记-清除算法的改进版本。在标记阶段,垃圾收集器标记所有活动对象;在整理阶段,垃圾收集器将活动对象移动到堆的一端,并更新引用。这种算法消除了内存碎片,但需要移动对象,可能会影响性能。
分代(Generational)算法:
分代算法是根据对象的存活周期将堆内存划分为不同的代(Generation),一般分为年轻代(Young Generation)和老年代(Old Generation)。年轻代使用复制算法,老年代使用标记-整理算法。这种算法利用了大部分对象的短暂存活周期,提高了垃圾回收的效率。
增量(Incremental)算法:
增量算法将垃圾回收过程分解为多个阶段,并在每个阶段之间允许应用程序执行一些操作。这样可以减少垃圾回收造成的停顿时间,提高应用程序的响应性。增量算法通常与标记-清除或标记-整理算法结合使用。
并发(Concurrent)算法:
并发算法允许垃圾回收过程与应用程序同时执行,减少了垃圾回收造成的停顿时间。并发算法通常需要牺牲一些性能来换取更低的停顿时间,因为它们需要与应用程序竞争CPU和内存资源。
##8、垃圾回收器
Serial收集器:
- Serial收集器是一种单线程的垃圾回收器,主要用于新生代。它使用复制算法进行垃圾回收,简单高效,适合于单线程的应用和客户端应用。
Parallel收集器:
- Parallel收集器也称为多线程收集器,主要用于新生代和老年代。它使用复制算法和标记-整理算法进行垃圾回收,利用多个线程并行处理垃圾回收任务,提高了垃圾回收的吞吐量。
CMS收集器(Concurrent Mark-Sweep):
- CMS收集器是一种并发垃圾回收器,主要用于老年代。它使用标记-清除算法进行垃圾回收,在标记和清除阶段与应用程序并发执行,减少了停顿时间,适合于对停顿时间要求较高的应用。
G1收集器(Garbage-First):
- G1收集器是一种面向服务端应用的垃圾回收器,主要用于新生代和老年代。它使用分代算法和标记-整理算法进行垃圾回收,将堆内存划分为多个区域,并根据垃圾回收的目标选择回收区域,适合于对吞吐量和停顿时间都有要求的应用。
ZGC收集器(Z Garbage Collector):
- ZGC收集器是一种低延迟垃圾回收器,主要用于大内存应用。它使用并发算法和分代算法进行垃圾回收,与应用程序并发执行,减少了停顿时间,适合于对停顿时间要求非常高的应用。
Shenandoah收集器:
- Shenandoah收集器是一种低延迟垃圾回收器,主要用于大内存应用。它使用并发算法进行垃圾回收,在标记和清理阶段与应用程序并发执行,减少了停顿时间,适合于对停顿时间要求非常高的应用。
##9、内存泄漏的场景
- 静态集合类
public class StaticCollectionLeak {
private static List<Object> list = new ArrayList<>();
public static void main(String[] args) {
while (true) {
Object obj = new Object();
list.add(obj);
}
}
}
- 单例模式
public class SingletonLeak {
private static SingletonLeak instance;
private SingletonLeak() {}
public static SingletonLeak getInstance() {
if (instance == null) {
instance = new SingletonLeak();
}
return instance;
}
}
- 内部类持有外部类
public class OuterClass {
private class InnerClass {
// Inner class holding reference to outer class
private OuterClass outer;
public InnerClass(OuterClass outer) {
this.outer = outer;
}
}
}
- 各种连接
public class ConnectionLeak {
public static void main(String[] args) {
while (true) {
// Open database connection but don't close it
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "username", "password");
// Perform database operations...
}
}
}
- 变量不合理的作用域
public class ScopeLeak {
public static void main(String[] args) {
List<Object> list;
while (true) {
list = new ArrayList<>(); // Define list within the loop
// Populate and use the list...
}
}
}
- 缓存泄露
public class CacheLeak {
private static Map<String, Object> cache = new HashMap<>();
public static void main(String[] args) {
while (true) {
// Populate cache but never remove entries
String key = "key";
Object value = new Object();
cache.put(key, value);
}
}
}
- 监听和回调
public class ListenerLeak {
public interface Listener {
void onEvent();
}
public static class MyClass {
private Listener listener;
public void setListener(Listener listener) {
this.listener = listener;
}
}
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.setListener(() -> {
// Do something
});
// obj is no longer needed but listener is still holding a reference
}
}
- 队列长度过长
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10000));
四、性能监控工具
**JProfiler:**JProfiler是一个功能强大的Java应用性能分析工具,用于监视、分析和优化Java应用程序的性能。它提供了丰富的功能和可视化工具,帮助开发人员识别和解决性能瓶颈、内存泄漏等问题。
常用功能:本地分析、dump文件分析
五、相关技术
TLAB技术、栈上分配、逃逸分析和标量替换