JVM学习笔记

一、内存结构

框架了解

(1)JVM位置

(2)JVM体系结构

简图:

详图:

1.程序计数器Program Counter Register(使用寄存器实现)

(1)作用:记住下一条jvm指令的执行地址

jvm指令->解释器->机器码->CPU执行

(2)特点

是线程私有的:每个线程都有自己的程序计数器,当线程切换运行时,程序计数器可以记住下一条待执行的jvm指令;

是不会存在内存溢出的。

2.虚拟机栈--线程运行时需要的内存空间,每个线程都需要一个

栈的数据结构特点:先进后出。

(1)每个栈由多个栈帧组成,每个线程只能有一个活动栈帧,对应当前正在执行的那个方法。

栈帧:每个方法运行时需要的内存(参数、局部变量,返回地址等)。

注意:垃圾回收不涉及栈内存;栈内存的分配(可自己定义,一般用默认)并不是越大越好,越大会影响线程数量,反而影响效率;如果方法内的局部变量没有逃离方法内的作用范围,则是线程安全的,如果局部变量引用了对象并逃离了作用范围,需要考虑线程安全。

(2)栈内存溢出的情况:

栈帧过多导致的栈内存溢出(递归无出口);栈帧过大导致的栈内存溢出(可能性很小)。

(3)线程运行诊断:

cpu占用过多:要学会定位线程,找出线程占用最多的位置。

程序运行很长时间无结果:可能陷入了死锁状态,也可以通过定位查询。

3.本地方法栈

本地方法:操作系统,c/c++语言编写。

java调用本地方法时本地方法运行的内存空间由本地方法栈提供。

本地方法接口(JNI):在执行引擎执行的时候通过本地方法接口加载本地方法库中的方法。

4.堆

通过new关键字创建对象都会使用堆内存。

特点:线程是共享的,堆中对象都需要考虑线程安全的问题;有垃圾回收机制。

堆内存溢出(outofmemory):对象一直被使用,一直无法被回收导致溢出。

堆内细分为三个区域:新生区(伊甸园区);养老区;永生区(jdk8以后改名为元空间)。

ps:gc垃圾回收主要是在伊甸园区和养老区。

元空间:逻辑上存在,物理上不存在,存储在本地磁盘内,不占用虚拟机内存。

5.方法区

存储类的数据,线程共享。所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。静态变量(static)、常量(final)、类信息(构造方法、接口定义)(Class)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关

两种jvm的方法区占地不同,jvm1.6用永久代,jvm1.8用元空间。

stringtable面试题:

(1)string s1=s2+s3;其中s2和s3和拼接执行了new string()代码,是堆变量,同时,s2和s3也在常量池中;

(2)string s1=“ab”;是常量池中的变量,栈变量,和(1)不相等;

(3)string s1="a"+"b";和(2)相等;

(4)string s1=s2.intern();

1.8版本intern()作用是将该字符串对象放入串池中(如果串池中没有);

1.6版本ntern()作用是将该字符串对象复制一份,再放入串池中(如果串池中没有);

6.类加载器classloader

(1)定义:.java文件通过java编译器编译成.class文件,当需要使用某个类时,虚拟机将会加载它的".class"文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载。

(2)三种类加载器

  • 启动(Bootstrap)类加载器:主要加载的是JVM自身需要的类;
  • 扩展(Extension)类加载器:负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库;
  • 系统(System)类加载器:负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径。

(3)双亲委派机制

双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,类加载器间的关系如下:

双亲委派机制工作原理:为如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。

双亲委派机制优点:

  • 因为双亲委派是向上委托加载的,所以它可以确保类只被加载一次, 避免重复加载
  • Java的核心API都是通过引导类加载器进行加载的,如果别人通过定义同样路径的类比如java.lang.Integer,类加载器通过向上委托,两个Integer,那么最终被加载的应该是jdk的Integer类,而并非我们自定义的,这样就 避免了我们恶意篡改核心包的风险 。

7.沙箱安全机制

沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。

系统资源包括:CPU、内存、文件系统、网络。

组成沙箱的基本组件:

  1. 字节码校验器(bytecode verifier) :确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
  2. 类裝载器(class loader) :其中类装载器在3个方面对Java沙箱起作用
  • 防止恶意代码去干涉善意的代码;
  • 守护了被信任的类库边界;
  • 将代码归入保护域,确定了代码可以进行哪些操作。

二、垃圾回收GC

特点:

  • 无法手动垃圾回收,只能手动提醒,等待JVM自动回收
  • GC的作用区在堆(Heap)和方法区中
  • JVM进行GC时,并不是统一对这三区域(新生区,幸存区,老年区)统一回收,回收都是新生代;轻GC(普通GC)只针对于新生区,偶尔作用幸存区(在新生区满的情况下);重GC(全局GC)全局释放内存。

1.常见垃圾回收算法

  • 引用计数算法
    原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为 0 的对象。此算法最致命的是无法处理循环引用的问题。

  • 复制算法
    此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中,同时回收未使用的对象。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理。
    优点:不会出现碎片化问题
    缺点:需要两倍内存空间,浪费

  • 标记-清除算法
    此算法执行分两阶段。第一阶段从引用根节点开始标记所用存活的对象,第二阶段遍历整个堆,把未标记的对象清除。
    优点:不会浪费内存空间
    缺点:此算法需要暂停整个应用,同时,会产生内存碎片

  • 标记-压缩算法
    此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有存活的对象,第二阶段遍历整个堆,清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。
    此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

2.分代回收策略(回顾堆)

  1. 绝大多数刚刚被创建的对象会存放在Eden区
  2. 当Eden区第一次满的时候,会触发MinorGC(轻GC:使用复制算法)。首先将Eden区的垃圾对象回收清除,并将存活的对象复制到S0,此时S1是空的。
  3. 下一次Eden区满时,再执行一次垃圾回收,此次会将Eden和S0区中所有垃圾对象清除,并将存活对象复制到S1,此时S0变为空。
  4. 如此反复在S0和S1之间切换几次(默认15次)之后,还存活的对象将他们转移到老年代中。
  5. 当老年代满了时会触发FullGC(全GC:一般是标记压缩算法)

3.垃圾收集器

1.串行收集器(Serial)
Serial 收集器是 Hotspot 运行在 Client 模式下的默认新生代收集器, 它的特点是:单线程收集, 但它却简单而高效

2.并行收集器(ParNew)
ParNew

3.Parallel Scavenge 收集器
与 ParNew 类似, Parallel Scavenge 也是使用复制算法, 也是并行多线程收集器. 但与其
他收集器关注尽可能缩短垃圾收集时间不同, Parallel Scavenge 更关注系统吞吐量:
系统吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

4.CMS 收集器(Concurrent Mark Sweep)
CMS是一种以获取最短回收停顿时间为目标的收集器(CMS又称多并发低暂停的收集器),
基于”标记-清除”算法实现, 整个 GC 过程分为以下 4 个步骤:
初始标记(CMS initial mark)
并发标记(CMS concurrent mark: GC Roots Tracing 过程)
重新标记(CMS remark)
并发清除(CMS concurrent sweep: 已死对象将会就地释放, 注意:此处没有压缩)

5.G1 收集器
G1将堆内存“化整为零”,将堆内存划分成多个大小相等独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。

为什么要垃圾回收时要设计STW(stop the world)?

  • 如果不设计STW,可能在垃圾回收时用户线程就执行完了,堆中的对象都失去了引用,全部变成了垃圾,索性就设计了STW,快速做完垃圾回收,再恢复用户线程运行。

三、JMM(java内存模型)

JMM(java内存模型)Java Memory Model,本身是一个抽象的概念,不是真实存在的,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素)的访问方式。

JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程读/写共享变量的副本。

JMM内存模型三大特性

1、原子性:一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行,这就是 原子性操作。

  • 使用 synchronized 互斥锁来保证操作的原子性

2、可见性:可见性指当一个线程修改了共享变量时,其他线程能够立即得知修改。volatile,synchronized,final都能 保证可见性。

  • volatile,会强制将该变量自己和当时其他变量的状态都刷出缓存。
  • synchronized,对一个变量执行 unlock 操作之前,必须把变量值同步回主内存。
  • final,被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),那么其它线程就能看见 final 字段的值。

3、有序性:虽然多线程存在并发和指令优化等操作,在本线程内观察该线程的所有执行操作是有序的。

  • 源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 ->最终执行的命令。
  • 重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
  • 处理器在进行重排时必须考虑数据的依赖性,多线程环境线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的。
  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值