jvm、垃圾回收、类加载(话术)

jvm、垃圾回收、类加载(话术)
[必会]JVM的内存模型

Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成
[必会]JVM的内存区域

JVM内存主要由 方法区、虚拟机栈、本地方法栈、堆 和 程序计数器组成,
其中方法区和堆是所有线程共享的区域,
方法区:

呢主要存放的是类的信息,常量,静态变量等信息
java虚拟机栈

虚拟机主要是存储的是栈帧 栈帧说白了就是描述我们所要调用方法的上下文 比如传递的参数啊,局部变量啊,或者是调用对象的引用等等,我们每调用一个方法就会创建一个栈帧加入到虚拟机栈中,方法执行完会出栈。
这里面有可能抛两种异常 一个栈溢出异常 一个内存溢出异常。
本地方法栈

和虚拟机栈很像不同的是虚拟机栈是执行java方法 而本地方法栈是为执行native方法而服务。(native方法就是一些其它语言的方法在java中使用native做标识),

则是内存当中最大的一块区域了,我们创建的对象基本上都在这里存放,这里也是GC垃圾回收的主要区域,因为现在的很多收集器都是采用分代算法,所以堆又分为 新生代 和 老年代 如果在细致点分 新生代又可以分为Eden (伊甸园)空间、From Survivor (幸存者)空间、To Survivor 空间,如果内存不够也会报内存溢出异常。
程序计数器

主要是记录当前线程执行到哪了,如果执行的是native方法 那计数器的值就为undefined
[必会]java的垃圾回收机制
概念介绍

这个应该算我们JAVA语言当中很重要的一个特性了,我们在开发中内存区域的资源很宝贵,有很多对象使用过后是需要清理的,java引用了GC垃圾收集器,当Java虚拟机发觉内存资源紧张的时候,就会自动地去清理无用对象所占用的内存空间。如果需要,可以在程序中显式地使用System.gc()来强制进行一次立即的内存清理。
工作原理

JVM 的垃圾回收机制中,虚拟机会根据可达性分析判断一个对象可达,对于不可达的对象 会被判断为可回收的对象进行回收处理
可达性分析

对象之间的引用可以抽象成树形结构,通过树根(GC Roots)作为起点,从这些树根往下搜索,搜索走过的链称为引用链,当一个对象到 GC Roots 没有任何引用链相连时,则证明这个对象是不可用的,该对象会被判定为可回收的对象。
哪些变量可以作为GCRoot : 方法区中的常量 、 static的变量 、 java虚拟机栈中的变量
回收算法

回收机制的算法:标记-清除 复制 标记-整理 分代算法
标记-清除

标记-清除算法采用从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收
标记-整理

标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。
复制

它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。一种典型的基于coping算法的垃圾回收是stop-and-copy算法,它将堆分成对象面和空闲区域面,在对象面与空闲区域面的切换过程中,程序暂停执行。
分代算法 (能简述即可)

分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。
年轻代(Young Generation)
1.所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
2.新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
3.当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收
4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)
年老代(Old Generation)
1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
2.内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
持久代(Permanent Generation)
用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。
[必会]ClassLoader类加载机制
概念

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制
哪些场景会触发类加载(必会)

1.new关键字实例化对象时、读取或者设置一个类的静态字段时、以及调用一个类的静态方法的时候如果类没有进行过初始化,则需要先触发其初始化。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3.当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要触发父类的初始化。
4.当虚拟机启动时,用户需要指定一个执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
整个生命周期包括:(必会)
加载、验证、准备、解析、初始化、使用和卸载七个阶段。
类加载器分类(必会)
Bootstrap ClassLoader
Extension ClassLoader
Application ClassLoader
双亲委派模型(必会)
如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
好处:java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类会出现不同版本
GC 垃圾回收器的分类(选看:高薪专用)
垃圾回收器分类
1、串行:垃圾回收器 (Serial Garbage Collector)
(1)串行垃圾回收器在进行垃圾回收时,它会持有所有应用程序的线程,冻结所有应用程序线程,使用单个垃圾回收线程来进行垃圾回收工作。
串行垃圾回收器是为单线程环境而设计的,如果你的程序不需要多线程,启动串行垃圾回收。
(2)串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)
使用方法:-XX:+UseSerialGC 串联收集
2、串行:ParNew收集器
ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩
使用方法:-XX:+UseParNewGC ParNew收集器
3、并行:Parallel收集器
Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩
4、并行:Parallel Old 收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供
使用方法: -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行
5、并发标记扫描CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。
6、G1收集器
G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS收集器相比G1收集器有以下特点:
(1). 空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
(2). 可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
选用总结
垃圾回收器目前分为6种类型, 串行,并行,并发标记,G1。
小数据量和小型应用,使用串行垃圾回收器即可。
对于对响应时间无特殊要求的,可以使用并行垃圾回收器和并发标记垃圾回收器。(中大型应用)
对于heap可以分配很大的中大型应用,使用G1垃圾回收器比较好,进一步优化和减少了GC暂停时间。
没有银弹,针对不同的场景,选用不同的垃圾回收器。
常用组合:
Serial
ParNew + CMS
ParallelYoung + ParallelOld
G1GC

JVM配置调优及问题分析(选看:高薪专用)
JVM参数列表
堆设置

-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
收集器选择

-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息

-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置

-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置

-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
JVM优化技巧(选看:高薪专用)

1:建议用64位操作系统 Linux下64位的jdk比32位jdk要慢一些,但是吃得内存更多,吞吐量更大。
2:XMX和XMS设置一样大,MaxPermSize和MinPermSize设置一样大,这样可以减轻伸缩堆大小带来的压力。
3:调试的时候设置一些打印参数,如-XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log,这样可以从gc.log里看出一些端倪出来。
4:系统停顿的时候可能是GC的问题也可能是程序的问题,多用jmap和jstack查看,或者killall -3 java,然后查看java控制台日志,能看出很多问题。有一次,网站突然很慢,jstack一看,原来是自己写的URLConnection连接太多没有释放,改一下程序就
5:仔细了解自己的应用,如果用了缓存,那么年老代应该大一些,缓存的HashMap不应该无限制长,建议采用LRU算法的Map做缓存,LRUMap的最大长度也要根据实际情况设定。
6:垃圾回收时promotion failed是个很头痛的问题,一般可能是两种原因产生,第一个原因是救助空间不够,救助空间里的对象还不应该被移动到年老代,但年轻代又有很多对象需要放入救助空间;第二个原因是年老代没有足够的空间接纳来自年轻代的对象;这两种情况都会转向Full GC,网站停顿时间较长。第一个原因我的最终解决办法是去掉救助空间,设置-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0即可,第二个原因我的解决办法是设置CMSInitiatingOccupancyFraction为某个值(假设70),这样年老代空间到70%时就开始执行CMS,年老代有足够的空间接纳来自年轻代的对象。
7:不管怎样,永久代还是会逐渐变满,所以隔三差五重起java服务器是必要的,我每天都自动重起。
8:采用并发回收时,年轻代小一点,年老代要大,因为年老大用的是并发回收,即使时间长点也不会影响其他程序继续运行,网站不会停顿。
JVM问题定位分析常用工具(选看:高薪专用)

JDK的安装目录中,提供了很多有用实用的JVM问题分析工具
命令行工具

https://www.cnblogs.com/zengweiming/p/8946195.html
jps(Java Virtual Machine Process Status Tool)
查看所有的jvm进程,包括进程ID,进程启动的路径等等。
jstack(Java Stack Trace)
① 观察jvm中当前所有线程的运行情况和线程当前状态。
② 系统崩溃了?如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。
③ 系统hung住了?jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。
jstat(Java Virtual Machine Statistics Monitoring Tool)
① jstat利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对进程的classloader,compiler,gc情况;
②监视VM内存内的各种堆和非堆的大小及其内存使用量,以及加载类的数量。
jmap(Java Memory Map)
监视进程运行中的jvm物理内存的占用情况,该进程内存内,所有对象的情况,例如产生了哪些对象,对象数量;
jinfo(Java Configuration Info)
观察进程运行环境参数,包括Java System属性和JVM命令行参数
可视化工具

https://blog.csdn.net/qq_31156277/article/details/80035430

Jconsole (Java Monitoring and Management Console),一种基于JMX的可视化监视、管理工具。

VisualVM(All-in-One Java Troubleshooting Tool) 功能最强大的运行监视和故障处理程序
加载过程及生命周期详解 (选看:高薪专用)

类从被加载到虚拟机内存开始,到卸载出内存为止,
整个生命周期包括:
加载、验证、准备、解析、初始化、使用和卸载七个阶段。
其中加载、验证、准备、初始化、和卸载这 5 个阶段的顺序是确定的。
而解析阶段不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的运行时绑定。
关于初始化:JVM 规范明确规定,有且只有 5 中情况必须执行对类的初始化(加载、验证、准备自然再此之前要发生):
加载

加载过程主要做以下 3 件事

通过一个类的全限定名称来获取此类的二进制流
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的 java.lang.Class 对象, 作为方法区这个类的各种数据访问入口。
验证
这个阶段主要是为了确保 Class 文件字节流中包含信息符合当前虚拟机的要求,并且不会出现危害虚拟机自身的安全。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都在方法区中分配。首先,这个时候分配内存仅仅包括类变量(被 static 修饰的变量),而不包括实例变量。实例变量会在对象实例化时随着对象一起分配在 java 堆中。其次这里所说的初始值 “通常情况下” 是数据类型的零值,假设一个类变量定义为 解析 解析阶段是把虚拟机中常量池的符号引用替换为直接引用的过程。
初始化
类初始化时类加载的最后一步,前面类加载过程中,除了加载阶段用户可以通过自定义类加载器参与以外,其余动作都是虚拟机主导和控制。
到了初始化阶段,才是真正执行类中定义 Java 程序代码。 准备阶段中,变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划初始化类变量。初始化过程其实是执行类构造器
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值