JVM整理

1.JVM内存结构 

Jvm 的主要组成部分?及其作⽤?
1. 类加载器( ClassLoader
2. 运⾏时数据区( Runtime Data Area
3. 执⾏引擎( Execution Engine
4. 本地库接⼝( Native Interface
各组件的作⽤:⾸先通过类加载器( ClassLoader )会把 Java 代码转换成字节码,运⾏时数据区( Runtime Data Area)再把字节码加载到内存中,⽽字节码⽂件只是 JVM 的⼀套指令集规范,并不能直接交给底层操作系统去执 ⾏,因此需要特定的命令解析器执⾏引擎(Execution Engine ),将字节码翻译成底层系统指令,再交由 CPU 去 执⾏,⽽这个过程中需要调⽤其他语⾔的本地库接⼝(Native Interface )来实现整个程序的功能。

方法区:存放已经加载的类信息,常量,静态变量。即永久代。在1.8中不存在方法区了,被元数据区替代了,原方法区被分为:1.加载的类信息;2.运行时常量池。加载的类信息被保存到元数据区中,运行时常量池保存在堆中。

由于 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执⾏时间的⽅式来实现的,在任何⼀个确定的时刻,
⼀个处理器都只会执⾏⼀条线程中的命令。因此,为了线程切换后能恢复到正确的执⾏位置,每条线程都需要有⼀
个独⽴的程序计数器,各线程之间的计数器互不影响,独⽴存储,我们程这块内存区域为 线程私有 的内存。
此区域是唯⼀ ⼀个虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
2. Java 虚拟机栈
Java 虚拟机栈( Java Virtual Machine Stacks ):描述的是 Java ⽅法执⾏的内存模型:每个⽅法在执⾏的同时都会 创建⼀个帧栈(Stack Frame )⽤于存储局部变量表、操作数栈、动态链接、⽅法出⼝等信息。每⼀个⽅法从调⽤ 直⾄执⾏完成的过程,就对应着⼀个栈帧在虚拟机栈中⼊栈到出栈的过程。它的线程也是私有的,⽣命周期与线程 相同。
3. 本地⽅法栈
本地⽅法栈( Native Method Stack ):与虚拟机栈所发ഀ的作⽤是⾮常相似的,它们之间的区别只不过是虚拟机 栈为虚拟机执⾏Java ⽅法(也就是字节码)服务,⽽本地⽅法栈则为虚拟机使⽤到的 Native ⽅法服务。
Java 虚拟机规范没有对本地⽅法栈中⽅法使⽤的语⾔、使⽤的⽅式和数据结构做出强制规定,因此具体的虚拟机可 以⾃由地实现它。⽐如:Sun HotSpot 虚拟机直接把 Java 虚拟机栈和本地⽅法栈合⼆为⼀。 与Java 虚拟机栈⼀样,本地⽅法栈也会抛出 StackOverflowError OutOfMemoryError 异常。
4. Java
Java 堆( Java Heap ):是被所有线程所共享的⼀块内存区域,在虚拟机启动时创建。此内存区域的唯⼀⽬的就
是:存放对象实例,⼏乎所有的对象实例都在这⾥分配内存。
5. ⽅法区
⽅法区( Method Area ):与 Java 堆⼀样,是各个线程共享的内存区域,它⽤于存储已被虚拟机加载的类信息、 常量、静态变量、即时编译器编译后的代码等数据。
虽然 Java 虚拟机规范把⽅法区描述为堆的⼀个逻辑部分,但是它却有⼀个别名叫做 Non-Heap (⾮堆),其⽬的 应该就是与 Java 堆区分开来。
堆中存的是对象。栈中存的是基本数据类型和堆中对象的引⽤。⼀个对象的⼤⼩是不可估计的,或者说是可以动态
变化的,但是在栈中,⼀个对象只对应了⼀个 4btye 的引⽤(堆栈分离的好处)。
为什么不把基本类型放堆中呢?
因为基本数据类型占⽤的空间⼀般是 1~8 个字节,需要空间⽐较少,⽽且因为是基本类型,所以不会出现动态增⻓ 的情况,⻓度固定,因此栈中存储就够了。如果把它存在堆中是没有什么意义的。基本类型和对象的引⽤都是存放 在栈中,⽽且都是⼏个字节的⼀个数,因此在程序运⾏时,它们的处理⽅式是统⼀的。但是基本类型、对象引⽤和 对象本身就有所区别了,因为⼀个是栈中的数据⼀个是堆中的数据。最常⻅的⼀个问题就是,Java 中参数传递时的 问题。

 

2.Java内存模型(JMM)

处理器和内存不是同数量级,所以需要在中间建⽴中间层,也就是⾼速缓存,这会引出缓存⼀致性问题。在多处理 器系统中,每个处理器都有⾃⼰的⾼速缓存,⽽它们⼜共享同⼀主内存(Main Memory ),有可能操作同⼀位置 引起各⾃缓存不⼀致,这时候需要约定协议在保证⼀致性。
Java 内存模型 (Java Memory Model JMM) :屏蔽掉了各种硬件和操作系统的内存访问差异,以实现让 Java 程序 在各种平台下都能达到⼀致性的内存访问效果它明确指定了一组排序规则,来保证线程间的可见性。

(1)volatile:保证可见性和有序性;

(2)synchronized:保证可见性(在unlock前写入内存)和有序性(只能一个线程占有锁)。通过管程(Monitor)保证原子性。但是不保证禁止指令重排

代码块的同步是利用monitorenter和monitorexit这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当jvm执行到monitorenter指令时,当前线程试图获取monitor对象的所有权,如果未加锁或者已经被当前线程所持有,就把锁的计数器+1;当执行monitorexit指令时,锁计数器-1;当锁计数器为0时,该锁就被释放了。如果获取monitor对象失败,该线程则会进入阻塞状态,直到其他线程释放锁。
(3)final保证可见性。

3.heap和stack

(1)heap由程序员自己申请,并指明大小;stack由系统分配,存局部变量。

(2)stack是连续的内存结构,默认大小2M;而堆不连续

4.栈溢出

私有,每个方法执行都会创建一个栈,当方法递归可能会出现。通过调整参数-xss调整栈大小。

5.OOM

除了程序计数器,其他都会发生OOM。

(1)栈发生stackoverflowerror,无限创建线程就会发生栈的OOM;(2)常量池在堆中,溢出会发出OutOfMemoryError。(3)堆内存溢出,报错同上;(4)方法区:动态生成大量的类,jsp

(5)直接内存OOM,涉及-XX:MaxDirectMemorySize参数和unsafe对象对内存的申请。

 6.Java常量池

7.判断一个对象是否存活

(1)引用计数法,无法解决循环引用,比如A引用B,B引用A,不采用。

(2)可达性分析法:从一个GC Roots对象向下搜索,如果没有任何引用链连接就不可用。

GC Roots对象:(1)方法区静态变量;(2)JVM 栈中引用的对象(3)方法区中常量池引用的对象(4)本地方法栈引用的对象。

还需要两次标记:第一次,判断当前对象有没有finalize()方法,若不存在就记为垃圾等待回收。与GCROOTS想连接。若有的话进行第二次标记,放入一个队列,生成一个finalize线程执行,执行后仍然没有于GC Roots对象有引用就被回收。

 

8.四种引用

(1)强引用:如string s=new string

(2)软引用:可有可无,当内存不足就回收,回收后还是不足就抛出内存溢出

(3)弱引用:无论内存足不足都会被回收

(4)虚引用:跟踪垃圾回收对象的活动。

被引用的对象不一定存活,要看引用类型。

9.垃圾回收算法

1.标记清除法:产生不连续的内存碎片,导致大对象分配不了再触发GC

2.标记整理法:适用存活对象多,垃圾少的情况。

3.复制法:将内存分为两块;不会产生内存碎片,缺点内存适用率低。

4.分代收集法:新生代采用复制,老年代标记整理或清除。

10.垃圾回收器

年轻代(复制算法);serial,ParNew,Parallel Scavenge

老年代:标记整理(serial old,parallel old),标记清除(CMS),

年轻代和老年代:G1

11.CMS回收过程

并发标记清除以获取最短回收停顿时间为目标的收集器,在垃圾收集时使得用户线程和GC线程并发执行。

(1)初始标记,仅标记GC ROOT的下一级对象,过程中会STW,但是跟GC ROOT直接关联的对象不多,时间段;

(2)并发标记:根据上一步结果,继续向下直到尽头。过程是多线程的,耗时长,但工作线程不会阻塞,没有STW;

(3)重新标记:标记在第二步中产生的新的垃圾;

(4)并发清除:清除删掉判断已经死亡的对象,由于不需要移动存活对象,所以这个阶段是与用户线程同时并发执行的。

缺点:(1)并发回收导致CPU资源紧张;(2)无法清理浮动垃圾:在并发清理时产生的垃圾只能下一次GC;(3)并发失败,由于并发需要挤出一部分空间给并发回收的程序适用,如老年代92%就触发,而不能100%;(4)内存碎片,可以通过命令在下一次前整理

12.G1回收过程

G1 可谓博采众家之⻓,⼒求到达⼀种完美。它吸取了增量收集优点,把整个堆划分为⼀个⼀个等⼤⼩的区域 (region )。内存的回收和划分都以 region 为单位;同时,它也吸取了 CMS 的特点,把这个垃圾回收过程分为⼏ 个阶段,分散⼀个垃圾回收过程;⽽且,G1 也认同分代垃圾回收的思想,认为不同对象的⽣命周期不同,可以采 取不同收集⽅式,因此,它也⽀持分代的垃圾回收。为了达到对回收时间的可预计性,G1 在扫描了 region 以后, 对其中的活跃对象的⼤⼩进⾏排序,⾸先会收集那些活跃对象⼩的 region ,以便快速回收空间(要复制的活跃对象少了),因为活跃对象⼩,⾥⾯可以认为多数都是垃圾,所以这种⽅式被称为 Garbage First G1 )的垃圾回收算 法,即:垃圾优先的回收。

(1)初始标记(会STW),也是只标记下一级关联的对象

(2)并发标记

(3)最终标记(会STW):对用户线程做短暂的暂停,处理并发阶段结束后任有引用变动的对象。

(4)清理阶段(STW):会更新region的回收价值和成本排序,把决定回收的一部分region复制到空,再清理。这里涉及存活对象的移动,必须暂停用户线程。

13.完整的GC

新生代1/3,老年代2/3;新生代分为Eden,to survivor,from survivor,比例8:1:1;

新生代复制,因为少量对象存活。

 14.空间分配担保机制

如果young GC后存在大量对象存活,而survivor放不下,老年代也放不下。这时老年代就会先检测-xx:HandlePromotionFailure参数是否允许担保失败。允许,判断老年代最大可用连续空间是否大于晋升对象的平均大小,大于就进行young GC,小于就full GC;

15.类加载

类的生命周期:加载,验证,准备(为class对象的静态变量分配内存并初始化),解析(符号引用转换为直接引用),初始化(调用类构造器的过程),适用和卸载。其中,验证准备解析称为连接。

加载过程:(1)通过类的全限定性类名获取类的二进制流;(2)转为方法区的运行时数据结构;(3)再堆中生成一个class对象。

初始化
在准备阶段,变量已经赋过⼀次系统要求的初始值了,⽽在初始化阶段,则根据程序员通过程序制定的主观计划去 初始化类变量和其他资源,或者可以从另外⼀个⻆度来表达:初始化阶段是执⾏类构造器 () ⽅法的过程。

16.类加载器:启动类加载器(加载Java核心类库),扩展类加载器(加载Java扩展库),系统类加载器(加载我们自己写的应用类),自定义类加载器(继承自class loader)

17.双亲委派机制

防止内存中出现多个相同的字节码;因为如果没有双亲委派,用户就可以自己定义一个String类,无法保证类的唯一性。

打破:自定义类加载器,继承class loader,重写loadclass和findclass方法。

 

 18.JVM调优命令:

(1)jsp:JVM 显示指定系统内所有的hotspot虚拟机进程。

(2)jstate:监控虚拟机运行时状态信息的命令

(3)jmap:生成heap dump文件。或者-XX:HeapDumpOnOutOfMemoryError让OOM时自动生成dump文件。还可以查询finalize执行队列,堆的适用和哪种收集器。

(4)jhat:和jmap一起适用,分析jmap生成的headdump文件,一般把dump文件复制到浏览器查看

(5)jstack:生成虚拟机当前时刻的线程快照,查看各个线程的调用堆栈

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值