JVM知识梳理
2024-12-31 11:49:59 15 举报
AI智能生成
JVM(Java Virtual Machine)是一种用于运行Java程序的虚拟计算机,它包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收器等组件。JVM通过加载并执行Java class文件来运行程序。 Java源代码文件(.java)经过编译后生成字节码文件(.class)。字节码文件是一种中间代码,由JVM执行。在运行期间,JVM将字节码解释成机器语言,以便在特定的平台上运行。 JVM内存区域包括堆、栈、方法区等。堆用于存储对象和数组,栈用于存储局部变量和方法调用,方法区用于存储类信息、常量和静态变量。 JVM还实现了自动垃圾回收机制(GC),用于释放不再使用的内存空间。GC有多种算法,如标记-清除、复制、标记-整理等。 JVM还提供了一套丰富的类库,包括集合、IO、网络、安全等,方便开发者进行编程。此外,JVM还支持多种语言(如Scala、Clojure、Groovy等)的运行,使其具有广泛的应用领域。
作者其他创作
大纲/内容
JVM参数命令及工具<br>
JVM参数<br>
标准参数
不会因为Java版本的变化而变化<br>
‐version<br>‐help<br>‐server<br>‐cp<br>...
非标准参数
可能会因为Java版本的变化而变化
-X<br>
1 ‐Xint 解释执行
2 ‐Xcomp 第一次使用就编译成本地代码
3 ‐Xmixed 混合模式,JVM自己来决定
-XX<br>
1 a.Boolean类型
2 格式:‐XX:[+‐]<name> +或‐表示启用或者禁用name属性
3 比如:‐XX:+UseConcMarkSweepGC 表示启用CMS类型的垃圾回收器
4 ‐XX:+UseG1GC 表示启用G1类型的垃圾回收器
5 b.非Boolean类型
6 格式:‐XX<name>=<value>表示name属性的值是value
7 比如:‐XX:MaxGCPauseMillis=500
其他参数<br>
1 ‐Xms1000M等价于 ‐XX:InitialHeapSize=1000M
2 ‐Xmx1000M等价于 ‐XX:MaxHeapSize=1000M
3 ‐Xss100等价于 ‐XX:ThreadStackSize=100
常见参数<br>
在哪设置<br>
(1)开发工具中设置比如IDEA,eclipse<br>(2)运行jar包的时候:java -XX:+UseG1GC xxx.jar<br>(3)中间件比如tomcat,可以在脚本中的进行设置<br>(4)通过jinfo实时调整某个java进程的参数(参数只有被标记为manageable的flags可以被实时修改)
查看参数<br>
(1)启动java进程时添加+PrintFlagsFinal参数<br>(2)通过jinfo命令查看,后面再聊
常见命令
jps<br>
jps<br>jps ‐l<br>
jinfo<br>
jinfo ‐flag name PID<br>jinfo ‐flag <name>=<value> PID<br>jinfo ‐flags PID
jstat<br>
jstat ‐class PID 1000 10 查看某个java进程的类装载信息,每1000毫秒输出一次,共输出10次<br>jstat ‐gc PID 1000 10
jstack<br>
jstack PID
jmap<br>
jmap ‐heap PID<br>jmap ‐dump:format=b,file=heap.hprof PID<br>XX:+HeapDumpOnOutOfMemoryError ‐XX:HeapDumpPath=heap.hprof # 关于dump文件的分析,可以用一些工具,暂时先不讨论
JDK通用工具
jconsole<br>
jconsole工具是JDK自带的可视化监控工具。查看java应用程序的运行概况、监控堆信息、永久区使用<br>情况、类加载情况等。
jvisualvm
可以监控某个java进程的CPU,类,线程等<br>连接远程java进程
第三方通用工具Arthas
Arthas 是Alibaba开源的Java诊断工具,采用命令行交互模式,是排查jvm相关问题的利器。
内存分析工具<br>
MAT<br>
介绍
Java堆分析器,用于查找内存泄漏
Heap Dump,称为堆转储文件,是Java进程在某个时间内的快照。
它在触发快照的时候保存了很多信息:Java对象和类信息。
一、获取dump文件<br>jmap ‐dump:format=b,file=heap.hprof 44808 或‐XX:+HeapDumpOnOutOfMemoryError ‐XX:HeapDumpPath=heap.hprof<br>二、Histogram:可以列出内存中的对象、对象个数及大小<br>Class Name:类名称,java类名<br>Objects:类的对象的数量,这个对象被创建了多少个<br>Shallow Heap:一个对象内存的消耗大小,不包含对其他对象的引用<br>Retained Heap:是shallow Heap的总和,即该对象被GC之后所能回收到内存的总和<br>三、右击类名--->List Objects--->with incoming references--->列出该类的实例<br>四、右击Java对象名--->Merge Shortest Paths to GC Roots--->exclude all ...--->找到GCRoot以及原因<br>五、Leak Suspects:查找并分析内存泄漏的可能原因Leak Suspects:查找并分析内存泄漏的可能原因<br>Reports‐‐‐>Leak Suspects‐‐‐>Details<br>六、Top Consumers:列出大对象
HeapHero<br>
Perfma
GC分析工具
GC日志<br>
gcviewer<br>
gceasy<br>
gcplot<br>
JVM性能优化及总结
JVM性能优化
介绍
JVM的性能优化可以分为代码层面和非代码层面。
在代码层面,大家可以结合字节码指令进行优化,比如一个循环语句,可以将循环不相关的代码提取到循环体之外,
这样在字节码层面就不需要重复执行这些代码了。
在非代码层面,一般情况可以从参数、内存、GC以及cpu占用率等方面进行优化。
注意,JVM调优是一个漫长和复杂的过程,而在很多情况下,JVM是不需要优化的,因为JVM本身已经做了很多的内
部优化操作,大家千万不要为了调优和调优。
代码优化<br>
参数优化<br>
‐XX:MaxTenuringThreshold<br>
Sets the maximum tenuring threshold for use in adaptive GC sizing. The largest value is 15. The default value is 15<br>for the parallel (throughput) collector, and 6 for the CMS collector.
‐XX:PretenureSizeThreshold
超过多大的对象直接在老年代分配,避免在新生代的Eden和S区不断复制
‐XX:+/‐ UseAdaptiveSizePolicy
Enables the use of adaptive generation sizing. This option is enabled by default.
‐XX:SurvivorRatio<br>
默认值为8
‐XX:ConcGCThreads
Sets the number of threads used for concurrent GC. The default value depends on the number of CPUs available to th<br>e JVM.
‐Xsssize<br>
Sets the thread stack size (in bytes). Append the letter k or K to indicate KB
‐Xms和‐Xmx<br>
两者值一般设置成一样大,防止内存空间进行动态扩容
‐XX:ReservedCodeCacheSize
Sets the maximum code cache size (in bytes) for JIT‐compiled code. Append the letter k or K to indicate<br>kilobytes, m or M to indicate megabytes, g or G to indicate gigabytes.
内存调优
内存分配
<br>
内存泄露导致内存溢出
01 启动<br> java ‐jar ‐Xms1000M ‐Xmx1000M ‐XX:+HeapDumpOnOutOfMemoryError ‐XX:HeapDumpPath=jvm.hprof jvm‐case‐0.0.1‐SNAPSHOT.ja<br>r<br><br>02 使用jmeter模拟并发<br><br>03 使用top命令查看<br> top<br> top ‐Hp PID<br><br>04 jstack查看有没有死锁或者IO阻塞<br> jstack PID<br><br>05 查看堆内存使用情况<br>jmap ‐heap PID<br>java ‐jar arthas.jar ‐‐‐> dashboard<br><br>06 获取到heap的文件,比如jvm.hprof,用相应的工具来分析,比如heaphero.io<br>
GC调优
是否选用G1
(1)50%以上的堆被存活对象占用<br>(2)对象分配和晋升的速度变化非常大<br>(3)垃圾回收时间比较长
G1日志文件<br>
参数:-XX:+UseG1GC -Xloggc:g1-gc.log<br>
G1调优实战<br>
(1)使用 G1 GC垃圾收集器,获取到日志文件:-Xms100M -Xmx100M<br>1 Throughput Min Pause Max Pause Avg Pause GC count<br>2 99.16% 0.00016s 0.0137s 0.00559s 12
(2)调整堆内存大小:-Xms300M -Xmx300M<br>
(3)调整最大停顿时间:-XX:MaxGCPauseMillis=200 设置最大GC停顿时间指标<br>
(4)启动并发GC时堆内存占用百分比:-XX:InitiatingHeapOccupancyPercent=45<br>G1用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比例。值为0则表示“一直执行GC循环”. 默认值为 45 (例如, 全部的 45% 或者使用了45%).
G1调优最佳实战
(1)不要手动设置新生代和老年代的大小,只要设置整个堆的大小<br>1 G1收集器在运行过程中,会自己调整新生代和老年代的大小<br>2 其实是通过adapt代的大小来调整对象晋升的速度和年龄,从而达到为收集器设置的暂停时间目标,如果手动设置了大小就意味着放弃<br>了G1的自动调优。<br>
(2)不断调优暂停时间目标<br>1 一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,<br>就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。<br>2 所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。暂停时间只是一个目标,并不能总是得到满足。
(3)使用-XX:ConcGCThreads=n来增加标记线程的数量
(4)适当增加堆内存大小
(5)不正常的Full GC<br>1 有时候会发现系统刚刚启动的时候,就会发生一次Full GC,但是老年代空间比较充足,一般是由Metaspace区域引起的。可以通过Me<br>taspaceSize适当增加其大家,比如256M。
CPU占用率过高
(1)top<br>(2)top ‐Hp PID<br> 查看进程中占用CPU高的线程id,即tid<br>(3)jstack PID | grep tid(1)top
大并发场景
在大并发场景下,除了对JVM本身进行调优之外,还需要考虑业务架构的整体调优<br>(1)浏览器缓存、本地缓存、验证码<br>(2)CDN静态资源服务器<br>(3)集群+负载均衡<br>(4)动静态资源分离、限流[基于令牌桶、漏桶算法]<br>(5)应用级别缓存、接口防刷限流、队列、Tomcat性能优化<br>(6)异步消息中间件 <br>(7)Redis热点数据对象缓存<br>(8)分布式锁、数据库锁<br>(9)5分钟之内没有支付,取消订单、恢复库存等<br>
JVM性能优化指南
发现问题
GC频繁
死锁
OOM
线程池不够用
CPU负载过高
排查问题<br>
打印出gc日志,查看minor gc/major gc,结合工具gc viewer/gceasy.io
jstack查看线程堆栈信息
dump出堆文件,使用MAT或者其他工具分析
合理使用jdk自带的jconsole,jvisualvm,阿里的arthas等实时查看JVM状态
灵活应用jps,jinfo,jstat,jmap等常用命令
解决方案
适当增加堆内存大小/选择合适的垃圾收集器
使用zk、redis实现分布式锁
设置本地。nginx等缓存减少对后端服务器的访问
后端代码优化及时释放资源/合理设置线程池中的参数
集群部署从而减少单节点的压力
利用一些消息中间件比如MQ、Kafka实现异步消息
......
JVM常见面试题<br>
内存泄漏与内存溢出的区别<br>
内存泄漏是指不再使用的对象无法得到及时的回收,持续占用内存空间,从而造成内存空间的浪费。<br>内存泄漏很容易导致内存溢出,但内存溢出不一定是内存泄漏导致的。
Young GC会有stw吗?<br>
不管什么GC,都会发送 stop‐the‐world,区别是发生的时间长短,主要取决于不同的垃圾收集器。
Major gc和Full gc的区别<br>
Major GC在很多参考资料中是等价于Full GC 的,我们也可以发现很多性能监测工具中只有Minor GC 和Full GC。一般情况下,一次 Full GC<br>将会对年轻代、老年代、元空间以及堆外内存进行垃圾回收。<br>触发Full GC的原因其实有很多:<br>当年轻代晋升到老年代的对象大小,并比目前老年代剩余的空间大小还要大时,会触发 Full GC;当老年代的空间使用率超过某阈值时,会触发 Fu<br>ll GC;当元空间不足时(JDK1.7永久代不足),也会触发 Full GC;当调用 System.gc() 也会安排一次 Full GC。
判断垃圾的方式<br>
引用计数<br>可达性分析
为什么要区分新生代和老年代<br>
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。<br>一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。<br>比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。<br>而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记‐清除”或“标记‐整理”算法进行垃圾收集。
方法区中的类信息会被回收吗?
方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的<br>类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类”<br>a‐该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。<br>b‐加载该类的 ClassLoader 已经被回收。<br>c‐该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
CMS与G1的区别<br>
CMS 主要集中在老年代的回收,而 G1 集中在分代回收,包括了年轻代的 Young GC 以及老年代的 Mixed GC。<br>G1 使用了 Region 方式对堆内存进行了划分,且基于标记整理算法实现,整体减少了垃圾碎片的产生。<br>在初始化标记阶段,搜索可达对象使用到的 Card Table,其实现方式不一样。<br>G1可以设置一个期望的停顿时间。
为什么需要Survivor区<br>
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。<br>这样一来,老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。<br>老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多。执行时间长有什么坏处?频发的Full GC消耗的时间很长,会影响<br>大型程序的执行和响应速度。
为什么需要两个Survivor区
最大的好处就是解决了碎片化。也就是说为什么一个Survivor区不行?第一部分中,我们知道了必须设置Survivor区。<br>假设现在只有一个Survivor区,我们来模拟一下流程:<br>刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。<br>这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,<br>如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。<br>永远有一个Survivor是空的,另一个非空的Survivor无碎片。
为什么Eden:S1:S2=8:1:1
新生代中的对象大多数是“朝生夕死”的
堆内存中的对象都是所有线程共享的吗?<br>
JVM默认为每个线程在Eden上开辟一个buffer区域,用来加速对象的分配,称之为TLAB,全称:Thread Local Allocation Buffer。<br>对象优先会在TLAB上分配,但是TLAB空间通常会比较小,如果对象比较大,那么还是在共享区域分配。
Java虚拟机栈的深度越大越好吗?<br>
线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,<br>就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。
垃圾收集器一般如何选择
a‐用默认的<br>b‐关注吞吐量:使用Parallel<br>c‐关注停顿时间:使用CMS、G1<br>d‐内存超过8GB:使用G1<br>e‐内存很大或希望停顿时间几毫秒级别:使用ZGC
什么是方法内联?<br>
正常调用方法时,会不断地往Java虚拟机栈中存放新的栈帧,这样开销比较大,其实jvm虚拟机内部为了节省这样开销,可以把一些方法放到同一个<br>栈帧中执行。
什么样的情况下对象会进入Old区?<br>
a‐大对象<br>b‐到了GC年龄阈值<br>c‐担保机制<br>d‐动态对象年龄判断
聊聊Minor GC、Major GC、Full GC发生的时机<br>
Minor GC:Eden或S区空间不足或发生了Major GC<br>Major GC:Old区空间不足<br>Full GC:Old空间不足,元空间不足,手动调用System.gc())
初识Java Virtual Machine
Introduction of JVM
What is JVM<br>
JVM是一种能够运行Java程序的虚拟机。它使得开发者只需编写一次程序,就可在多种不同的系统平台上运行,无需重新编译。
JVM products<br>
java -version命令查看<br>
Oracle:HotSpot(最常用)、JRoctit<br>
IBM:J9 VM
Ali:TaobaoVM<br>
Zual:Zing
JDK JRE JVM<br>
JDK包含JRE,还包含一些开发工具,如编译器、打包工具等;JRE包含JVM和一些基础类库,负责执行java字节码;JVM是JRE的核心组件,负责执行java字节码
结合JDK看JVM<br>
能够把Class文件翻译成不同平台的CPU指令集<br>
也是Write Once Run Anywhere的保证<br>
HotSpot JVM Architecture
<br>
类加载器
运行时数据区
方法区
堆
java栈<br>
程序计数器
本地方法栈
执行引擎<br>
解释器<br>
即时编译器
垃圾收集器
本地方法接口<br>
Class file<br>
Java Source
Java源码文件、Kotlin源码文件、Groovy源码文件等
Early compile
<br>
Class format
Java 的 .class 文件格式通过上述结构定义了类的基本信息、常量、字段、方法和其他属性。这种结构化的设计使得 JVM 能够有效地加载和执行 Java 字节码,同时也支持跨平台的特性。
Analyse
魔数(magic)<br>
cafe babe<br>
这是一个固定的标识符,用于标识该文件是一个有效的 Java 类文件,它的值为 0xCAFEBABE。
版本号(minor_version + major_version)<br>
0000 + 0034<br>
这部分表示类文件的版本号。minor_version 和 major_version 的组合表示 JDK 的版本。在这个例子中,34(十六进制)对应于 52(十进制),表示该类文件是为 JDK 8 编译的。<br>
反汇编
javap -h
javap ‐v ‐c ‐p User.class > User.txt 进行反编译,查看字节码信息和指令等信息
类加载机制
Loading
ClassLoader
Bootstrap ClassLoader(引导类加载器)
功能 :负责加载JVM核心类库,通常是 JAVA_HOME/jre/lib/rt.jar 中的类。<br>特点 :这是最顶层的类加载器,使用 -Xbootclasspath 选项可以指定加载的类路径。<br>
Extension ClassLoader(扩展类加载器)<br>
功能 :加载Java平台的扩展库,通常是 JAVA_HOME/jre/lib/ext 目录下的jar包。<br>特点 :可以通过 -Djava.ext.dirs 选项指定额外的扩展目录。
App ClassLoader(应用类加载器)<br>
功能 :加载classpath中指定的jar包以及 -Djava.class.path 所指定目录下的类。<br>特点 :这是用户应用程序的主要类加载器,负责加载应用程序的类。<br>
Custom ClassLoader(自定义类加载器)
功能 :通过 java.lang.ClassLoader 的子类来自定义类加载器,允许开发者根据需要自定义类加载逻辑。<br>特点 :适用于需要特殊类加载需求的应用,如Tomcat、JBoss等Java EE容器会使用自定义类加载器。
双亲委派机制
检查某个类是否已经加载<br>自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个Classloader已加<br>载,就视为已加载此类,保证此类只所有ClassLoader加载一次。
加载的顺序<br>自顶向下,也就是由上层来逐层尝试加载此类。<br>
破坏双亲委派
tomcat<br>
在 Tomcat 中,类加载机制的实现允许应用程序使用自己的类加载器,<br>这可能会破坏双亲委派机制<br>
Web 应用的类加载器<br>
共享库<br>
SPI机制<br>
服务提供者可以使用不同的类加载器来加载其实现类,<br>从而绕过双亲委派机制。这可能导致同名类的冲突和不一致性。<br>
动态加载
通过 SPI,应用程序可以动态加载服务实现,而不需要依赖于父类加载器。<br>这意味着可以在运行时选择不同的实现类。<br>
不同的类加载器<br>
服务提供者可以使用不同的类加载器来加载其实现类,从而绕过双亲委派机制。<br>这可能导致同名类的冲突和不一致性。<br>
OSGi
OSGi 是一个模块化的 Java 平台,<br>允许开发者创建可动态加载和卸载的模块(称为 Bundle)<br>
模块化设计
在 OSGi 中,每个 Bundle 都有自己的类加载器,Bundle 可以直接加载其他 Bundle 的类,<br>而不需要通过父加载器。这种设计允许更灵活的模块管理,但也可能导致类的版本冲突。<br>
模块化设计<br>
OSGi 支持动态更新和卸载 Bundle,这意味着在运行时可以替换类的实现,<br>可能会导致类加载的不一致性。<br>
自定义类加载器<br>
通过创建一个自定义的类加载器,可以在其 findClass 方法中直接加载类,而不调用父类加载器。这种方式可以绕过双亲委派机制<br>
使用同名类 <br>
如果在不同的类加载器中加载同名的类,可能会导致类的冲突。子加载器可以加载与父加载器同名的类,从而破坏双亲委派机制<br>
修改类加载器的行为
通过反射或其他手段,可以在运行时修改类加载器的行为,使其不遵循双亲委派机制<br>
Linking<br>
Verification<br>
保证被加载类的正确性<br>
Preparation<br>
为类的静态变量分配内存,并将其初始化为默认值<br>
Resolution<br>
把类中的符号引用转换为直接引用
符号引用
符号引用是指在类的字节码中使用字符串形式表示的类、方法、字段等的引用。这种引用方式是为了保持跨平台的兼容性和灵活性。<br>例如,某个类可能会以字符串形式表示其方法的名称和参数类型。
直接引用<br>
直接引用是指在内存中实际的地址或指针,能够直接访问到目标类、方法或字段。<br>直接引用通常是一个内存地址,指向实际的类或对象。<br>
解析过程<br>
在解析阶段,JVM会根据符号引用的信息,查找相应的类、方法或字段,并将其转换为直接引用。<br>这个过程涉及到查找符号引用所对应的类、方法或字段的实际内存地址,并将其存储在相应的引用位置。
Initialization<br>
对类的静态变量,静态代码块执行初始化操作
运行时数据区<br>
Method Area(方法区)
介绍
JVM运行时数据区是一种规范,真正的实现在JDK 8中就是Metaspace,在JDK6或7中就是Perm Space
方法区是各个线程共享的内存区域,在虚拟机启动时创建,虽然Java虚拟机规范把方法区描述为堆的一
个逻辑部分,但是它却又一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来。
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。当方法区无法
满足内存分配需求时,将抛出OutOfMemoryError异常。
常量池和运行时常量池
常量池
常量池是指在Java类文件中定义的一个表格,包含了编译时已知的常量信息,如数字字面量、字符串字面量以及符号引用(例如类名、方法名和字段名)。<br>这些常量在编译阶段就已经确定,并存储在类文件中。
运行时常量池
运行时常量池是每个类或接口在Java虚拟机中创建的一个运行时表示,来源于类文件中的常量池表。<br>它不仅包含编译时的常量,还可以包含在运行时解析后生成的常量,例如方法和字段的引用。<br>运行时常量池的构建是在类或接口被创建时进行的,且它的内存分配来自Java虚拟机的方法区。<br>
区别
常量池是在编译时定义的静态信息,而运行时常量池是在类加载后动态生成的,包含了更广泛的常量信息 。运行时常量池的功能类似于传统编程语言中的符号表,但其数据范围更广泛。
String常量到底存在哪
Heap(堆)
介绍
(1)Java堆是Java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享<br>(2)Java对象实例以及数组都在堆上分配<br>(3)堆内存空间不足时,也会抛出OOM
Java对象内存布局
一个Java对象在内存中包括3个部分:对象头、实例数据和对齐填充
<br>
方法区引用指向堆
堆指向方法区
Java Virtual Machine Stacks(Java虚拟机栈)<br>
介绍<br>
(1)虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。换句话说,一个Java线程的运行状态,<br>由一个虚拟机栈来保存,所以虚拟机栈肯定是线程私有的,独有的,随着线程的创建而创建。<br>(2)每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。调用一个方法,就会向栈中压入一个<br>栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。
压栈出栈<br>
子主题<br>
Frame(栈帧)<br>
index为0还是1<br>
局部变量的索引始终是从0开始的 ,在静态方法中,参数从0开始;在实例方法中,局部变量0用于 this 引用,后续参数从1开始。
栈引用指向堆
局部变量表中对象指向堆中地址
Native Method Stacks(本地方法栈)<br>
The pc Register(程序计数器)
如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的<br>是Native方法,则这个计数器为空。
JVM内存模型与垃圾收集器
JVM内存模型<br>
整体描述
重点存储数据的是堆和方法区(非堆),所以内存的设计也着重从<br>这两方面展开(注意这两块区域都是线程共享的)。对于虚拟机栈,本地方法栈,程序计数器都是线程私有<br>的。
(1)一块是非堆区,一块是堆区<br>(2)堆区分为两大块:一个是Old区,一个是Young区<br>(3)Young区分为两大块:一个是Survivor区(S0+S1),一块是Eden区<br>(4)S0和S1一样大,也可以叫From和To
Java Memory Model
对象在内存中的分配回收
当新对象申请内存时,首先检查Eden区是否有足够空间,如果有则直接分配内存成功,流程结束;如果没有足够空间,则触发Minor GC回收不活跃对象,再次检查Eden区是否有足够空间,如果有则分配内存成功,流程结束;如果仍然没有足够空间,则检查Survivor区是否有足够空间,如果有则将Eden区存活对象复制到Survivor区;如果Survivor区没有足够空间,则检查Old区是否有足够空间,如果有则将Survivor区存活对象复制到Old区;如果Old区也没有足够空间,则触发Full GC回收整个堆内存,再次检查Old区是否有足够空间,如果有则将Survivor区存活对象复制到Old区,内存申请成功,流程结束;如果仍然没有足够空间,则抛出OutOfMemoryError。
垃圾收集<br>
确定垃圾对象
引用计数法<br>
对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任<br>何指针对其引用,它就是垃圾。
可达性分析
通过GC Root的对象,开始向下寻找,看某个对象是否可达。<br>能作为GC Root: 类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量等。
垃圾收集算法
标记-清除<br>
标记:找出内存中需要回收的对象,并且把它们标记出来<br>清除:清除掉被标记需要回收的对象,释放出对应的内存空间<br>
标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不<br>得不提前触发另一次垃圾收集动作。<br>(1)标记和清除两个过程都比较耗时,效率不高<br>(2)会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前<br>触发另一次垃圾收集动作<br>
标记-整理
标记<br>整理
标记-复制
标记<br>复制
(1)存在大量的复制操作,效率会降低<br>(2)空间利用率降低<br>
垃圾收集算法选择<br>
Young区<br>
复制算法(对象在被分配之后,可能生命周期比较短,Young区复制效率比较高)
Old区
标记清除或标记整理(Old区对象存活时间比较长,复制来复制去没必要,不如做个标记再清理)
垃圾收集器
解释
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现
Serial<br>
可以用于新老年代<br>新生代: 复制算法<br>老年代: 标记-整理算法<br>The serial collector uses a single thread to perform all garbage collection work
Parallel
可以用于新老年代<br>新生代:复制算法<br>老年代:标记整理算法<br>The parallel collector is also known as throughput collector, it's a generational collector similar<br>to the serial collector. The primary difference between the serial and parallel collectors is that<br>the parallel collector has<br>multiple threads that are used to speed up garbage collection.
CMS(ConcMarkSweepGC)
可以用于老年代<br>采用标记-清除算法<br>回收过程:https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html<br>The Concurrent Mark Sweep (CMS) collector is designed for applications that prefer shorter<br>garbage collection pauses and that can afford to share processor resources with the garbage<br>collector while the application is running.<br>
(1) 初始标记 CMS initial mark 标记GC Roots直接关联对象,不用Tracing,速度很快<br>(2) 并发标记 CMS concurrent mark 进行GC Roots Tracing<br>(3) 重新标记 CMS remark 修改并发标记因用户程序变动的内容<br>(4) 并发清除 CMS concurrent sweep 清除不可达对象回收空间,同时有新垃圾产生,留着下次清理称为浮动垃圾<br>
<br>
优点:并发收集、低停顿<br>缺点:产生大量空间碎片、并发阶段会降低吞吐量
G1(Garbage First)
可以用于新老年代<br>整体上采用标记-整理算法<br>回收过程:https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html<br>G1 is a mostly concurrent collector. Mostly concurrent collectors perform some expensive work<br>concurrently to the application. This collector is designed to scale from small machines to large<br>multiprocessor machines<br>with a large amount of memory. It provides the capability to meet a pause-time goal with high<br>probability, while achieving high throughput.<br>
(1) 初始标记(Initial Marking) 标记以下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程<br>(2) 并发标记(Concurrent Marking) 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行<br>(3) 最终标记(Final Marking) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程<br>(4) 筛选回收(Live Data Counting and Evacuation) 对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收<br>计划
1 使用G1收集器时,Java堆的内存布局与就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有<br>新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合<br>2 每个Region大小都是一样的,可以是1M到32M之间的数值,但是必须保证是2的n次幂<br>3 如果对象太大,一个Region放不下[超过Region大小的50%],那么就会直接放到H中<br>4 设置Region大小:‐XX:G1HeapRegionSize=M<br>5 所谓Garbage‐Frist,其实就是优先回收垃圾最多的Region区域
ZGC
Java11引入的垃圾收集器<br>不管是物理上还是逻辑上,ZGC中已经不存在新老年代的概念了<br>会分为一个个page,当进行GC操作时会对page进行压缩,因此没有碎片问题<br>只能在64位的linux上使用,目前用得还比较少<br>The Z Garbage Collector (ZGC) is a scalable low latency garbage collector. ZGC performs all<br>expensive work concurrently, without stopping the execution of application threads.
(1)可以达到10ms以内的停顿时间要求<br>(2)支持TB级别的内存<br>(3)堆内存变大后停顿时间还是在10ms以内
垃圾收集器分类<br>
(1)串行:Serial 适合内存比较小的嵌入式设备<br>(2)并行:Parallel 更加关注吞吐量:适合科学计算、后台处理等若交互场景<br>(3)并发:CMS、G1 更加关注停顿时间:适合web交互场景
<br>
Reponsiveness and throughput
停顿时间->垃圾收集器 进行 垃圾回收终端应用执行响应的时间<br>吞吐量->运行用户代码时间/(运行用户代码时间+垃圾收集时间)
什么时候会发生垃圾收集
GC是由JVM自动完成的,根据JVM系统环境而定,所以时机是不确定的。<br>当然,我们可以手动进行垃圾回收,比如调用System.gc()方法通知JVM进行一次垃圾回收,但是具体什么时刻运行也<br>无法控制。<br>也就是说System.gc()只是通知要回收,什么时候回收由JVM决定。但是不建议手动调用该方法,因为GC消耗的资源<br>比较大。
(1)当Eden区或者S区不够用了: Young GC 或 Minor GC<br>(2)老年代空间不够用了: Old GC 或 Major GC<br>(3)方法区空间不够用了: Metaspace GC<br>(4)System.gc()<br>Full GC=Young GC+Old GC+Metaspace GC
Execution Engine
介绍
User.java源码文件是Java这门高级开发语言,对程序员友好,方便我们开发。<br>javac编译器将User.java源码文件编译成class文件[我们把这里的编译称为前期编译],交给JVM运行,因为JVM只能认<br>识class字节码文件。同时在不同的操作系统上安装对应版本的JDK,里面包含了各自屏蔽操作系统底层细节的JVM,<br>这样同一份class文件就能运行在不同的操作系统平台之上,得益于JVM。这也是Write Once,Run Anywhere的原因<br>所在。<br>最终JVM需要把字节码指令转换为机器码,可以理解为是0101这样的机器语言,这样才能运行在不同的机器上,那么<br>由字节码转变为机器码是谁来做的呢?说白了就是谁来执行这些字节码指令的呢?这就是执行引擎里面的解释执行器<br>和编译器所要做的事情。
Interpreter<br>
Interpreter,解释器逐条把字节码翻译成机器码并执行,跨平台的保证。<br>刚开始执行引擎只采用了解释执行的,但是后来发现某些方法或者代码块被调用执行的特别频繁时,就<br>会把这些代码认定为“热点代码”。
即时编译器
介绍
Just-In-Time compilation(JIT),即时编译器先将字节码编译成对应平台的可执行文件,运行速度快。
即时编译器会把这些热点代码编译成与本地平台关联的机器码,并且进行各层次的优化,保存到内存中。
C1和C2
HotSpot虚拟机里面内置了两个JIT:C1和C2<br>1 C1也称为Client Compiler,适用于执行时间短或者对启动性能有要求的程序<br>2 C2也称为Server Compiler,适用于执行时间长或者对峰值性能有要求的程序<br>Java7开始,HotSpot会使用分层编译的方式<br>1 也就是会结合C1的启动性能优势和C2的峰值性能优势,热点方法会先被C1编译,然后热点方法中的热点会被C2再次编译
AOT
在Java9中,引入了AOT(Ahead of Time)编译器<br>即时编译器是在程序运行过程中,将字节码翻译成机器码。而AOT是在程序运行之前,将字节码转换为<br>机器码<br>1 优势 :这样不需要在运行过程中消耗计算机资源来进行即时编译<br>2 劣势 :AOT 编译无法得知程序运行时的信息,因此也无法进行基于类层次分析的完全虚方法内联,或者基于程序profifile的投机性优化(并非<br>硬性限制,我们可以通过限制运行范围,或者利用上一次运行的程序profifile来绕开这两个限制)
Graal VM
在Java10中,新的JIT编译器Graal被引入。它是一个以Java为主要编程语言,面向字节码的编译器。跟<br>C++实现的C1和C2相比,模块化更加明显,也更加容易维护。<br>Graal既可以作为动态编译器,在运行时编译热点方法;也可以作为静态编译器,实现AOT编译。<br>除此之外,它还移除了编程语言之间的边界,并且支持通过即时编译技术,将混杂了不同的编程语言的<br>代码编译到同一段二进制码之中,从而实现不同语言之间的无缝切换。
0 条评论
下一页