大家好,今天和大家一起分享一下JVM相关内容~
JVM 内存模型
Java 虚拟机(JVM)的内存模型是 Java 应用程序运行时数据区域的抽象,它描述了 JVM 如何管理和使用内存。JVM 的内存模型主要由以下几个部分组成:程序计数器、Java 虚拟机栈、本地方法栈、堆、方法区。
1. 程序计数器(Program Counter Register)
- 作用:记录当前线程所执行的字节码指令的地址。如果正在执行的是一个本地方法,则程序计数器的值为未定义。
- 特点:每个线程都有一个独立的程序计数器,它是线程私有的。
- 内存消耗:非常小,通常可以忽略不计。
2. Java 虚拟机栈(Java Virtual Machine Stacks)
- 作用:存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法在执行的同时都会创建一个栈帧用于存储这些信息。
- 特点:每个线程都有一个私有的栈,生命周期与线程相同。栈帧随着方法的调用而创建,随着方法的结束而销毁。
- 异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存会抛出 OutOfMemoryError。
3. 本地方法栈(Native Method Stack)
- 作用:与 Java 虚拟机栈类似,但是为虚拟机使用的 Native 方法服务。
- 特点:也是每个线程私有的,异常情况与 Java 虚拟机栈类似。
4. 堆(Heap)
- 作用:存放对象实例,几乎所有的对象实例都在这里分配内存。
- 特点:是所有线程共享的一块内存区域,是垃圾回收的主要场所。
- 异常:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError。
5. 方法区(Method Area)
- 作用:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 特点:是各个线程共享的内存区域。在 HotSpot 虚拟机中,方法区也被称为“永久代”(Permanent Generation),但在 JDK 8 及以上版本中,永久代被元空间(Metaspace)取代。
- 异常:如果方法区无法满足新的内存分配请求,将会抛出 OutOfMemoryError。
元空间(Metaspace)
- 作用:从 JDK 8 开始,方法区被元空间替代。元空间与永久代最大的区别在于,元空间并不在虚拟机中,而是使用本地内存。
- 特点:理论上,只要本地内存足够大,元空间就不会出现溢出的问题。
内存管理与垃圾回收
JVM 的内存管理主要包括对象的创建和垃圾回收两个方面。对象的创建在堆中进行,而垃圾回收则是为了回收不再使用的对象所占用的内存,从而避免内存泄漏和提高程序性能。JVM 提供了多种垃圾回收算法,如标记-清除、复制、标记-整理等,不同的垃圾回收器(如 Serial、Parallel、CMS 和 G1)采用了不同的算法组合来优化内存管理和性能。
JVM 常用命令
Java 虚拟机(JVM)提供了一系列命令行工具,用于监控、调试和优化 Java 应用程序。以下是一些常用的 JVM 命令及其功能介绍:
1. jps - Java Virtual Machine Process Status Tool
- 用途:列出当前系统中所有 Java 进程的进程 ID(PID)和主类名称。
- 常用选项:
-
- -l:显示完整的包名和主类名。
- -m:显示传递给主类的参数。
- -q:仅显示 PID。
- -v:显示传递给 JVM 的参数。
示例:
jps -l
输出:
12345 com.example.MyApplication
67890 sun.tools.jps.Jps
2. jstat - Java Virtual Machine Statistics Monitoring Tool
- 用途:监控 JVM 的性能统计信息,如垃圾回收、类加载、编译等。
- 常用选项:
-
- -gc:显示垃圾回收相关的统计信息。
- -gccapacity:显示垃圾回收容量信息。
- -gccause:显示最后一次和当前垃圾回收的原因。
- -class:显示类加载统计信息。
- -compiler:显示 JIT 编译器统计信息。
示例:
jstat -gc 12345 1000 5
输出:
S0C S1C S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
32768.0 32768.0 0.0 262144.0 53248.0 2097152.0 344064.0 262144.0 255616.0 32768.0 31808.0 24 0.024 0 0.000 0.024
3. jinfo - Java Configuration Info Tool
- 用途:查看和修改目标 JVM 的配置信息。
- 常用选项:
-
- -flag :显示指定的 JVM 标志。
- -flag [+|-]:启用或禁用指定的 JVM 标志。
- -sysprops:显示系统的属性。
示例:
jinfo -flag UseConcMarkSweepGC 12345
输出:
-XX:UseConcMarkSweepGC=false
4. jmap - Memory Map for Java
- 用途:生成堆转储快照(heap dump)和显示堆内存的详细信息。
- 常用选项:
-
- -heap:显示堆内存的详细信息。
- -histo:显示堆中对象的直方图。
- -dump:format=b,file=:生成堆转储快照文件。
示例:
jmap -histo 12345
输出:
num #instances #bytes class name
1: 12345 1234560 java.lang.String
2: 5678 567800 java.util.HashMap$Node
5. jstack - Java Stack Trace
- 用途:生成 Java 进程的线程堆栈跟踪。
- 常用选项:
-
- -l:长列表,显示锁信息。
- -m:显示 Java 和本机框架。
示例:
jstack -l 12345
输出:
2023-10-10 12:34:56
Full thread dump OpenJDK 64-Bit Server VM (25.212-b04 mixed mode):
"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007f8a0c001000 nid=0x7f8a0c001000 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"main" #1 prio=5 os_prio=0 tid=0x00007f8a0c009800 nid=0x7f8a0c009800 runnable [0x00007f8a1d5e5000]
java.lang.Thread.State: RUNNABLE
at com.example.MyApplication.main(MyApplication.java:10)
6. jconsole - Java Monitoring and Management Console
- 用途:图形化工具,用于监控和管理 Java 应用程序的性能。
- 功能:
-
- 显示内存使用情况、线程状态、类加载等信息。
- 可以连接到本地或远程的 JVM。
示例:
jconsole
打开图形界面后,选择要监控的 Java 进程即可。
7. jvisualvm - VisualVM
- 用途:图形化工具,集成了多个监控和故障排除工具的功能。
- 功能:
-
- 监控内存、CPU 使用情况。
- 分析堆转储文件。
- 查看线程堆栈跟踪。
- 性能分析(Profiling)。
示例:
jvisualvm
打开图形界面后,选择要监控的 Java 进程即可。
java内存泄漏
什么是内存泄漏
内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。
内存泄漏的例子
1、集合类引起内存泄露
Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}
在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。
2、各种连接
比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。
3、单例模式
不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露。
class A{
public A(){
B.getInstance().setA(this);
}
....
}
//B类采用单例模式
class B{
private A a;
private static B instance=new B();
public B(){}
public static B getInstance(){
return instance;
}
public void setA(A a){
this.a=a;
}
//getter... }
显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。