JVM
一、Java内存区域划分
1.堆内存
用于存放对象实例,被所有线程共享。JVM动态分配内存空间,生命周期由Java虚拟机的垃圾收集器管理。
2.栈内存
用于存放基本数据类型和对应引用变量,每个线程都有一个独立的栈空间,超过变量的作用域时会自动释放内存空间。
3.本地方法栈
本地方法栈与栈内存的作用非常相似,区别在于本地方法栈只为native方法服务。
4.方法区
用于存放类信息、静态变量、常量池、编译后的代码数据,所有线程共享。
5.程序计数器
用于引导字节码解析顺序(如分支、循环、跳转、异常处理),线程私有。
二、垃圾收集器与内存分配策略
垃圾收集(Garbage Collection,GC)
栈内存、本地方法栈、程序计数器的生命周期和线程一致,当方法结束或线程结束时,内存就回收了。而堆内存和方法区则不一样,一个方法中多个分支需要的内存可能不一样,只有在程序处于运行期间才知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的就是这部分内存。
垃圾收集算法
1.标记-清除
通过可达性分析算法标记出所有需要回收的对象,然后清除。该算法效率不高,而且会产生内存碎片。
2.复制
将可用内存分为相等的两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另一块内存上面,然后再把已经使用过的内存空间清理掉。
3.标记-整理
通过可达性分析算法标记出所有需要回收的对象,然后清除。清除之后将存活的对象向一端移动。
4.分代回收
一般把堆内存分为新生代和老年代,新生代使用复制算法、老年代使用标记-整理算法。
垃圾收集器
1.Serial
复制。
2.ParNew
复制。
3.Parallel Scavenge
复制。
4.Serial Old
标记-整理。
5.Parallel Old
标记-整理。Java6默认收集器。
6.CMS
标记-清除。已在Java14中删除。整个过程分为4步:初始标记--并发标记--重新标记--并发清除。特点是并发、低停顿。
7.G1
分代回收。Java7中引入,Java9默认收集器。特点是并行和并发,能利用多CPU、多核环境下的硬件优势、空间整合。
8.ZGC
标记-整理。Java11中引入。特点:基于Region内存布局,不分代,使用了读屏障、颜色指针等技术来实现可并发的标记-整理算法。
如何标记?
1.引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1;当引用失效时,计数器减1。当计数器为0时,表示对象不可能再被引用。该算法实现简单,判断效率高,缺点是无法解决对象相互引用的问题。
2.可达性分析算法
在主流JVM中都是通过可达性分析来判断对象被引用。通过一系列被称为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径被称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用。
可作为GC Roots的对象包括下面几种:
栈中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI引用的对象。
什么时候回收?
在CPU空闲或内存不足时回收。
内存分配策略
1.大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
2.可配置-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样可以避免在Eden区及两个Survivor区之间发生大量的内存复制。
3.
三、虚拟机性能监控与故障处理工具
1.jdk的命令行工具
jps 显示虚拟机的进程状况
jstat 显示虚拟机中各种运行状态信息,如类装载、内存、垃圾收集、JIT编译等
jinfo 显示虚拟机的配置信息
jmap 用于生成堆转储快照(一般称为heapdump或dump文件)
jhat 与jmap搭配使用,来分析jmap生成的堆转储快照
jstack 用于生成当前线程正在执行的方法中堆栈的集合
2.jdk的可视化工具
2.1.JConsole:Java监视与管理控制台
2.2.VisualVM:多合一故障处理工具
四、调优案例分析
五、类文件结构
计算机只认识0和1,所以我们写的程序需要经编译器翻译成由0和1构成的二进制格式才能由计算机执行。
六、虚拟机类加载机制
虚拟机把class文件加载到内存,并对数据进行验证、准备、解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
类的生命周期:加载、验证、准备、解析、初始化、使用、卸载。
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,而解析阶段则不一定,它在某些特定情况下可以在初始化之后再开始。
类加载的过程
类加载的过程包括加载、验证、准备、解析和初始化5个阶段。
1.加载
加载阶段,虚拟机需要完成3件事:
a.通过一个类的全限定名来获取定义此类的二进制字节流。
b.将这个字节流代表的静态存储结构转化为方法区的运行时数据结构。
c.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。
2.验证
验证的目的是为了确保Class文件中字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。
3.准备
准备阶段会在方法区给类变量分配内存并设置初始值,其中类变量的初始值为0,常量的初始值为程序设定的值。实例变量将会在对象实例化时随着对象一起分配在Java堆中。
4.解析
解析阶段是虚拟机将常量池中的类、方法、属性替换为可直接引用的过程。
5.初始化
初始化类变量,执行构造器。
虚拟机规范规定了5种情况必须立即对类进行初始化(而加载、验证、准备自然在此之前执行)
1.new操作。
2.反射。
3.当初始化一个类时,如果该类的父类没有进行初始化,则先触发父类的初始化。
4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类)。
5.JDK1.7,java.lang.invoke.MethodHandle。
类与类加载器
类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。