介绍下 Java 内存区域(运⾏时数据区)

JVM - 图1
线程私有的:

  • 程序计数器
  • 虚拟机栈
  • 本地⽅法栈

线程共享的:

  • ⽅法区
  • 直接内存 (⾮运⾏时数据区的⼀部分)

程序计数器

指示Java虚拟机下一条需要执行的字节码指令。

虚拟机栈

虚拟机栈中执行每个方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。

本地⽅法栈

虚拟机使⽤到的 Native ⽅法服务。

Java 对象的存储区域,几乎所有的对象实例以及数组都在这里分配内存。
在JDK7和之前,堆内存通常被分为三部分:

  • 新生代内存
  • 老生代
  • 永生代

JDK8版本之后,方法区(永生代)被彻底移除了,变为了元空间,元空间使用的是直接内存

⽅法区

它⽤于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?

  1. 整个永久代有⼀个 JVM 本身设置固定⼤⼩上限,⽆法进⾏调整,⽽元空间使⽤的是直接内存,受本机可⽤内存的限制,虽然元空间仍旧可能溢出,但是⽐原来出现的⼏率会更⼩。
    2.元空间⾥⾯存放的是类的元数据,这样加载多少类的元数据就不由 MaxPermSize 控制了,⽽由系统的实际可⽤空间来控制,这样能加载的类就更多了。

常用的 jvm 调优的参数都有哪些?

image.jpeg

Xmx2g JVM最大可用内存为2g
Xms2g JVM初始内存2g,避免gc后JVM动态分配内存
Xmn1g JVM年轻代大小
Xss256k 每个线程的堆栈大小

说⼀下Java对象的创建过程

Step1:类加载检查

虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载。

Step2:分配内存

在类加载检查通过之后,接下来虚拟机将为新生对象分配内存。若Java堆中内存是绝对规整的,使用“指针碰撞“方式分配内存;如果不是规整的,就从空闲列表中分配,叫做”空闲列表“方式。
image.png
在创建对象的时候有⼀个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采⽤两种⽅式来保证线程安全:

  • CAS+失败重试: CAS 是乐观锁的⼀种实现⽅式。所谓乐观锁就是,每次不加锁⽽是假设没有冲突⽽去完成某项操作,如果因为冲突失败就重试,直到成功为⽌。 虚拟机采⽤ CAS配上失败重试的⽅式保证更新操作的原⼦性
  • TLAB: 为每⼀个线程预先在 Eden 区分配⼀块⼉内存, JVM 在给线程中的对象分配内存时,⾸先在 TLAB 分配,当对象⼤于 TLAB 中的剩余内存或 TLAB 的内存已⽤尽时,再采⽤上述的 CAS 进⾏内存分配

    Step3:初始化零值

    内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这⼀步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使⽤,程序能访问到这些字段的数据类型所对应的零值。

    Step4:设置对象头

    初始化零值完成之后, 虚拟机要对对象进⾏必要的设置

    Step5:执⾏ init ⽅法

    在上⾯⼯作都完成之后,从虚拟机的视⻆来看,⼀个新的对象已经产⽣了,但从 Java 程序的视⻆来看,对象创建才刚开始, ⽅法还没有执⾏,所有的字段都还为零。

什么是类加载?何时类加载?类加载流程?

Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。


Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。


类装载分为以下 5 个步骤:
(1)加载:根据查找路径找到相应的 class 文件然后导入;
(2)验证:检查加载的 class 文件的正确性;
(3)准备:给类中的静态变量分配内存空间;
(4)解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
(5)初始化:对静态变量和静态代码块执行初始化工作。

知道哪些类加载器。类加载器之间的关系?

主要有一下四种类加载器:
(1)启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
(2)扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
(3)系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
(4)用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。

类加载器的双亲委派了解么?

.
双亲委派模型:当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。

为 什 么 需 要 双 亲 委 派

可以避免多层级的加载器结构重复加载某些类

栈中存放什么数据,堆中呢?

1.堆内存用来存放由new创建的对象和数组。
2.栈内存用来存放方法或者局部变量等

大对象放在哪个内存区域

JVM在分配内存空间给大对象时,如果young区空间不足,会直接在old区切一块过去。从而保证整个JVM的正常运转。

垃圾回收有哪些算法

标记-清除算法

该算法分为“标记”和“清除”阶段:⾸先标记出所有不需要回收的对象,在标记完成后统⼀回收掉所有没有被标记的对象。它是最基础的收集算法,后续的算法都是对其不⾜进⾏改进得到。这种垃圾收集算法会带来两个明显的问题:1. 效率问题 2. 空间问题(标记清除后会产⽣⼤量不连续的碎⽚)

复制算法

为了解决效率问题, “复制”收集算法出现了。它可以将内存分为⼤⼩相同的两块,每次使⽤其中的⼀块。当这⼀块的内存使⽤完后,就将还存活的对象复制到另⼀块去,然后再把使⽤的空间⼀次清理掉。这样就使每次的内存回收都是对内存区间的⼀半进⾏回收。

标记-整理算法

分为两阶段“标记”和“整理”。首先标记出哪些对象可被回收,在标记完成后,将对象向一端移动,然后直接清理掉边界以外的内存。

分代收集算法

当前虚拟机的垃圾收集都采⽤分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为⼏块。⼀般将 java 堆分为新⽣代和⽼年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
⽐如在新⽣代中,每次收集都会有⼤量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。⽽⽼年代的对象存活⼏率是⽐较⾼的,⽽且没有额外的空间对它进⾏分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进⾏垃圾收集。

GC的全流程

1、新创建的对象,绝大多数都会存储在 Eden 中,
2、当 Eden 满了(达到一定比例)不能创建新对象,则触发垃圾回收( GC),将无用对象清理掉,然后剩余对象复制到某个 Survivor 中,如 S1,同时清空 Eden 区。
3、当 Eden 区再次满了,会将 S1 中的不能清空的对象存到另外一个 Survivor 中,如 S2,同时将 Eden 区中的不能清空的对象,也复制到 S1 中,保证 Eden 和 S1,均被清空。
4、重复多次(默认 15 次)Survivor 中没有被清理的对象,则会复制到老年代 Old(Tenured)区中,
5、当 Old 区满了,则会触发一个一次完整地垃圾回收( FullGC),之前新生代的垃圾回收称为( minorGC)

如何识别垃圾,判定对象是否可被回收?

答:

  1. 引用计数法,一个对象如果没有任何与之关联的引用,即他们的引用计数都不为0。缺点:无法解决循环引用的问题
  2. 可达性分析,GC root和一个对象之间没有可达路径,则称该对象不可达。

Java 中的堆是 GC 收集垃圾的主要区域,GC 分为两种:Minor GC、Full GC ( 或称为 Major GC )。

  • Minor GC:新生代(Young Gen)空间不足时触发收集,由于Java 中的大部分对象通常不需长久存活,新生代是GC收集频繁区域,所以采用复制算法
  • Full GC:老年代(Old Gen )空间不足或元空间达到高水位线执行收集动作,由于存放大对象及长久存活下的对象,占用内存空间大,回收效率低,所以采用标记-清除算法

jvm 有哪些垃圾回收器?

JVM - 图4

Serial(串行)收集器

针对新生代;
采用复制算法;
单线程收集;
进行垃圾收集时,必须暂停所有工作线程,直到完成;

使用-XX:+UseSerialGC参数可以设置新生代使用这个Serial收集器

ParNew(并行)收集器

除了多线程外,其余的行为、特点和Serial收集器一样,也是使用复制算法—
通常大家线上系统都是ParNew垃圾回收器作为新生代的垃圾回收器
“-XX:+UseParNewGC”选项 ,JVM启动之后对新生代进行垃圾回收的,就是
ParNew垃圾回收器了

CMS收集器

答:CMS是一款并发、使用标记-清除算法的gc。CMS是针对老年代进行回收的GC。
如何执行: ①、初始标记,会导致stw
②、并发标记,与用户线程同时运行
④、 重新标记 (STW)
⑤、并发清理
缺点:

  1. 垃圾碎片的问题,由于它使用的是 标记-清楚算法,所以就会出现垃圾碎片问题
  2. 重新标记耗时长
  3. 会产生concurrent mode failure,意思是当执行GC的过程中,年轻代和老年代空间都不足,但此时业务创建的对象放不下导致的

如何解决:

  1. 设置-XX:CMSFullGCsBeforeCompaction=n ,意思是上一次CMS并发GC执行过后,到底还要再执行多少次full GC才会做压缩
  2. -XX:+CMSScavengeBeforeRemark,意思是在执行remark操作之前先做一次Young GC,目的在于减少年轻代对老年代的无效引用,降低remark时的开销
  3. -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=60:是指设定CMS在对内存占用率达到60%的时候开始GC。

    G1收集器

    G1是一款面向服务端应用的垃圾回收器。
  • 并行与并发:与CMS类似,充分里用多核CPU的优势,G1仍然可以不暂停用户线程执行垃圾收集工作
  • 分代收集:分代的概念依然在G1保留,当时它不需要和其他垃圾收集器配合使用,可以独立管理整个堆内存
  • 空间的整合:G1整体上采用的是标记-整理算法,从局部(Region)采用的是复制算法,这两种算法都意味着G1不需要进行内存碎片整理
  • 可预测的停顿:能够让用户指定在时间片段内,消耗在垃圾收集的时间不超过多长时间。

说一下 jvm 调优的工具?

答:jdk自带的工具:
1. jps:虚拟机进程状况工具
2. jstat:虚拟机统计信息监视工具
3. jmap: Java 内存印象工具
4. jhat:虚拟机堆转储快照分析工具
5. jstack: Java 堆栈跟踪工具
6. jinfo: Java 配置信息工具
第三方:MAT、gcviewer

强引用、软引用、弱引用、虚引用的区别?

强引用 : 我们平时new了一个对象就是强引用,例如 Object obj = new Object();即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。
软引用 :如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存
弱引用 :具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
虚引用 :如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。



课外资料:
https://www.it610.com/article/1295493801233686528.htm