1.是什么
(1) 基本概念:可运行 Java 代码的非真实计算机 ,包括一套字节码指令集
、一组寄存器
、一个栈
、一个垃圾回器
,堆
和一个存储方法域
。它运行在操作系统之上,与硬件没有直接的交互。
(2) 运行过程:Java 源文件.java
通过编译器javac
,能够生产相应的.class
字节码文件,而字节码文件又通过 Java 虚拟机中的解释器,编译成特定机器上的机器码。
不同平台的解释器不同,但是编译的过程是相同的,这就是所谓的一次编译到处执行
跨平台 。一个程序开始运行java -jar xxx.jar
虚拟机就会进行实例化,多个程序启动就会存在多个虚拟机实例。程序退出或者关闭,则虚拟机实例被销毁,多个虚拟机实例之间数据不能共享。
2.内存结构
内存是非常重要的系统资源,是硬盘和 CPU 的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM 内存布局规定了 Java 程序在运行过程中的内存申请、分配、管理策略,保证了它的高效稳定运行。不同的 JVM 对于内存的划分方式和管理机制存在着部分差异。
JVM 运行时管理的内存就是虚拟机内存
它会把这些内存分配成不同的区域, JVM 利用到的没有直接管理的物理内存就是本地内存
,这两种内存有一定的区别:
- 虚拟机内存:受虚拟机内存参数控制
-Xmx 设置; Runtime.getRuntime().maxMemory() 进行查看
,超过参数设置的大小时就会报OOM。 - 本地内存:本地内存不受虚拟机内存参数的限制,只受物理内存容量的限制,虽然不受参数的限制,但是如果内存的占用超出物理内存的大小,同样也会报OOM。
JVM 定义了若干种程序运行期间会使用到的运行时数据区内存区域
,其中有一些会随着虚拟机启动而创建,随着虚拟机退出或关闭而销毁。另外一些则是与线程一一对应的,这些与线程一一对应的数据区域会随着线程开始和结束而创建和销毁。
- 线程共享:方法区、堆、堆外内存(永久代或元空间、代码缓存)
- 线程私有:栈、程序计数器、本地方法栈
下图是 JVM 整体架构,中间部分就是它定义的各种运行时数据区域:
3.堆内存空间
Heap Area 堆
是 JVM 内存中最大的一块,被垃圾收集器管理也被所有线程共享。主要存放对象实例,由于 JVM 的发展,堆中也多了许多东西:
堆可以是处于物理上不连续的内存空间中,只要逻辑上是连续的即可,像磁盘空间一样,既可以是固定大小,也可以是可扩展的(通过参数-Xmx和-Xms
设定),堆在无法扩展或者无法分配内存时会报 OOM 也就是堆内存耗尽
,堆内存逻辑上
被划分成 3️⃣ 个区域(分代的唯一理由就是优化 GC 性能):
- 新生区(年轻代):新对象和没达到一定年龄的对象都在新生区【占堆的1/3】。
- 养老区(老年代):被长时间使用的 MinorGC 未回收的对象【占堆的2/3】。
- 元空间(JDK8之前叫永久区):像一些方法中操作的临时对象等,JDK8之前是占用JVM内存,JDK8使用本地内存
这也是为什么在限制了JVM堆内存之后程序处理大量数据时电脑内存还是被大量占用的原因
。
Visual VM 的插件 Visual GC 绘制的 Spaces:
3.1 新生区 (New Space/Young Generation)
新生区是所有新对象存储的地方,被分为 3️⃣ 个区域:伊甸区(Eden Space
)和两个幸存区(Survivor Space
,被称为 from survivor/to survivor 或 s0/s1),默认比例是8:1:1
。
刚被new出来的对象都是存放在伊甸区【如果新创建的对象占用内存很大,超过了-XX:PetenureSizeThreshold 则直接分配到养老区
】,当前空间用完时,程序又需要创建对象,JVM 的垃圾回收器将对伊甸区进行垃圾回收(这种垃圾收集称为MinorGC
),销毁伊甸区中不再被其他对象所引用的对象并将剩余对象移动到s0
区。若s0
区也满了,再对该区进行垃圾回收,然后移动到s1
区。那如果s1
区也满了呢?再移动到养老区。若养老区也满了,那么这个时候将产生 MajorGC(FullGC),进行养老区的内存清理。若养老区执行了 FullGC 之后发现依然无法进行对象的保存,就会产生OOM。
MinorGC
的过程为(复制->清空->互换):
【问题:为什么要将新生区分为三个区域?
】由于年轻代的垃圾回收算法,(复制算法)设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入S1区(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生),接着新对象继续分配在Eden区和另外那块开始被使用的Survivor区,然后始终保持一块Survivor区是空着的,就这样一直循环使用这三块内存区域。
JVM 会给new出来的对象定义一个对象年轻计数器
每次的 MinorGC 对象的年龄就会+1
达到老年的标准-XX:MaxTenuringThreshold
【默认值为15】则复制到养老区:
3.2 养老区(Tenure Space/Old Generation)
养老区主要存放应用程序中生命周期长的内存对象。老年代的对象比较稳定,所以Major GC
不会频繁执行。在进行Major GC
前一般都先进行了一次Minor GC
,使得有新生代的对象晋升入老年代,导致空间不够用时才触发。
大对象直接进入养老区(大对象是指需要大量连续内存空间的对象,超过了-XX:PetenureSizeThreshold
)。这样做的目的是避免在 Eden 区和两个 Survivor 区之间发生大量的内存拷贝。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次Major GC
进行垃圾回收腾出空间。
Major GC
采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。因为要扫描再回收所以Major GC
的耗时比较长。Major GC
会产生内存碎片,为了减少内存损耗,一般需要进行合并或者标记出来方便下次直接分配。当养老区也满了装不下的时候,就会抛出OOM。
3.3 元空间(Meta Space/Permanent Generation)
不管是 JDK8 之前的永久代,还是 JDK8 及以后的元空间,都可以看作是 JVM 规范中方法区的实现。虽然 JVM 规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫 Non-Heap(非堆),目的应该是与 Java 堆区分开。
永久存储区是一个常驻内存区域,用于存放 JDK 自身所携带的 Class、Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。
如果出现java.lang.OutOfMemoryError: PermGen space
,说明是 JVM 对永久代 Perm 内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方 jar 包。例如:在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致 Perm 区被占满。
版本 | 永久代 | 常量池 |
---|---|---|
JDK6及之前 | 有永久代 | 在方法区 |
JDK7 | 有永久代,已逐步“去永久代” | 在堆 |
JDK8及之后 | 无永久代 | 在元空间 |
4.堆内存参数
Java 堆用于存储 Java 对象实例,那么堆的大小在 JVM 启动的时候就确定了,我们可以通过 -Xmx
和 -Xms
来设定
-Xms
堆的起始内存,默认情况下为服务器内存的1/64
,等价于-XX:InitialHeapSize
-Xmx
堆的最大内存,默认情况下为服务器内存的1/4
,等价于-XX:MaxHeapSize
如果堆的内存大小超过 -Xms
设定的最大内存, 就会抛出OOM。通常会将 -Xmx
和 -Xms
两个参数配置为相同的值,其目的是为了能够在垃圾回收机制清理完堆区后不再需要重新分隔计算堆的大小,从而提高性能。
可以通过代码获取到我们的设置值,当然也可以模拟 OOM:
public static void main(String[] args) {
// JVM 堆大小
long totalMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
// JVM 堆的最大内存
long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
System.out.println("-Xms : " + totalMemory + "M");
System.out.println("-Xmx : " + maxMemory + "M");
// 反向计算计算间内存
System.out.println("系统内存大小:" + totalMemory \* 64 / 1024 + "G");
System.out.println("系统内存大小:" + maxMemory \* 4 / 1024 + "G");
}
5.堆内存分配
在默认不配置 JVM 堆内存大小的情况下,JVM 根据默认值来配置当前内存大小:
- 默认情况下新生区和养老区的比例是
1:2
,可以通过–XX:NewRatio
来配置。 - 新生代中的
Eden:From Survivor:To Survivor
的比例是8:1:1
,可以通过-XX:SurvivorRatio
来配置。 - 若在JDK7中开启了
-XX:+UseAdaptiveSizePolicy
,JVM 会动态调整 JVM 堆中各个区域的大小以及进入老年代的年龄,此时–XX:NewRatio
和-XX:SurvivorRatio
将会失效;而 JDK8 是默认开启-XX:+UseAdaptiveSizePolicy
在 JDK8中,不要随意关闭-XX:+UseAdaptiveSizePolicy
,除非对堆内存的划分有明确的规划。
每次 GC 后都会重新计算 Eden、From Survivor、To Survivor 的大小计算依据是 GC 过程中统计的GC时间、吞吐量、内存占用量:
# JDK8
java -XX:+PrintFlagsFinal -version | grep HeapSize
uintx ErgoHeapSizeLimit = 0 {product}
uintx HeapSizePerGCThread = 87241520 {product}
uintx InitialHeapSize := 29360128 {product}
uintx LargePageHeapSizeThreshold = 134217728 {product}
uintx MaxHeapSize := 459276288 {product}
java version "1.8.0\_251"
Java(TM) SE Runtime Environment (build 1.8.0_251-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)
# 查看进程的堆信息
jmap -heap 进程号
6.堆内存回收(垃圾回收)
JVM 在进行 GC 时,并非每次都对堆内存(新生区、养老区、方法区)区域一起回收的,大部分时候回收的都是指新生代。针对 HotSpot VM 的实现,它里面的 GC 按照回收区域又分为两大类:
(1)整堆收集(FullGC):收集整个 Java 堆和方法区的垃圾。
(2)部分收集:不是完整收集整个 Java 堆的垃圾收集。其中又分为:
新生代收集(MinorGC/YoungGC):只是新生代的垃圾收集。
老年代收集(MajorGC/OldGC):只是老年代的垃圾收集。
目前只有 G1 GC 会有这种行为
目前,只有 CMS GC 会有单独收集老年代的行为
很多时候 MajorGC 会和 FullGC 混合使用,需要具体分辨是老年代回收还是整堆回收
混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集
7.TLAB(Thread Local Allocation Buffer)
从内存模型而不是垃圾回收的角度,对 Eden 区域继续进行划分,JVM 为每个线程分配了一个私有缓存区域,它包含在 Eden 空间内多线程同时分配内存时,使用 TLAB 可以避免一系列的非线程安全问题,同时还能提升内存分配的吞吐量,因此我们可以将这种内存分配方式称为快速分配策略。为什么要有 TLAB:
- 堆区是线程共享的,任何线程都可以访问到堆区中的共享数据
- 由于对象实例的创建在 JVM 中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的
- 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度
尽管不是所有的对象实例都能够在 TLAB 中成功分配内存,但 JVM 确实是将 TLAB 作为内存分配的首选。在程序中,可以通过 -XX:UseTLAB
设置是否开启 TLAB 空间。默认情况下,TLAB 空间的内存非常小,仅占有整个 Eden 空间的 1%,我们可以通过 -XX:TLABWasteTargetPercent
设置 TLAB 空间所占用 Eden 空间的百分比大小。一旦对象在 TLAB 空间分配内存失败时,JVM 就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在 Eden 空间中分配内存。
还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!
王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。
对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!
【完整版领取方式在文末!!】
93道网络安全面试题
内容实在太多,不一一截图了
黑客学习资源推荐
最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
😝朋友们如果有需要的话,可以联系领取~
1️⃣零基础入门
① 学习路线
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
② 路线对应学习视频
同时每个成长路线对应的板块都有配套的视频提供:
2️⃣视频配套工具&国内外网安书籍、文档
① 工具
② 视频
③ 书籍
资源较为敏感,未展示全面,需要的最下面获取
② 简历模板
因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!