1.java源文件-》编译器-》字节码文件;
2. 字节码文件-》jvm-》机器码(见上图)
3.每一个平台的解释器是不同的,但是实现的虚拟机是相同的,这是java能够跨平台的原因。
4.当一个程序从开始运行,虚拟机就开始实例化,多个程序启动的话就会存在多个虚拟机实例。程序退出或者关闭,虚拟机实例会消失,多个虚拟机实例之间的数据不能共享。
1.运行时数据区
2.直接内存
3.运行时数据区主要内容
存放对象实例与数组
存放局部变量
Java 栈内存空间用于执行线程, 栈内存始终遵循LIFO(Last-in-first-out) 顺序, 每当一个方法被执行, 会在栈内存中创建一个新的栈帧 用于保存在函数中定义的基本数据类型变量以及对象的引用变量;
当方法结束时, 这个栈帧 改变它的状态为未使用并且可用于执行下一个方法
实例理解堆栈内存:
代码:
public class HeapStackTestMemory { public static void main(String[] args) { //Line 1 int i = 1; //Line 2 Object obj = new Object(); //Line 3 HeapStackTestMemory mem = new HeapStackTestMemory(); //Line 4 mem.foo(obj); //Line 5 } //Line 9 private void foo(Object param) { //Line 6 String str = param.toString(); //Line 7 System.out.println(str); } //Line 8 } 复制代码
程序执行步骤:
1, 生命周期 : 堆内存属于java 应用程序所使用, 生命周期与jvm一致;栈内存属于线程所私有的, 它的生命周期与线程相同
2, 引用 :不论何时创建一个对象, 它总是存储在堆内存空间 并且栈内存空间包含对它的引用 . 栈内存空间只包含方法原始数据类型局部变量以及堆空间中对象的引用变量
3, 在堆中的对象可以全局访问, 栈内存空间属于线程所私有
4, jvm 栈内存结构管理较为简单, 遵循LIFO 的原则, 堆空间内存管理较为复杂 , 细分为:新生代和老年代 etc..
5, 栈内存生命周期短暂, 而堆内存伴随整个用用程序的生命周期
6, 二者抛出异常的方式 : 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常, 堆内存抛出OutOfMemoryError异常
Thread Local Allocation Buffer 线程本地分配缓存区
如果设置了虚拟机参数 -XX:UseTLAB,在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个空间,如果需要分配内存,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在子常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。在类加载检查通过后,虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。
1. 指针碰撞 : 假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。
2. 空闲列表 : 如果Java堆中内存并不是规整的,已使用的内存和空闲的内存相互交错,虚拟机就必须维护一个列表,记录上那些内存块是可用的,在分配的时候从列表中找到一个足够大的空间划分给对象实例,并更新列表上的记录。
选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞;而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。
参考: juejin.im/post/59df28… juejin.im/post/5a5eeb…
不再使用的对象需要进行回收,不使用的类也有可能回收。
如何判断一个对象不再使用?
定义:给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器就减一;任何时刻计数器为0的对象就是不会被使用的对象。
通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径被称为引用链,当一个对象到“GC Roots”没有任何引用链相连的时候,就证明此对象是不可用的。
可以作为GC root对象包括:
分代收集:根据对象存活的不同年龄划分不同的域。
新生代:复制算法
老年代:标记复制算法:
分代收集算法将heap区域划分为新生代和老年代,新生代的空间比老年代的空间要小。新生代又分为了Eden和两个survivor空间,它们的比例为8:1:1。对象被创建时,内存的分配是在新生代的Eden区发生的,大对象直接在老年代分配内存,IBM的研究表明,Eden区98%的对象都是很快消亡的。
为了提高gc效率,分代收集算法中新生代和老年代的gc是分开的,新生代发生的gc动作叫做minor gc 或 young gc,老年代发生的叫做major gc 或 full gc。
minor gc 的触发条件:当创建新对象时Eden区剩余空间小于对象的内存大小时发生minor gc;
major gc 触发条件:
1、显式调用System.gc()方法;
2、老年代空间不足;
3、方法区空间不足;
4、从新生代进入老年代的空间大于老年代空闲空间;
Eden区对象的特点是生命周期短,存活率低,因此Eden区使用了复制算法来回收对象,上面也提到复制算法的特点是在存活率较低的情况下效率会高很多,因为需要复制的对象少。与一般的复制算法不同的是,一般的复制算法每次只能使用一半的空间,另一半则浪费掉了,Eden区的回收算法也叫做"停止-复制"算法,当Eden区空间已满时,触发Minor GC,清理掉无用的对象,然后将存活的对象复制到survivor1区(此时survivor0有存活对象,survivor1为空的),清理完成后survivor0为空白空间,survivor1有存活对象,然后将survivor0和survivor1空间的角色对象,下次触发Minor gc时重复上述过程。如果survivor1区剩余空间小于复制对象所需空间时,将对象分配到老年代中。每发生一次Minor gc时,存活下来的对象的年龄则会加1,达到一定的年龄后(默认为15)该对象就会进入到老年代中。
老年代的对象基本是经过多次Minor gc后存活下来的,因此他们都是比较稳定的,存活率高,如果还是用复制算法显然是行不通的。所以老年代使用“标记-整理”算法来回收对象的,从而提高老年代回收效率。
总的来说,分代收集算法并不是一种具体的算法,而是根据每个年龄代的特点,多种算法结合使用来提高垃圾回收效率。
标记清除算法分为“标记”和“清除”两个阶段,首先先标记出那些对象需要被回收,在标记完成后会对这些被标记了的对象进行回收
缺点:碎片多;虚拟机在给内存较大对象分配空间时,有可能找不到足够大的连续空间存放,从而引发垃圾回收动作。实际上有大量空闲空间,只是不连续;
复制算法是将内存分为两块大小一样的区域,每次是使用其中的一块。当这块内存块用完了,就将这块内存中还存活的对象复制到另一块内存中,然后清空这块内存。
现在商用的jvm中都采用了这种算法来回收新生代,因为新生代的对象基本上都是朝生夕死的,存活下来的对象约占10%左右,所以需要复制的对象比较少,采用这种算法效率比较高。
hotspot版本的虚拟机将堆(heap)内存分为了新生代和老年代,其中新生代又分为内存较大的Eden区和两个较小的survivor区。当进行内存回收时,将eden区和survivor区的还存活的对象一次性地复制到另一个survivor空间上,最后将eden区和刚才使用过的survivor空间清理掉。hotspot虚拟机默认eden和survivor空间的大小比例为8:1,也就是每次新生代中可用内存空间为整个新生代空间的90%(80%+10%),只会浪费掉10%的空间。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当survivor空间不够用时,需要依赖于其他内存(这里指的是老年代)进行分配的担保。
复制算法在对象存活率较高的情况下就要进行较多的对象复制操作,效率将会变低。更关键的是,如果你不需要浪费50%的空间,就需要有额外的空间进行分配担保,用以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种办法。
根据老年代的特点,有人提出了标记-整理的算法,标记过程仍然与标记-清楚算法一样,但后续步骤不是直接将可回收对象清理掉,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,算法示意图如下:
新生代
单线程
复制算法 client模式下默认的新生代垃圾收集器
serial+多线程
server模式下默认的新生代垃圾收集器
多线程复制算法
高效
重点关注程序达到一个可控制的吞吐量 : 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
自适应调节策略,是PS收集器与ParNew收集器的一个重要区别
单线程
标记整理算法
运行在client默认的java虚拟机老年代拉进收集器
在server模式下,两个用途:
1.jdk15之前版本与新生代PS收集器搭配使用;
2.作为老年代中使用CMS收集器的后备垃圾收集方案。
PS老年版本
多线程标记-整理算法
jdk1.6之前,PS只能跟serial old搭配收集;
jdk1.6之后,PS与老年代的parallel Old版本搭配:
老年代的垃圾收集算法,主要目标是获取最短垃圾回收停顿时间,与其他老年代标记整理不同的是,这里使用的是多线程标记-清除算法,主要分为4个阶段:
类加载:object o =new object() 主要包括三个阶段:通过不同的类加载器加载,当类被加载之后,进行验证,只要包括:文件格式验证,元数据验证,字节码验证,符号引用验证;准备阶段是为类的静态变量分配内存;
字节码是如何被虚拟机执行从而完成指定功能?
jvm参数
jstat查看虚拟机统计信息
jmap+MAT分析内存溢出
jstack分析死循环与死锁
参考: www.mamicode.com/info-detail…
1.了解了java数据区域的整体内存模型,在声明局部变量以及new对象实例的内存分配。
2.方法运行时,java栈中栈帧的一系列变化。
3.堆栈在jvm中的区别
4.分代垃圾回收机制,什么时候回收?回收什么垃圾?有哪些垃圾回收算法?不同版本下使用不同的垃圾收集器。
5.反编译后(javap -c)的字节码指令大概能够看一点,了解其整体在jvm中是如何一步一步运行的。
6.监控和GC调优待更。