JVM学习第一章
JVM学习第一章
- 为什么要学习JVM
- 运行时数据区
- 程序记数器
- Java虚拟机栈
- 本地方法栈
- Java堆(Java Heap 、GC堆)
- 相关参数
- 方法区(Non-Heap "非堆")
- 运行时常量池
- 直接内存
- 如何判断对象是否可以回收
- 引用计数法
- 引用
- 可达性分析算法
为什么要学习JVM
从网上了解的信息和问ai,总结来看有这些原因:
- 就是因为java和C、C++不一样,可以自主进行内存管理,而是由JVM进行,平时来看简化了程序员的编码工作,但是一旦出现了内存相关的问题,内存溢出、内存泄漏,就需要程序员可以根据jvm报错日志找到问题根源所在
- 可以通过设置jvm参数,提高性能。
- 现代框架都深度依赖JVM特性
运行时数据区
运行时数据区,即JVM运行时对内存的管理模型。

程序记数器
- 线程私有
- 是一块很小的内存,也是唯一一个没有被定义任何OOM的地方
- 可以看作字节码解释器的行号指示器,字节码指示器就是通过程序计数器的值来判断下一条要执行的字节码。
- jvm的多线程是通过多个线程轮流切换、分配处理器时间来实现的,同一时间单个处理器只能处理单个线程的一条字节码指令,因此保证当切换到当前线程时可以知道正确的位置,所以程序计数器时线程私有的。
- 对于Java方法,程序计数器记录的是虚拟机字节码指令的地址,对于Native方法,值为空(Undefined)
Java虚拟机栈
- 线程私有
- 描述的是java方法执行时的内存模型。
- 每当方法执行的时候,Java虚拟机栈都会同步创建一个栈帧(Stack Frame),存储:局部变量表、方法出口、操作数栈、动态连接等信息。
- 每一个方法被调用到执行完成,就对应一个栈帧从入栈到出战的过程。
- 在《java虚拟机规范》中定义了两种异常,当请求深度超过当前Java虚拟机栈所允许的最大深度,就会抛出StackOverFlowError;如果Java虚拟机栈可以动态扩展,当栈扩展时无法申请到足够的内存时,就会抛出OOM。
本地方法栈
- 和Java虚拟机栈类似,只不过描述对象从Java方法编程了本地方法。
Java堆(Java Heap 、GC堆)
- JVM管理的内存的最大的一块。
- 线程共享
- 唯一目的就是存放对象实例
- 在《Java虚拟机规范》中:所有的对象实例和数组都应在java堆上分配内存
- 如果从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。
- 从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆中经常会出现“新生代”“老年代”“永久代”“Eden空间”“From Survivor空间”“To Survivor空间”等名词 (还没有学,就直接复制粘贴了)
- 如果在Java堆中没有内存完成实例分配,并且堆也无法再 扩展时,Java虚拟机将会抛出OutOfMemoryError异常。
相关参数
- -Xms : 初始堆大小
- -Xmn : 新生代大小
- -Xmx :最大堆大小
- -XX : NewRatio 新生代与老生代的容量比值 和 -Xmn二选一使用
- -XX:SurvivorRatio 新生代中Eden区与Survivor区容量比值
方法区(Non-Heap “非堆”)
- 线程共享
- 它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
- JDK8中,使用Meta-space 来代替,存放在本地内存(Native Memory)
运行时常量池
- 是方法区的一部分
- 用于存放Class文件中的常量池表,常量池表是存放编译期生成的各种字面量和符号引用。
直接内存
- 不是java虚拟机运行时数据区的一部分
- 在JDK1.4中加入了一个NIO类,基于通道和缓充区的输入输出方式,可以使用Native函数库直接分配堆外内存,,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
如何判断对象是否可以回收
引用计数法和可达性分析算法
引用计数法
- 在对象中添加一个引用记数器,每当有一个地方引用它时,记数器就加一,当引用失效时,计数器的值就减一;任何时刻,记数器为0的对象就是不可能在被使用的。
- 缺点:当两个对象彼此互相引用时,导致他们的计数器都不为0,这个方法也就无法回收他们。
主流的jvm都没有选用这个。
测试代码:
gvm参数:
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps
-Xloggc:gc.log
public class TestGC {public Object instance = null;private static final int _1MB = 1024 * 1024;/*** 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过*/private byte[] bigSize = new byte[2 * _1MB];public static void main(String[] args) {TestGC objA = new TestGC();TestGC objB = new TestGC();objA.instance = objB;objB.instance = objA;System.out.println("建立循环引用");objA = null;objB = null;System.out.println("将objA和objB设置为null");System.out.println("开始强制GC...");// 假设在这行发生GC,objA和objB是否能被回收?System.gc();// 给GC一些时间执行try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("程序执行完毕");}@Overrideprotected void finalize() throws Throwable {System.out.println("对象被GC回收: " + this);}}
生成的日志:

对于日志的解读问了ai,后续看完在说吧。
- 年轻代GC成功回收了大部分对象:年轻代从11898K减少到5000K,回收了约6.8MB内存
- Full GC进一步清理:年轻代完全清空(5000K->0K),老年代有对象晋升(8K->4725K)
- 最终堆使用量:4725K,这主要是JVM运行时必需的对象
引用
- 强引用
- 强引用是最常见的引用类型,我们平时使用的普通对象引用就是强引用。只要强引用存在,垃圾回收器就永远不会回收掉被引用的对象。
public class StrongReferenceExample {public static void main(String[] args) {// 创建强引用byte[] strongRef = new byte[1024 * 1024]; // 1MB对象System.out.println("创建强引用: " + strongRef);// 只要strongRef变量在作用域内,对象就不会被GC回收// 即使内存不足,JVM宁愿抛出OOM也不会回收强引用对象strongRef = null; // 只有显式置为null,对象才可能被GC回收System.gc(); // 建议GC,此时对象可能被回收System.out.println("强引用已置为null");}
}
- 软引用
- 软引用用来描述一些还有用但非必需的对象。在内存足够时,软引用对象不会被回收;只有在内存不足时,系统才会回收软引用对象。
SoftReference<Object> softRef = new SoftReference<>(new Object());
- 弱引用
- 弱引用比软引用的生命周期更短。在垃圾回收器线程扫描内存区域时,一旦发现弱引用对象,不管当前内存是否足够,都会回收弱引用对象。
WeakReference<Object> weakRef = new WeakReference<>(new Object());
- 虚引用
- 虚引用也称为幽灵引用或幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。虚引用主要用来跟踪对象被垃圾回收的活动。
- 终结器引用
- 终结器引用是用于实现对象的finalize()方法的。每个被声明了finalize()方法的对象都会在创建时被一个终结器引用所引用。当垃圾回收器发现一个对象只有终结器引用时,会将其加入一个引用队列(Finalizer Queue),然后由一条低优先级的线程(Finalizer线程)来执行这些对象的finalize()方法。在执行完finalize()方法后,对象就可以被回收了。
- 终结器引用由JVM内部使用,程序员无法直接使用。
- 引用队列(ReferenceQueue)
- 引用队列可以与软引用、弱引用和虚引用一起使用。当垃圾回收器准备回收一个对象时,如果发现它还有软引用、弱引用或虚引用,并且这些引用关联了引用队列,那么垃圾回收器就会在回收对象之前,将这些引用加入到引用队列中。程序可以通过判断引用队列中是否有引用来了解被引用的对象是否即将被回收。
可达性分析算法
- 就是通过一系列的“GC Roots”对象,根据引用关系向下搜索,搜索过程走过的路径成为引用链,如果某个对象到GC roots 没有任何引用链,也就是不可到达,则证明此对象时不可能被引用的。
- 可以成为GC roots对象:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆、栈中使用到的参数、局部变量、临时变量等。
- 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
- 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
- 所有被同步锁(synchronized关键字)持有的对象。
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
