一、组成及其作用

1、类加载器

虚拟机把描述类的数据从Class文件加载到内存,并且对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型;

1.1、类加载过程加载:根据查找路径导入相应的class文件;

验证:文件格式验证、元数据验证、字节码验证、符号引用验证,检查加载的class文件的正确性;

准备:给类中的静态变量分配内存空间;

解析:虚拟机将常量池中的符号引用替换成直接应用的过程,符号引用用一组符号来描述所引用的目标,在直接引用中直接指向内存中的地址;

初始化:对静态变量和静态代码块执行初始化工作;

1.2、双亲委派模型

工作原理:

如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式;

优势

避免类的重复加载;

1.3、类加载器分类启动类加载器:加载Java的核心库;

扩展类加载器:加载Java的扩展库;

应用程序类加载器:加载Java应用的类;

2、运行时区域

2.1、 程序计数器

记录当前正在执行的虚拟机字节码的指令地址;

如果正在执行的是本地方法则为空;

2.2、Java虚拟机栈

每个Java方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法的调用直至完成的过程中,对应一个栈帧在Java虚拟机中入栈和出栈的过程;

该区域可能抛出以下异常:当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;

栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。

2.3、本地方法栈

本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。

2.4、堆

所有对象在这里分配内存,是垃圾回收的主要区域(“GC堆”),被所有线程所共享;

从内存回收的角度,现在的垃圾收集器都是采用分代收集算法,主要是针对不同类型的对象采取不同的垃圾回收算法,可以将堆分为两块:新生代(Young Generation)

老年代(Old Generation)

其中新生代按照8:1:1的比例分为Eden区、from Survivor、to Survivor三个区域,

堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常,可以通过 -Xms和-Xmx这两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设定初始值,第二个参数设定最大值;

2.5、方法区

用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;

在Java1.8开始,移除永久代,并且把方法区移至元空间,位于本地内存中,而不是虚拟机内存中;

2.6、运行时常量池

运行时常量池是方法区的一部分,Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。

二、垃圾收集

如何判断一个对象是否可被回收

1、引用计数法

为对象添加一个引用计数器,当对象增加一个引用计数器加1,引用失效时计数器减1,引用计数为0的对象可以被回收;

缺点:无法解决对象直接的循环引用问题;

2、可达性分析算法

以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。Java虚拟机中使用该算法来判断对象是否可以回收,GCRoots一般包含以下内容:虚拟机栈中局部变量表中引用的对象;

本地方法栈中 JNI 中引用的对象;

静态成员变量或者常量引用的对象;

3、一个对象有多个引用,如何判断它的可达性

单弱多强

引用类型

1、强引用

被强引用关联的对象不会被回收,使用new一个新对象的方式来创建强引用:

Object obj = new Object();1

2、软引用

被软引用的对象只有在内存不够的情况下才会被回收,使用SoftReference类来创建软引用。Object obj = new Object();SoftReference sf = new SoftReference(obj);obj = null;1

2

3

4

3、弱引用

被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。使用WeakReference 类来创建弱引用:

Object obj = new Object();WeakReference wf = new WeakReference(obj);obj = null;1

2

3

4、虚引用

为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知;

使用PhantomReference来创建虚引用:Object obj = new Object();PhantomReference pf = new PhantomReference(obj,null);obj = null;1

2

3

垃圾收集算法

1、标记-清除算法

首先标记出所有需要回收的对象,在标记完成之后统一回收所有被标记的对象;

不足:标记和清除过程的效率都不高;

标记清除之后会产生大量不连续的内存碎片,而导致在分配较大对象时因为无法得到足够的连续内存而不得不提前触发一次垃圾收集动作;

2、标记-整理算法

让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存;不会产生内存碎片;

不足:需要移动大量对象,处理效率比较低;

3、复制

将可用内存分为大小相等的两块,每次只使用其中的一块,当其中一块内存用完之后,就将存活的对象复制到另外一块上,然后再把已使用的内存空间一次性清理掉,使得每次都可以对整个半区进行内存回收

在商业虚拟机中并不需要按照1:1的比例进行划分内存空间,将内存分为一个较大的Eden和两块较小的Survior空间;当回收时,将Eden和Survivor中还存活的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间;

HotSpot虚拟机默认Eden和Sruvivor的大小比例是8:1;

4、分代收集算法

现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。

一般将堆分为新生代和老年代。新生代使用:复制算法(新生代中每次垃圾收集时都有大批对象死去,只有少量对象存活)

老年代使用:标记 - 清除 或者 标记 - 整理 算法(老年代对象中因为对象存活率高、没有额外的空间进行分配担保)

垃圾收集器

1、老年代回收器

CMS

CMS收集器是牺牲吞吐量为代价来获取最短停顿时间为目标的垃圾回收器,

CMS收集器是基于“标记——清除”算法,整个过程分为四个步骤:初始标记:仅仅标记一下GC Roots能直接关联到的对象;

并发标记:进行GC Roots Tracing,耗时最长不需要停顿;

重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录;

并发清理:不需要停顿;

优点:并发收集、低停顿;

缺点:吞吐量低:低停顿时间是以牺牲吞吐量为代价的,导致CPU利用率不高;

无法处理浮动垃圾,可能出现 Concurrent Mode Failure。浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收;

标记-清除算法导致得到空间碎片,导致会给大对象的内存分配出现问题;

Serial old

作为Serial 收集器的老年代版本,也是给 Client 场景下的虚拟机使用。如果用在 Server 场景下,它有两大用途:在 JDK 1.5 以及之前版本(Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。

作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。

Parallel old

Parallel Scavenge 收集器的老年代版本。

在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。

2、新生代回收器

serial

单线程收集器,只会使用一个线程进行垃圾回收工作;

优点:简单高效,没有线程交互的开销,拥有最高的单线程收集效率;

在内存不大的场景下,收集一两百兆垃圾的停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这些停顿时间是可以接受的;

Parnew

Serial 收集器的多线程版本;

Server 场景下默认的新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合使用;

Parallel Scavenge

与 ParNew 一样是多线程收集器

达到可控制的吞吐量,吞吐量是指CPU用于运行用户程序的时间占总时间的比值;

高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算程序;

缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。

3、整堆回收器

G1

G1是一种兼顾吞吐量和停顿时间的GC实现,是JDK9以后的默认GC选项;一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。

通过引入Region的概念,将原先的一整块内存空间划分成多个小空间,使得每个小空间可以单独进行垃圾回收;

步骤分为四步:初始标记:

并发标记:

最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。

筛选回收:首先对于各个Region内中的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划;此阶段也可以做到和用户程序一起并发执行,但是因为只回收一部分Region,时间时用户可控制的,而且停顿用户线程将大幅度提高收集效率;

空间整合:

从整体来看是基于'标记-整理'算法实现的收集器,从局部上来看是基于复制算法实现的,这意味着运行期间不会产生内存空间碎片;

最大的特点是引入了分区的思路,弱化了分化的概念;

每个分区被标记了E、S、O和H,H表示这些Region中存储的是巨型对象,新建对象大小超过Region大小一半时,直接在歆的一个或者多个连续分区中分配,并标记为H;

三、内存分配和回收策略

内存分配和回收策略

Minor GC 和 Full GCMinor GC:回收新生代,因为新生代对象存活时间很短,Minor GC就会频繁执行,执行的速度一般也会比较快;

Full GC:回收老年代和新生代,老年代对象其存活时间长,因此Full GC很少执行,执行速度会比Minor GC慢很多;

内存分配策略

1、对象优先在Eden分配

大多数情况下,对象在新生代Eden上分配,当Eden空间不够时,发起Minor GC

2、大对象直接进入老年代

大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组;

经常出现大对象会提前出发垃圾收集来获取足够的连续空间分配给大对象;

-XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在Eden和Survivor之间的大量内存复制;

3、长期存活的对象进入老年代

为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中;

-XX:MaxTenuringThreshold 用来定义年龄的阈值;

4、动态对象年龄判定

虚拟机并不是永远要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。

5、空间分配担保

在发起Minor GC之前,虚拟机先检查老年代中最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么Minor GC可以确认是安全的;

Full GC的触发条件

对于MinorGC,其触发条件十分简单,当Eden空间满时,就将触发一次Minor GC,而Full GC相对复杂,条件如下:

1、调用 System.gc()

只是建议虚拟机执行 FullGC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

2、老年代空间不足

老年代空间不租的常见问题是大对象直接进入老年代、长期存活的对象进入老年代等;

应当避免创建过大的对象和数组,除此之外还可以通过-Xmn虚拟机参数来调大新生代的大小,让对象尽量在新生代中被回收掉,不进入老年代;还可以通过-XX:MaxTenuringThreshold调大对象进入老年代的年龄,让对象在新生代中多存活一段时间;

3、空间分配担保失败

使用复制算法的 MinorGC需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC;

4、Concurrent Mode Failure

执行CMS GC的过程中同时有对象要放入老年代中,而此时的老年代空间不租(可能是GC过程中浮动垃圾过多导致暂时性地空间不足),便会报Concurrent Mode Failure错误,并且触发Full GC;

内存泄漏程序动态分配了内存,但是在程序结束时,没有进行及时释放,导致那部分内存不可用;

被分配地对象可达但是已经没有作用,循环创建对象,各种连接没有及时释放;

可以通过一些性能检测工具,如JProfiler等工具查找内存泄漏;

内存溢出虚拟机和本地方法栈溢出

堆溢出

方法区溢出

内存溢出和内存泄漏的区别:内存泄漏导致内存溢出的原因之一;内存泄漏积累起来将导致内存溢出;

内存泄漏可以通过完善代码来避免;内存溢出可以通过调整配置来减少发生频率,但无法彻底避免;

如何避免内存泄漏和溢出?尽早释放无用对象的引用;

采用临时变量的时候,让引用变量在退出活动域之后自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄漏;

程序进行字符串处理时,应该避免使用string,而应使用StringBuffer,因为每一个String对象都会独立占用内存一块区域;

相关面试题

1、如何减少GC次数?尽量少用静态变量;

对象不用时最好显式设置为null;

增大堆的最大值设置;

尽量使用stringBuffer而不是String,减少不必要的中间对象

经常使用的图片可以使用软引用类型;

2、对象在内存中的初始化过程

https://blog.csdn.net/WantFlyDaCheng/article/details/81808064

以Student s = new Student()为例:首先查看类的符号引用,看是否在常量池中,不在的话进行类加载的过程;

在栈内存为s变量申请一个空间;

在堆内存中为Student对象申请空间;

对类中的成员变量进行默认初始化

对类中的成员变量进行显示初始化;

有构造代码块就先执行,没有就省略;

执行构造方法,通过构造方法来对对象数据进行初始化;

堆内存中的数据初始化完毕之后,把内存值复制给s变量;

3、一般Java堆是如何实现的?

4、对象的强、软、弱和虚引用

java虚拟机堆和栈用途_Jvm虚拟机学习相关推荐

  1. Java虚拟机:Java中堆和栈的详细区别

    一.Java中内存分配策略: 在比较堆和栈的区别之前,我们先了解下Java的内存分配策略,按照编译原理的观点,程序运行时的内存分配有三种策略,分别是:静态的,栈式的,和堆式的. (1)静态存储分配:是 ...

  2. java堆和栈 常量池_GitHub - han-guang-xue/difference-of-stack-heap-pool: Java中堆、栈和常量池的区别...

    Java中堆.栈和常量池的区别 栈 堆 常量池的概念 首先我们先了解一下概念,Java把内存分成两种,一种叫做栈内存,一种叫做堆内存. 栈内存 存放基本类型的变量数据和对象类型的引用(请注意存放的是引 ...

  3. Java中堆、栈、常量池等概念解析

    Java中堆.栈.常量池等概念解析 程序运行时,我们最好对数据保存到什么地方做到心中有数.特别要注意的是内存的分配.有六个地方都可以保存数据: (1) 寄存器.这是最快的保存区域,因为它位于和其他所有 ...

  4. JAVA中堆和栈的区别和联系

    一.Java的堆内存和栈内存 Java把内存划分成两种:一种是堆内存,一种是栈内存. 堆:主要用于储存实例化的对象,数组.由JVM动态分配内存空间.一个JVM只有一个堆内存,线程是可以共享数据的. 栈 ...

  5. 对这个java虚拟机内存的,年轻代,年老代,永久代i,搞不太清楚。年轻代、年老代存放在堆还是栈。新版虚拟机没有永久代是个啥情况

    1.什么是jvm?(1)jvm是一种用于计算设备的规范,它是一个虚构出来的机器,是通过在实际的计算机上仿真模拟各种功能实现的.(2)jvm包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个 ...

  6. java中堆和栈的区别!!!!

    Java的堆是一个运行时数据区,类的(对象从中分配空间.这些对象通过new.newarray.anewarray和multianewarray等 指令建立,它们不需要程序代码来显式的释放.堆是由垃圾回 ...

  7. java gc堆中的分区_jvm内存各个区域详解

    内存区域划分 Java虚拟机所管理的内存区域分为如下部分:方法区.GC堆.虚拟机栈.本地方法栈.PC程序计数器. 其中方法区.GC堆是所有线程共享的:虚拟机栈.本地方法栈.PC程序计数器是各个线程独占 ...

  8. java内存堆和栈_java中堆,栈主要区别及内存存储

    java堆.栈.堆栈的区别 1.栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方.与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆. 2. 栈的优势是,存取速度 ...

  9. Java面试--堆和栈的概念和区别

    堆和栈的概念和区别[转载自博客] 在说堆和栈之前,我们先说一下JVM(虚拟机)内存的划分: Java程序在运行时都要开辟空间,任何软件在运行时都要在内存中开辟空间,Java虚拟机运行时也是要开辟空间的 ...

最新文章

  1. 指针的底层原理与使用
  2. java数据库编程——事务
  3. 开源好用的思维导图软件XMind
  4. 2016宁波计算机程序复赛,宁波第31届中小学生计算机程序设计竞赛复赛试题小学组.PDF...
  5. 【spring】通过GZIP压缩提高网络传输效率(可以实现任何资源的gzip压缩、包括AJAX)
  6. torch.randn【返回从标准正态分布(均值为0,方差为1,即高斯白噪声)中抽取的一组随机数】
  7. [LeetCode] Android Unlock Patterns 安卓解锁模式
  8. python水印倾斜_python中图像特定位置的水印算法
  9. 提升网站转化率的四步优化方案
  10. atitit.浏览器插件解决方案----ftp插件 attilax 总结
  11. yoyo跑_足球YOYO体测大揭秘 失去资格只需两次
  12. js 操作获取和设置cookie
  13. plc和pc串口通讯接线_PLC与PC机之间的串行通信
  14. 冒泡排序超详细讲解C语言
  15. 040 罗尔定理与零点定理、介值定理综合应用;柯西中值定理; 型二( f(n) (ξ) =0 )
  16. php mysql orm_PHP基于ORM操作MySQL数据库 - strtolower
  17. Java web video 视频开发
  18. 初识Vulkan渲染管线
  19. 从反人工智能到反无人机,谁在左右科技的进步?
  20. 大学英语六级考试大纲 A

热门文章

  1. 8086微型计算机原理答案,8086微型计算机原理与应用(吴宁)习题答案(第二章)
  2. 不瞎忙的人生,需要做对五件事
  3. svn,git 分支管理
  4. 【游戏手柄】如何在PC上使用非XBOX手柄玩游戏
  5. QUIC浅析,android开发者模式
  6. 金融科技之:融资租赁业务系统建设方案分享
  7. “信”守不渝,坚持为用户创造价值
  8. 同态滤波(Homomorphic filtering)
  9. 麒麟V10服务器SP1安装教程步骤
  10. 2023年深圳CPDA数据分析师认证将于2/25正式开班,快来报名