JVM复习

1.JVM类加载机制

1.1JVM内存模型
JVM运行时内存 =共享内存区 + 线程内存区
在这里插入图片描述
线程共享内存区 = 持久带(方法区 + 其他)+ 堆(Old Space + Young Space(den + S0 + S1))
在这里插入图片描述
持久代(方法区):
JVM用持久带(Permanent Space)实现方法区,主要存放所有已加载的类信息,方法信息,接口定义,常量池等等。可通过-XX:PermSize和-XX:MaxPermSize来指定持久带初始化值和最大值。Permanent Space并不等同于方法区,只不过是Hotspot JVM用Permanent Space来实现方法区而已,有些虚拟机没有Permanent Space而用其他机制来实现方法区。
堆(heap):
Heap, 一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把什么东西放到堆中?
类, 方法,常量,变量~,保存我们所有引用类型的真实对象;
堆内存中还要细分为三个区域:
●新生区(伊甸园区) Young/New
●养老区old
●永久区Perm
图 堆内存详细划分
GC垃圾回收,主要是在伊甸园区和养老区~
假设内存满了,OOM,堆内存不够! java.lang.OutOfMemoryError:Java heap space
永久存储区里存放的都是Java自带的 例如lang包中的类 如果不存在这些,Java就跑不起来了
在JDK8以后,永久存储区改了个名字(元空间)
新生区:
●类:诞生和成长的地方,甚至死亡;
●伊甸园,所有的对象都是在伊甸园区new出来的!
●幸存者区(0,1)
图 重GC和轻GC
伊甸园满了就触发轻GC,经过轻GC存活下来的就到了幸存者区,幸存者区满之后意味着新生区也满了,则触发重GC,经过重GC之后存活下来的就到了养老区。
真理:经过研究,99%的对象都是临时对象!|

永久区
这个区域常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境~ 这个区域不存在垃圾回收,关闭虚拟机就会释放内存
●jdk1.6之前:永久代,常量池是在方法区;
●jdk1.7:永久代,但是慢慢的退化了,去永久代,常量池在堆中
●jdk1.8之后:无永久代,常量池在元空间
在这里插入图片描述
元空间:逻辑上存在,物理上不存在 (因为存储在本地磁盘内) 所以最后并不算在JVM虚拟机内存中
线程私有内存区
在这里插入图片描述
线程内存区(JVM栈):
在这里插入图片描述
图 为什么main()先执行,最后结束~

在这里插入图片描述
**************图 栈的位置示意图
栈:栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题
栈:栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收
线程内存区=单个线程内存+单个线程内存+…
单个线程内存=PC Regster+JVM栈+本地方法栈
JVM栈=栈帧+栈帧+…
栈帧=局域变量区+操作数区+帧数据区
在Java中,一个线程会对应一个JVM栈(JVM Stack),JVM栈里记录了线程的运行状态。JVM栈以栈帧为单位组成,一个栈帧代表一个方法调用。栈帧由三部分组成:局部变量区、操作数栈、帧数据区。
线程在栈区,不能共享数据,只能通过复制共享区的数据作为一块缓存,所有多线程写会有bug,voliate使得取到的数据不做缓存,是实时更新的。
补:
关键字 volatile 是轻量级的同步机制。
Volatile 变量对于all线程的可见性,指当一条线程修改了这个变量的值,新值对于其他 线程来说是可见的、立即得知的。 Volatile 变量在多线程下不一定安全,因为他只有可见性、有序性,但是没有原子性。

1.2JVM内存溢出的情况
在这里插入图片描述
1.3常见JVM内存溢出(OOM)的原因和解决方法
1.堆溢出

java.lang.OutOfMemoryError: Java heap space

原因
1、代码中可能存在大对象分配
2、可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。

解决方法:
1、检查是否存在大对象的分配,最有可能的是大数组分配
2、通过jmap命令,把堆内存dump下来,使用mat工具分析一下,检查是否存在内存泄露的问题
3、如果没有找到明显的内存泄露,使用 -Xmx 加大堆内存
4、还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性

2.永久代/元空间溢出

java.lang.OutOfMemoryError: PermGen spacejava.lang.OutOfMemoryError: Metaspace

原因
永久代是 HotSot 虚拟机对方法区的具体实现,存放了被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。
JDK8后,元空间替换了永久代,元空间使用的是本地内存,还有其它细节变化:
字符串常量由永久代转移到堆中和永久代相关的JVM参数已移除
可能原因有如下几种:
1、在Java7之前,频繁的错误使用String.intern()方法
2、运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载
3、应用长时间运行,没有重启
解决方法
因为该OOM原因比较简单,解决方法有如下几种:
1、检查是否永久代空间或者元空间设置的过小
2、检查代码中是否存在大量的反射操作
3、dump之后通过mat检查是否存在大量由于反射生成的代理类
4、放大招,重启JVM

3.方法栈溢出

java.lang.OutOfMemoryError : unable to create new native Thread

原因

出现这种异常,基本上都是创建的了大量的线程导致的,以前碰到过一次,通过jstack出来一共8000多个线程。

解决方法

1、通过 -Xss 降低的每个线程栈大小的容量
2、线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制:

  • /proc/sys/kernel/pid_max
  • /proc/sys/kernel/thread-max
  • maxuserprocess(ulimit -u)
  • /proc/sys/vm/maxmapcount

1.4JVM类加载阶段:
加载->连接(验证->准备->解析)->初始化->卸载 五部分

1.加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象

2.初始阶段,才开始真正执行类中定义的 Java 程序代码。初始化阶段是执行类构造器方法的过程

1.5类加载器详解
作用:加载Class文件
图
类在经过Class Loader之后的变化
JVM 提供了 3 种类加载器
在这里插入图片描述
类加载方式总结:
隐式加载:new
显示加载:loadClass 和 forName
Class.forName 得到的 class 时已经初始化完成的
ClassLoader.loadClass 得到的 class 是还没有连接的,Spring 的延迟加载使用 classloder.loadClass
1.6双亲委派机制
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的 Class),子类加载器才会尝试自己去加载。采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象。
在这里插入图片描述
1.7沙箱安全机制
Java安全模型的核心就是Java沙箱(sandbox) ,
 什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。
 沙箱主要限制系统资源访问,那系统资源包括什么? CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
 所有的Java程序运行都可以指定沙箱,可以定制安全策略。
 在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱Sandbox)机制。
Java1.1版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限
Java1.2版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制
最新的安全机制实现,则引入了域(Domain)的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain),对应不一样的权限(Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示最新的安全模型(jdk 1.6)
在这里插入图片描述
图 JDK1.6安全模型
组成沙箱的基本组件
●字节码校验器(bytecode verifier) :确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
●类裝载器(class loader) :其中类装载器在3个方面对Java沙箱起作用
  它防止恶意代码去干涉善意的代码;
  它守护了被信任的类库边界;
  它将代码归入保护域,确定了代码可以进行哪些操作。
 虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成, 每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。
 类装载器采用的机制是双亲委派模式。
 1.从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
 2.由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
●存取控制器(access controller) :存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
●安全管理器(security manager) : 是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
●安全软件包(security package) : java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:
  安全提供者
  消息摘要
  数字签名
  加密
  鉴别

1.8 Native 关键字

private native void start0();

native :凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层c语言的库!
 会进入本地方法栈
 调用本地方法本地接口 JNI (Java Native Interface)
 它在内存区域中专门开辟了一块标记区域: Native Method Stack,登记native方法
它的初衷是融合C/C++程序, Java在诞生的时候是C/C++横行的时候,想要立足,必须有调用C、C++的程序,于是就在内存中专门开辟了块区域处理标记为native的代码,它的具体做法是在Native Method Stack 中登记native方法,在( Execution Engine )执行引擎执行的时候加载Native Libraies。
 在最终执行的时候,加载本地方法库中的方法通过JNI
 例如:Java程序驱动打印机,管理系统,掌握即可
 //调用其他接口:Socket. . WebService~. .http~

2.JVM内存区域划分

在这里插入图片描述
在这里插入图片描述
JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区 域【JAVA 堆、方法区】、直接内存。

线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 Hotspot VM 内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的 生/死对应)。

线程共享区域随虚拟机的启动/关闭而创建/销毁。
直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提 供了基于Channel 与 Buffer 的 IO 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后使用DirectByteBuffer 对象作为这块内存的引用进行操作

3.GC(垃圾回收)

在这里插入图片描述
JVM在进行GC时,并不是对这三个区域统-回收。大部分时候,回收都是新生代~
●新生代
●幸幸区(form, to)
●老年区
GC两种类:轻GC (普通的GC),重GC (全局GC)
**3.1引用计数法
引用计数器:
在这里插入图片描述
引用计数法 很少使用了
3.2复制算法
按内存容量将内存划分为等大小 的两块。每次只
使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用 的内存清掉,如图:
在这里插入图片描述
在这里插入图片描述
图 复制算法大致图
●好处:没有内存的碎片~
●坏处:浪费了内存空间~ :多了一半空间永远是空to。假设对象100%存活(极端情况)
复制算法最佳使用场景:对象存活度较低的时候;新生区~
3.3标记清除
标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间
●优点:不需要额外的空间!
●缺点:两次扫描,严重浪费时间,会产生内存碎片。
3.4标记压缩
再优化:
加粗样式
3.5分代收集
分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不
同的域,目的是提高 JVM 的回收效率。一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young
Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃 圾回收时都有大
量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
总结:
内存效率: 复制算法> 标记清除算法 > 标记清除压缩算法
内存整齐度: 复制算法=标记压缩算法 > 标记清除算法
内存利用率: 标记压缩算法 = 标记清除算法 > 复制算法
年轻代: 存活率低,复制算法;
老年代: 存活率高, 标记清除算法+ 标记清除压缩算法

4.JAVA引用类型和对象的复制(了解)

在 JDK 1.2 以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从 JDK 1.2 版本开始,把对象的引用分为 4 种级别,从而使程序能更加灵活地控制对象的生命周期。这 4 种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

4.1强引用
4.2软引用
4.3弱引用
4.4虚引用

4.5直接赋值复制
4.6浅复制(复制引用但不复制引用的对象)
4.7深复制(复制对象和其应用对象)
4.8 序列化(深 clone 一种实现)

5.JMM(Java内存模型)

https://baijiahao.baidu.com/s?id=1654026271456285249&wfr=spider&for=pc

https://www.jianshu.com/p/8420ade6ff76

https://www.jianshu.com/p/8a58d8335270

CPU和缓存一致性
程序的处理过程

处理器优化和指令重排
并发编程的三个特性:
什么是内存模型
什么是Java内存模型
Java内存模型的实现

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值