JVM学习

jvm的体系结构

所以,jvm调优调的就是方法区和堆,而99%的情况是调堆

springboot自动装配原理

spring.factories中有自动配置的类,启动类启动后会根据引入的starter来进行加载,spring.factories中有@ConditionOnXXX的注解,比如说不存在某个类,不存在某个方法的时候,就不会进行加载,而对应的starter就是引入对应的类包,使判断成立,这样springboot才会加载,实现我们看到的自动加载

双亲委派机制

在不考虑自定义类加载器的情况下,一个类进行加载的时候,顺序是AppClassLoader --> ExtClassLoader --> BootstrapClassLoader,会不断的进行向上传递,之后再从顶上,也就是BootstrapClassLoader进行判断,是否可以加载这个类,不可以就让子类进行加载,循环这个过程,直到被加载,如果到最后还没有被加载,就会抛出ClassNotFoundException异常

保护程序安全,防止核心API被随意修改

(loadClass)

findLoadedClass(name) -- 检查该类是否已经被加载

parent.loadClass(name, false); -- 调用父类的类加载方法

findBootstrapClassOrNull(name) -- bootstrapclassloader 进行加载

findClass(name) -- 自己进行类加载

沙箱安全机制

Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

  所有的Java程序运行都可以指定沙箱,可以定制安全策略。

native method stack 本地方法栈

是一些java调用不到的方法,是由C、C++写的 ,比如说线程中的start方法

private native void start0();

这个方法在一个类中,就是因为native关键字,所以不报错

运行机制是,在native method stack中登记native方法,在Execution Engine执行引擎执行的时候加载Native Libraies本地库,具体是可以看jvm的体系结构里画的图

现在企业级基本不用,实现多语言访问可以使用http、socket通信等方式

PC寄存器

程序计数器 Program Counter Register

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计

方法区 method area

Method Area

方法区是被所有线程共享,所有字段和方法字节码、以及一些特殊方法, 如构造函数、接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间

静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关

栈 stack

也叫栈内存,对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就结束,栈是线程级的

栈经常与队列进行比较,

栈类似于一个桶,先进后出,后进先出

队列类似于一个管道,先进先出

栈里面主要存的东西是八大基本类型,对象引用,实例方法

栈运行原理,栈帧

栈堆满之后,也就是栈溢出,会抛出StackOverflowError

方法内的指令是保存在方法区,方法内定义的局部变量、方法的返回地址存在在栈帧中

三种JVm

  • Sun公司 HotSpot

  • BEA JRockit

  • IBM J9 VM

堆 heap

一个JVM只有一个堆,所以堆中的垃圾非常多,堆内存的大小是可以调节的。

GC垃圾回收,主要是在伊甸园区和养老区

假设堆内存满了,OOM,堆内存不够!java.lang.OutOfMemoryError: Java heap space

在JDK1.8以后,永久存储区改了个名字(元空间);

大概流程是这样的,new 一个对象的时候,对象的数据放到了堆中,对象的引用放到了栈中,刚new出来的时候,就在伊甸园区,当伊甸园区内存满了之后,会触发轻GC垃圾回收机制,将一些没有引用的对象清除,没有被清除的会被放到幸存区0区1区。当整个新生区都满了之后,会触发一次重GC垃圾回收机制,活下来的对象会放到养老区,当养老区也满了之后,就会报OOM

经调查,99%的对象都是临时对象,所以很少会有进入养老区的,也就是为什么很少见到OOM的原因

新生区

  • 类 诞生和成长的地方,甚至是死亡;

  • 伊甸园区: 所有的对象都是在伊甸园区new出来的

  • 幸存者区(0,1)

老年区

新生区活下来的对象就会进入老年区

永久区

这个区域是常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是java运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭JVM虚拟机就会释放这个区域的内存

一个程序什么时候会在永久区崩掉?

一个启动类加载了大量的第三方jar包。Tomcat不输了太多的应用,大量动态生成的反射类。不断地被加载,直到内存满,就会出现OOM

  • jdk1.6之前: 永久代,常量池是放在方法区的

  • jdk1.7 : 还是有永久代,但是慢慢的退化了,去永久代,常量池在堆中

  • jdk1.8之后 : 无永久代,常量池在元空间

(方法区中更小的一块是常量池)

可能逻辑上存在,物理上不存在,元空间也有个别名,被称作是非堆

如果报了OOM,可以手动 调参

-Xms1024m -Xmx1024m -XX:+PrintGCDetails

-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

使用该命令可以生成java dump 内存文件(内存快照)idea,可以看到具体是哪行代码出现的堆内存溢出

GC垃圾回收

垃圾回收器:

Serial GC:
它是最古老的垃圾收集器, “Serial”体现在其收集工作是单线程的,并且在进行垃圾收集过程中,会进入臭名昭著的“Stop-The-World”状态。当然,其单线程设计也意味着精简的GC实现,无需维护复杂的数据结构,初始化也简单,所以一直是Client模式下JVM的默认选项。
从年代的角度,通常将其老年代实现单独称作Serial Old,它采用了标记-整理(Mark-Compact)算法,区别于新生代的复制算法。Serial GC的对应JVM参数是:-XX:+UseSerialGC

ParNew GC:
很明显是个新生代GC实现,它实际是Serial GC的多线程版本,最常见的应用场景是配合老年代的CMS GC工作,下面是对应参数 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
CMS GC,基于标记-清除(Mark-Sweep)算法 设计目标是尽量减少停顿时间,这一点对于Web等反应时间敏感的应用非常重要,一直到今天,仍然有很多系统使用CMS GC。但是, CMS采用的标记-清除算法,存在着内存碎片化问题,所以难以避免在长时间运行等情况下发生full GC,导致恶劣的停顿。另外,既然强调了并发(Concurrent), CMS会占用更多CPU资源,并和用户线程争抢。

Parrallel GC:
在早期JDK 8等版本中,它是server模式JVM的默认GC选择,也被称作是吞吐量优先的GC。它的算法和Serial GC比较相似,尽管实现要复杂的多,其特点是新生代和老年代GC都是并行进行的,在常见的服务器环境中更加高效。开启选项是:-XX:+UseParallelGC 另外, Parallel GC引入了开发者友好的配置项,我们可以直接设置暂停时间或吞吐量等目标, JVM会自动进行适应性调整,例如下面参数:

-XX:MaxGCPauseMillis=value,
-XX:GCTimeRatio=N	//GC时间和用户时间比例 = 1 / (N+1)

查看jdk垃圾收集器:java -XX:+PrintCommandLineFlags -version 

G1 GC:

这是一种兼顾吞吐量和停顿时间的GC实现,是Oracle JDK 9以后的默认GC选项。 G1可以直观的设定停顿时间的目标,相比于CMS GC, G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多。
G1 GC仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个region。 Region之间是复制算法,但整体上实际可看作是标记-整理(MarkCompact)算法,可以有效地避免内存碎片,尤其是当Java堆非常大的时候, G1的优势更加明显。
G1吞吐量和停顿表现都非常不错,并且仍然在不断地完善,与此同时CMS已经在JDK 9中被标记为废弃(deprecated),所以G1 GC值得你深入掌握。

目前尚处于开发中的 JDK 11,你会发现,JDK 又增加了两种全新的 GC 方式,分
别是:
Epsilon GC:
简单说就是个不做垃圾收集的 GC,似乎有点奇怪,有的情况下,例如在进行
性能测试的时候,可能需要明确判断 GC 本身产生了多大的开销,这就是其典型应用场景。

ZGC:
这是 Oracle 开源出来的一个超级 GC 实现,具备令人惊讶的扩展能力,比如支持 T
bytes 级别的堆大小,并且保证绝大部分情况下,延迟都不会超过 10 ms。虽然目前还处于
实验阶段,仅支持 Linux 64 位的平台,但其已经表现出的能力和潜力都非常令人期待。

垃圾回收器原文链接:https://blog.csdn.net/weixin_43258908/article/details/89287217

根据之前的学习,知道了GC垃圾回收的作用区域是方法区和堆,而百分之99的区域都是在堆中

JVM在进行GC时,不是对这三个区域进行统一回收,大部门时候,都是对新生代进行回收

新生代、幸存区(from、to 谁空谁是to)、老年代

GC分为两类:轻GC(普通的GC,主要是对新生代和幸存区)、重GC(全局GC,全局进行回收)

GC题目:

  • JVM的内存模型和分区,详细到每个区放什么

  • 堆里面的分区有哪些,eden、from、to、老年区,说说他们的特点

  • GC的算法有哪些,标记清除法、标记压缩、复制算法、引用计数器,怎么用的

  • 轻GC和重GC分别发生在什么时候

GC垃圾回收的算法有多种:

引用计数法

标记清除法

上面是标记,将活着的对象进行标记,下面是清除,清除掉死了的对象,

优点:不需要额外的空间,比如说空着的to区

缺点:两次扫描,严重浪费时间,同时有内存碎片问题

标记压缩法

标记清除压缩法

标记、清除、压缩合起来就是 标记清除压缩法

优化: 先标记清除多次,内存碎片就会很多,这个时候进行一次压缩

复制算法

新生区(Eden区,幸存区from,幸存区to)主要用的就是复制算法,谁空谁是to区

每次GC都会将Eden活的对象移到幸存区中,一旦Eden区被GC后,就会是空的

当一个对象经历了15次GC(默认值),都还没有死,就会从幸存区转到老年区

-XX: -XXMaxTenuringThreshold=999 通过这个参数可以设定进入老年代的时间

  • 好处:没有内存碎片

  • 坏处:浪费了内存空间,多了一半空间永远是空to,假设对象百分百存活(极端情况),就需要把Eden区和from幸存区的所有对象存入到幸存to区,消耗就会非常大,出现oom等问题,所以,复制算法最佳使用场景就是对象存活度较低的时候,比如说新生区

总结

内存效率:复制算法 > 标记清除法 > 标记清除压缩算法 (时间复杂度)

内存整齐度:复制算法 = 标记清除压缩算法 > 标记清除法

内存利用率:标记清除压缩算法 = 标记清除法 > 复制算法

没有最好的算法,只有最合适的算法 ---> GC: 分代收集算法

年轻代:

  • 存活率低

  • 复制算法

老年代:

  • 区域大,存活率高

  • 标记清除(内存碎片不是太多) + 标记压缩混合实现 JVM调优--> 进行几次标记清除,再进行压缩

JMM -- Java Memory Model

解决共享对象可见性这个问题:volatile

同步机制:Synchronized

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值