java 代码执行过程
● 1.用javac代码编译为class
● 2.装载class ClassLoader
● 3.执行class,包括解释执行和编译执行
内存管理
jvm 内存区域
程序计数器(线程私有)
空间相对比较小,为数不多不会发送OutofMemoryError,如果正在执行java方法的话,计数器记录的是虚拟机字节码指令(当前指令的地址),如果是native的方法,则为空
虚拟栈(线程私有)
是描述 java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成 的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异 常)都算作方法结束。
本地方法区(线程私有)
本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为 Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个 C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。
堆(Heap-线程共享)-运行时数据区
是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行 垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以 细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代
方法区/永久代(线程共享)
即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静 态变量、即时编译器编译后的代码等数据. HotSpot VM 把 GC 分代收集扩展至方法区, 即使用 Java 堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型 的卸载, 因此收益一般很小)。
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版 本、字段、方法、接口等描述等信息外,还有一项信息是常量池。
用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加 载后存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每一部分(自然也包括常量 池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会 被虚拟机认可、装载和执行。
注意:
在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间 的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用 本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由 MaxPermSize 控制, 而由系统的实际可用空间来控制。
内存空间
● 方法区
● 堆
● 方法栈
● 本地方法栈
● pc寄存器
内存分配
● 堆上分配
● TLAB(Thread Local Allocation Buffer)分配 https://blog.csdn.net/xiaomingdetianxia/article/details/77688945
● 栈上分配
内存回收
内存回收算法
● copy(复制算法)
● mark-Sweep(标记清除算法)
● mark-compact(标记整理算法)
● 分代回收
○ 新生代可用的GC
■ 串行Copying
■ 并行回收Copying
■ 并行Copying
○ 旧生代可用的GC
■ 串行Mark-Sweep-Compact
■ 并行Compacting
■ 并发mark-sweep
内存状况分析
● jconsole
● visualvm
● jstat
● jmap
● MAT
线程同步和交互机制
● 线程资源同步
○ 线程资源执行机制
○ 线程资源同步机制
■ Synchronized
■ lock/unlock的实现机制
○ 线程交互机制
■ Object.wait/notify/notifyAll
■ 并发提供的交互机制
● semaphore
● counteddownlatch
○ 线程状态以及分析方法
■ jstack
■ TDA
JVM 内存区域
线程私有(thread local)
● 程序计数器PC 指向虚拟机字节指令的位置,唯一一个无OOM的区域
● 虚拟机VM stack
○ 虚拟机栈和线程的生命周期
○ 一个线程中,每调用一个方法创建一个栈帧(Stack Frame)
○ 栈帧的结构
■ 本地变量表 Local Variable
■ 操作数栈 operand stack
■ 对运行时常量池的引用(Runtime Constant Pool Reference)
● 异常
○ 线程请求的栈深度大于JVM所允许的深度StackOverflow
○ 若JVM允许动态扩展,若无法申请到足够内存(OutOfMemoryError)
● 本地方法栈异常
○ 线程请求的栈深度大于JVM所允许的深度StackOverflow
○ 若JVM允许动态扩展,若无法申请到足够内存(OutOfMemoryError)
线程共享(thread shared)
● 方法区(永久代)Method Area 运行时常量池 Runtime Constant Pool
● 类实例区(Java堆)Objects
● 直接内存(Direct Memory) 不受JVM GC管理
Java堆从GC角度可以细分为新生代(Eden区、From Survior区和To Survior区)和老年代
● Eden 区
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老 年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行 一次垃圾回收。
● ServivorFrom
上一次 GC 的幸存者,作为这一次 GC 的被扫描者
● ServivorTo
保留了一次 MinorGC 过程中的幸存者
永久代
指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被 放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这 也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间 的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用 本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由 MaxPermSize 控制, 而由系统的实际可用空间来控制。
如何确定垃圾
引用计数法
在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单 的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关 联的引用,即他们的引用计数都不为 0,则说明对象不太可能再被用到,那么这个对象就是可回收 对象。
注意这种有 循环引用的风险。
可达性分析
为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”
对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。
要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记 过程。两次标记后仍然是可回收对象,则将面临回收。
可作为GCRoot的对象包括以下几种:
● 虚拟机栈(栈帧中的本地变量表)
● 方法区类静态属性引用的对象
● 方法中常量引用的对象
● 本地方法栈中的JNI(Native方法)引用的对象
再谈引用
● 强引用
Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引 用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即 使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因。
● 软引用
软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它
不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。
● 弱引用
弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象
来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。
● 虚引用
虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚 引用的主要作用是跟踪对象被垃圾回收的状态。
新生代与复制算法
目前大部分 JVM 的 GC 对于新生代都采取 Copying 算法,因为新生代中每次垃圾回收都要 回收大部分对象,即要复制的操作比较少,但通常并不是按照 1:1 来划分新生代。一般将新生代 划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space)。
老年代与标记复制算法
老年代因为每次只回收少量对象,因而采用 Mark-Compact 算法。
● Java虚拟机提到过处于方法区的永生代,用来存储class类,常量,方法描述(待检查)
● 对象的内存分配主要在新生代的Eden Space和Survivor Space的From Space(Survivor目前存放对象的那一块),少数情况会直接分配到老生代
● 当新生代Eden Space和From Space空间不足就会发生一次GC,进行GC后,Eden Space和From Space区的存活对象就会挪到To Space,然后将Eden Space和From Space进行清理
● 如果ToSpace无法足够存储某个对象,则将这个对象存储到老生代
● 进行GC后,使用的便是Eden Space和To Space了,如何反复循环。
● 当对象Survior区躲过一次GC后,其年龄就会+1,默认情况下年龄达到15对象就会移到老生代中。
分区GC算法
分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做好处是可以控制一次回收多少小区间根据目标停顿时间, 每次合理地回收若干个小区间(而不是整个堆), 从而减少一次 GC 所产生的停顿。
GC什么时候回收内存
即使在可达性分析算法中不可达的对象,并不是马上回收,而是进入“缓刑阶段”。至少要经过两次标记。
java 虚拟机的组成
Java虚拟机体系运作顺序:
class文件通过类装载子系统将信息存入运行时数据区(包括方法区、堆、Java栈、程序计数器、本地方法栈),执行引擎通过一定的规范去解释执行。本地方法能通过执行引擎也能自己去调用运行时数据区。