JVM学习一

JVM学习一

jvm的内存结构、相关的垃圾算法、分代回收算法、相关溢出的问题排查思路

jvm的内存结构:可以看到我们的java文件会首先编译成class文件,经过类加载器进行加载,然后经过jvm的相关区域:f方法区、堆、虚拟机栈、程序计数器、本地方法栈等地,可以进行本地方法接口进行调用,执行引擎,进行编译,执行程序。当中涉及到垃圾回收。

1.程序计数器:Program Count Register

寄存器,记住下一条jvm指令的执行地址,是线程私有的,JVM中唯一不会存在内存溢出的。

2.虚拟机栈:Java Virtual Mechinal Stacks

每个线程只能有一个活动栈帧,通常是参数、局部变量、返回地址,对应着当前正在执行的那个方法

栈帧:每个方法运行时需要的内存。

垃圾回收设计栈内存吗?不涉及栈内存。

栈内存的分配越大越好吗?

不是的,通常是默认的,1024k,其中linux和mac电脑默认是1024k,而windows是根据系统而定。由于一个线程对应一个栈帧,如果越大,则会拖慢虚拟机的运行性能。

方法内的局部变量是否线程安全?

如果方法内的局部变量没有逃离方法的作用访问,则它是线程安全的,如果局部变量应用了对象,并逃离方法的作用范围,则需要考虑线程安全的问题。也即与变量的作用域有关。

栈内存溢出:栈帧过多、过大都会导致栈内存溢出。

栈内存溢出的排查方法:

首先采用top命令,查看当前是哪个进程cpu占用过高

接着采用ps H -eo pid,tid,%cpu |grep 进程id

用jstack 进程id排查到有问题线程,定位到问题代码的行号,从而解决问题。

3.本地方法栈:Native Method Stacks

主要存放操作系统的方法,存放native方法的内存调用。

4.堆:Heap

通过new关键字创建的对象都会被存放在堆内存中。它是线程共享的,堆中对象都需要考虑线程安全的问题,同时有垃圾回收机制。

堆溢出问题解决:

通常会出现java.lang.OutofMemoryError:java heap space的错误信息。

首先通过jps查看到当前有哪些java进程在运行。

接着采用jmap查看堆使用情况,通常jmap只能查看某一时刻的信息。jmap -heap 进程id

jconsole工具可以以图形化界面看到相关的监控信息,做到连续监控。连接jconsole,打开其图形化界面,进行查看。

采用jvisualvm ,可以进行堆dump日志分析,排查问题。

5.方法区:Method Area

主要存放成员方法、方法、构造方法、运行时常量池(String Table)

虚拟机启动时被创建,逻辑上是堆的一部分。jdk1.7及以前,存放在永久代中。而jdk1.8的时候存放在metaspace中。

方法区jdk1.7的时候会出现溢出,但jdk1.8之后存放在metaspace元空间中,因此通常情况下不会出现内存溢出的情况。

方法区常量池:运行时常量池StringTable

常量池就是一张类似于hash一样的表。虚拟机指令根据这张表找到执行的类名、方法名、类、字面量等信息。当类文件被加载时常量信息会加载到运行时常量池中,并把里面的符号地址变成真实地址。

串池(调用intern方法就可以放入)中如果有的常量,则不会到常量池中去找,其加载策略是惰性的。常量池中的字符串是符号,第一次用到时才是对象。同时利用串池可以避免重复创建,节省内存。字符串拼接通常是在编译期优化的。

如果项目中常量信息较多,同时又有重复,则可以考虑串池进行调优。同时如果常量信息较多,也可以调大桶的个数,由于其本身就是一张表。-XX:stringTableSize=桶个数

6.直接内存:Direct Memory

操作系统内存,常用于NIO操作时,用于数据缓冲区,分配回收成本较高,但读写性能高,不受jvm内存回收管理。

分配与回收:使用unsafe对象完成直接内存的分配和回收,分配内存可以采用allocateMemory来进行分配内存,并且回收需要主动调用freeMemory方法。ByteBuffer的实现类内部使用了cleaner虚引用来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过clear的clean方法调用freeMemory来释放内存。

相关引用

强引用、软引用、弱引用、虚引用、终结器引用

强引用:只有所有GC roots对象都不通过强引用引用该对象,该对象才能被垃圾回收。

软引用:仅有软引用该对象,在垃圾回收后,内存还不足时会再次触发垃圾回收,回收软引用对象,可以配合引用队列来释放引用自身。

弱引用:仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象,可以配合引用队列来释放引用自身。

虚引用:必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会被徐引用入队,由Referencehandler线程调用虚引用相应的方法来释放直接内存。

终结器引用:无需手动编码,但器内部配合引用队列使用,在垃圾回收时,终结器引用入队,找到被引用对象并调用finalize方法,第二次GC回收的时候才能回收被引用对象。

垃圾回收

如何判断对象可以回收?采用可达性分析算法,也即没有引用的对象,通常会进行回收,也即孤立的对象会被回收。

垃圾回收算法:

通常可分为三种:标记清除mark sweep、标记整理mark compact、复制copy。

这三种算法:标记清除算法速度快,但产生内存碎片较多,不利于内存的充分使用。标记整理,速度适中,同时能够整理内存碎片。复制算法,速度最慢,但不会产生内存碎片,占用内存双倍。

由于三种算法都有其优点和缺点,因此需要综合使用它们。因此就产生了分带垃圾回收算法。

新生代由于产生的对象是朝生夕死的,因此通常会常用复制算法,而老年代则采用标记整理或标记清除算法。

同时可分为新生代、老年代、MetaSpace区(jdk1.7永久代)

新生代又可分为Eden区、Survivor区,而Survivor区又可分为From、To两部分。

对象存活区域:

1.通常首先分配在Eden区

2.新生代空间不足时,会触发minor gc,Eden区和from存活对象使用copy算法复制到to中,存活的对象年龄+1,并且交换from、to

3.minor gc会引发stop the world,暂停其他用户的线程,等垃圾回收结束,用户线程才回复运行

4.当对象寿命超过阀值时,会晋升到老年代,最大寿命是15(4bit)

5.当老年代空间不足时,会先尝试触发minor gc,如果空间不足,那么触发full gc,stw的时间更长

相关jvm参数:

堆初始化大小:-Xms

堆最大大小:-Xmx或-XX:MaxHeapSize=size

新生代大小:-Xmn或-XX:newSize=size+-XX:MaxNewSize=size

幸存区比例(动态):-XX:InittalSurvivorRatio=ratio和-XX:+UseAdaptiveSizePolicy

幸存区比例:-XX:SurvivorRatio=ratio

晋升阀值:-XX:MaxTenuringThreshold=threshold

晋升详情:-XX:+PrintTenuringDistribution

GC详情:-XX:+PrintGCDetails -verbose:gc

FullGC前MinorGC:-XX:+scavengeBeforeFullGC

垃圾回收器:

可以分为串行垃圾回收器、并行垃圾回收器、并发垃圾回收器、G1等

串行垃圾回收器:单线程、堆内存较小,适合个人电脑,-XX:+userSerialGC=Serial+SerialOld,串行新生代和老年代

并行垃圾回收器:多线程,吞吐量高,-XX:+UserParallelGC —— -XX:+UseParallelOldGC,-XX:GCTimeRatio=ratio,-XX:MaxGCPauseMillis=ms,-XX:ParallelgcThreads=n,并行GC,新生代和老年代,同时可以设置比例,响应时间、线程数等信息

并发垃圾回收器:多线程,响应时间短,-XX:+UseConcMarkSweepGC—— -XX:UseParNewGC——SerialOld,-XX:ParallelGCThreads=n—— -XX:Concgcthreads,-XX:CMSInitiatingOccupancyFranction=percent,-XX:+CMSScavengeBeforeRemark,包含信息并发新生代GC,串行Old,并发线程数,CMS比例,重新标记再清理

G1垃圾回收器:

使用场景:注重高吞吐量,低延迟的场景,默认的暂停目标是200ms。超大堆内存,会将堆划分为多个大小相等的Region,整体上标记+整理算法,两个区域之间采用复制算法。

相关参数:

-XX:+UseG1GC

-XX:G1HeapRegionsSize=size

-XX:MaxGCPauseMillis=time

垃圾回收阶段:

Young Collection、Mixed Collection、Young Collection+Concurrent Mark

Young Collection:会STW.

Young Collection+CM:在Young GC时会进行GC Root的初始化标记,老年代占用堆空间比例达到阀值时,进行并发标记,不会STW.

Mixed Collection:会对E、S、O进行全面垃圾回收,最终标记会STW,拷贝存活会STW

进行Full GC:相关代的FullGC

Young Collection跨代引用:新生代回收的跨代引用(老年大引用新生代)问题:卡表与Remebered Set,在引用变量时通过post-write barier+dirty card queue进行处理,同时concurrent refinement threads更新Remembered Set.

Remark:pre-write barrier + satb_mark_queue

通常调优需要考虑:内存、锁竞争、cpu占用、IO、代码这些层面上的调优。

通常最快的GC:

考虑不发生GC,也即查看FullGC前后的内存占用,考虑数据是否太多、数据表示是否太大、是否存在内存泄漏(变量的作用范围、是否存在软、弱引用、第三方缓存等)。

本文分享自微信公众号 - 后端技术学习(gh_9f5627e6cc61),作者:亚洲

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JVM学习二

    jps、jstat、jinfo、jhat、jstack、jconsole、jmap、MAT、Btrace、psi_probe监控tomcat,通过gceasy查...

    路行的亚洲
  • LinkedBlockingQueue源码学习

    采用线程池和阻塞队列实现生产/消费者模型。其中LinkedBlockingQueue是阻塞队列,同时线程安全,其特点:

    路行的亚洲
  • RocketMQ学习5

    进行消息发送的过程首先会准备好路由信息,最终是由netty完成的,也即使用nettyRemotingClient来实现的。

    路行的亚洲
  • Java内存管理

    内存溢出 理论学习 问题解决 垃圾回收 问题 理论学习 垃圾回收过程 常用垃圾回收器 工具篇 GC日志 命令行工具 可视化工具 问题解决 内存溢出 首先是比较”...

    Java帮帮
  • Java内存故障?只是因为你不够帅!

    从小我就对Java有着深厚的感情,算下来有几十年的Java经验了。当年的Java还是Sun公司的,我有着多年的Servlet经验,CURD经验,在现在已经被自我...

    xjjdog
  • 7种jvm垃圾回收器,这次全部搞懂

    之前我们讲解了jvm的组成结构与垃圾回收算法等知识点,今天我们来讲讲jvm最重要的堆内存是如何使用垃圾回收器进行垃圾回收,并且如何使用命令去配置使用这些垃圾回收...

    公众号 IT老哥
  • JVM垃圾回收参数说明整理

    启用-server时新生代默认采用并行收集,其他情况下,默认不启用。-server策略为:新生代使用并行清除,年老代使用单线程Mark-Sweep-Compac...

    用户3003813
  • tomcat调优

    Tomcat 的启动参数位于tomcat的安装目录\bin目录下,如果你是Linux操作系统就是catalina.sh文件,如果你是Windows操作系统那么你...

    剑行者
  • 《深入理解Java虚拟机》读书笔记

    -Xmn: 可以设置新生代的大小,设置一个比较大的新生代会减少老年代的大小,这个设置对系统性能以及GC行为有很大的影响,新生代大小一般会设置整个堆空间的1/3到...

    用户5640963
  • 深入理解JVM(六)——JVM性能调优实战

    如何在高性能服务器上进行JVM调优? 为了充分利用高性能服务器的硬件资源,有两种JVM调优方案,它们都有各自的优缺点,需要根据具体的情况进行选择。 1. 采用...

    大闲人柴毛毛

扫码关注云+社区

领取腾讯云代金券