再回首看JVM

前序:

java开发8年,竟说不清道不明jvm到底身为何物?我都不好意思说自己是从事java开发的。

平时开发很少涉及到JVM底层优化,有关JVM的概念全停留在零零碎碎的理论层面。

今天抽空一探究竟,顺便整理一遍。

1. 内存模型

有图有真相,一图剩千言:

 补充说明一点:Java虚拟机栈的生命周期与线程一致

StackOverflowError(栈溢出):线程请求的栈深度大于虚拟机所允许的深度(栈帧)
OutOfMemoryError(内存溢出):创建对象或使用资源时无法申请到足够的堆栈内存

Memory Leak(内存泄露):使用完资源后未释放(比如流未关闭),导致资源占用的内存无法被回收利用,就会引起内存泄露,内存泄漏累积会导致内存溢出

2. 堆栈关联

3. 内存管理

下面通过jdk自带的jvisualvm来观察jvm内存配置情况,并根据实际需要自行调优JVM

3.1 选择Visual GC 插件

visualvm访问地址:https://visualvm.github.io/index.html

 然后根据自己安装的JDK版本找到对应版本的插件, 复制插件资源地址URL

3.2 配置Visual GC 插件

打开本地的jdk/bin/jvisualvm.exe

 编辑录入复制的插件资源地址URL

3.3 安装Visual GC 插件

 3.4 重启jdk/bin/jvisualvm.exe,查看Vsiual GC

 可以直观地看到内存区域的划分

3.5 远程JVM调试查看

第一步:登录远程服务器配置jstatd的远程RMI服务

进入java/bin目录下,创建文件jstatd.all.policy:

grant codebase "file:${java.home}/../lib/tools.jar" {

permission java.security.AllPermission;

};

第二步:执行jstat命令:

jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.2.9 &

(192.168.2.9为服务器ip地址, &表示用守护线程的方式运行)

第三步:打开本地的jdk/bin/jvisualvm.exe配置远程连接

连通后,将看到很多本地的java服务,点击可以查看各个服务的内存区域划分情况

3.6 内存配置

服务器上使用jps, 可以看到多出两个进程:

11523 Jstatd

6863 jar

ps -ef|grep 6863

可以查看JVM内存配置参数:

/usr/java/jdk1.8.0_121/bin/java -server

非堆区配置

// -XX:PermSize设置非堆内存初始值,默认是物理内存的1/64

// -XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4

对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出

永久代Perm在JDK7后已废弃,改为Metaspace,从JVM堆内存空间中独立出去,共享本地内存

元空间配置

-XX:MetaspaceSize 初始非堆区元空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。

-XX:MaxMetaspaceSize 最大非堆区元空间,默认是没有限制的。
-XX:MinMetaspaceFreeRatio 在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio 在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

线程堆栈配置

//-Xss1m  // 设置每个线程的堆栈大小,默认1M,适当减小该值能生成更多的线程

堆区配置

-Xms2048m  // JVM启动时整个堆内存(包括年轻代,年老代)的初始化大小,(s->starter)

一般Xms与Xmx保持一致,避免GC后重新分配内存

-Xmx2048m  // JVM运行时整个堆内存的最大值 (x->max)

-Xmn512m    // 新生代的空间大小,剩下的是年老代的空间 (n->new)

-XX:NewRatio=3  // 新生代 :老年代 = 1 :3(即Xmn占Xmx的1/4)(与-Xmn512m配置其一即可)

-XX:SurvivorRatio=8 // 新生代中 Eden :Survivor = 8 :2;

即Eden占新生代空间的 8/10;S0,S1各占 1/10; 因此新生代空间利用率最高可达90%

-XX:+UseConcMarkSweepGC // 使用CMS标记清理回收器

JDK7后推荐使用+UseG1GC,被称为G1类型(或Garbage First)的回收器

-XX:+PrintGCDetails // 打印 GC 信息 // + 启用选项; -不启用选项

-XX:+HeapDumpOnOutOfMemoryError // 让虚拟机在发生内存溢出时 dump 出当前的内存堆转储快照,以便分析时用

4. 垃圾回收

4.1 垃圾回收分类

Minor GC:

在创建新生对象时,如果Eden区空间不足就会触发一次Young GC 。此时会将 S0 区与Eden区的对象一起进行GC Roots可达性分析,然后将仍被使用或引用的活跃对象复制到 S1 区,并提升活跃对象的分代年龄,同时再将 S0 区域和 Eden 区的对象清空,最后将 S0 区 和 S1区交换,等待下次的Young GC。

Major GC:

当 Survvivor 空间不够用时,需要依赖老年代进行分配担保。

如创建大对象时,如果 Eden 区无法容纳会触发Minor GC,Minor GC后仍无法创建,会尝试直接在老年代创建,老年代空间不足,就会触发Major GC,Major GC后仍无法创建,则报OOM异常

Major GC发生在老年代 ,基本上发生了一次Major GC 就会发生一次 Minor GC。并且Major GC 的速度往往会比 Minor GC 慢 10 倍。

Full GC:

Full GC 时,会导致服务器STW(stop the world), 此时服务对外不可用,响应超时。

尽量避免Full GC: 避免定义过大的对象(数组) 以及 避免将过大对象定义为静态变量

JVM 调优,也就是最大化利用服务器资源开销,减少YGC, 避免FGC

4.2 垃圾回收算法

可达性分析:

 4.2.1 复制算法(适用于新生代)

4.2.2 标记-清除算法(适用于老年代)(存在内存碎片,无法存放新建的大对象)

 4.2.3 标记-整理算法(适用于老年代)

5. 类的加载

双亲委派模型:如果一个类加载器收到一个类加载的请求,它首先不会自己加载,而是把这个请求委派给父类加载器。只有父类无法完成时子类才会尝试加载。

双亲委派模型的好处:一是可以避免类的重复加载,二是可以避免java的核心API被篡改

 反射机制:

  • 隐式加载类(正射),通过 new 关键字创建类的实例
  • 显式加载类(反射):

       (1) ClassLoader.loadClass:得到的class是已经初始化完成的
       (2) forName:得到的class是还没有链接(验证,准备,解析三个过程被称为链接)

6. 线程调度

后记:

感觉东西太多了,简略整理出以上内容。

想深入探究,还是要拜读下《深入理解Java虚拟机》。

另说明本文参考了很多技术大佬的博客,比如:https://blog.csdn.net/qq_41701956/article/details/81664921

https://blog.csdn.net/Javazhoumou/article/details/99298624

非常感谢作者的分享!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值