jvm学习日志

jvm学习日志_第1张图片

在jdk1.8中,方法区被替换成了内存中的元空间。
程序计数器:主要有两个作用:1.字节码解释器通过改变程序计数器来依次读取指令,实现流程控制,如顺序、循环、选择、异常处理等;2.在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而线程切换回来的时候知道线程运行到了何处。(生命周期与线程相同,不会出现oom)
虚拟机栈:生命周期与线程相同,描述的是java方法执行的内存模型,每次的方法调用的数据都是通过栈传递的。(每一个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息)(会出现爆栈和内存)(每一次方法调用就是入栈,执行完成就出栈)
本地方法栈主要就是虚拟机使用到的native(Object中的hashCode就是native修饰的方法)(会出现爆栈和内存)
:所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
这里是垃圾收集器的主要区域,因此也被叫做GC堆。一般分为新生代和老年代。新生代还可以被分为Eden、From Survivor、To Survivor;
大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。
几种oom的情况
OutOfMemoryError: GC Overhead Limit Exceeded : 当JVM花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
java.lang.OutOfMemoryError: Java heap space :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发(和本机物理内存无关,和你配置的内存大小有关!)
方法区:方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分。
常量池主要包括了:被final修饰的常量、文本字符串、基本数据类型的值等。

java对象创建的过程

jvm学习日志_第2张图片

Step1:类加载检查
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
Step2:分配内存
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。(当没有碎片时,使用的是指针碰撞;有碎片则使用空闲链表、有没有碎片通常取决于GC的算法用的是“标记-清除(有碎片)”、“标记-整理(无碎片)”、“复制算法”(无碎片))
利用cas+失败重试、或者TLAB(为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配)来保证线程安全
Step3:初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
Step4:设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
Step5:执行 init 方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

jvm学习日志_第3张图片

如何判断一个对象已经死亡(应该被回收)
1.引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。(实现简单、效率高,但是无法解决循环引用的问题。主流的虚拟机没有采用这种方式)
2.可达性分析算法
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

jvm学习日志_第4张图片

垃圾收集算法

1.标记-清除算法
该算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:一个是效率;一个是空间(会产生大量不连续的碎片)
2.复制算法
为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
3.标记-整理
根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
4.分代收集
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

常见的垃圾收集器

1.Serial收集器(串行)
单线程收集器,在工作时会暂停其他所有的线程(新生代采用复制算法,老年代采用标记-整理算法)
2.ParNew收集器(多线程版本的Serial)
3.ParallelScavenge收集器(提高吞吐量的ParNew)
4.Serial Old收集器(老年代版本的Serial)
5.Parallel Old收集器(老年代版本的Parallel)
6.CMS收集器(Concurrent Mark Sweep)(最短回收停顿时间为目标)
7.G1收集器
是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.

你可能感兴趣的:(java)