JVM内存区域、内存模型+GC

JVM内存区域

image.png

方法区

       存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。简单说方法区用来存储类型的元数据信息,一个.class文件是类被java虚拟机使用之前的表现形式,一旦这个类要被使用,java虚拟机就会对其进行装载、连接(验证、准备、解析)和初始化。而装载(后的结果就是由.class文件转变为方法区中的一段特定的数据结构。

可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。

方法区主要有以下几个特点:

1、方法区是线程安全的。由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入JVM,那么只允许一个线程去装载它,而其它线程必须等待

2、方法区的大小不必是固定的,JVM可根据应用需要动态调整。同时,方法区也不一定是连续的,方法区可以在一个堆(甚至是JVM自己的堆)中自由分配。

3、方法区也可被垃圾收集,当某个类不在被使用(不可触及)时,JVM将卸载这个类,进行垃圾收集

常量池

       常量池就是这个类型用到的常量的一个有序集合,包括实际的常量(string,integer,和floating point常量)和对类型,域和方法的符号引用。池中的数据项象数组项一样,是通过索引访问的。

JVM堆

       Jvm 堆也是属于线程共享的内存区域,它在虚拟机启动时创建,是Java 虚拟机所管理的内存中最大的一块,主要用于存放对象实例,几乎所有的对象实例都在这里分配内存,注意Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做GC 堆,如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。

        

虚拟机栈

线程私有,它的生命周期与线程相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。

本地方法栈

本地方法栈(Native MethodStacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。

程序计数器

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

线程的

内存模型

主要目标:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。

image.png

Java内存模型中规定:

1. 线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量

2. 不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成

内存模型解决并发问题主要采用两种方式:

  • 限制处理器优化
  • 使用内存屏障

性质

原子性

在 Java 中,为了保证原子性,提供了两个高级的字节码指令 Monitorenter 和 Monitorexit。


在 Synchronized 的实现原理文章中,介绍过,这两个字节码,在 Java 中对应的关键字就是 Synchronized。

因此,在 Java 中可以使用 Synchronized 来保证方法和代码块内的操作是原子性的。

可见性

Java 中的 Volatile 关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存。


被其修饰的变量在每次使用之前都从主内存刷新。因此,可以使用 Volatile 来保证多线程操作时变量的可见性。

有序性

在 Java 中,可以使用 Synchronized 和 Volatile 来保证多线程之间操作的有序性。

Synchronized和Volatile对比

Synchronized和Lock对比

1.Lock可以手动释放和获得锁,synchronized是被动的

2.Lock基于jdk实现,synchronized基于jvm实现

GC(垃圾回收机制)

JVM堆内存结构

image.png

堆内存分为三部分:新生代、老生代和永久代。其中新生代又进一步划分为Eden、S0、S1(Survivor)三个区

对象存活的时间来区分的,存活时间越来越长,就会到老生代,不会被清理掉的就到永久代里去

分代回收算法

复制算法

针对新生代的算法:把内存分成两块,只用其中一块。用完后,把还存活的对象存到另一块,这一块就处理掉,重新使用。

hotspot虚拟机的新生代内部比例:8:1:1

标记-清除算法

标记

标记出需要回收的对象空间。

清除

将标记出的对象空间清除掉

不足

1.标记和清除效率不够高

2.清理后产生大量不连续碎片,导致接下来无法分配大对象

标记-整理算法

标记回收后,把存活的对象空间挪到一起,然后再清理

垃圾收集器

其中新生代收集器主要有Serial收集器、ParNew收集器和Parallel Scavenge收集器。老年代收集器主要有Serial Old收集器、Parallel Old收集器和CMS收集器。当然还包括了一款全新的、新生代老年代通用的G1收集器。

新生代收集器

Serial收集器

是一个单线程收集器,基于复制算法实现

在垃圾回收的时候仅使用单条线程并且在回收过程中会挂起所有的用户线程。

造成应用停顿

ParNew收集器

采用多线程和复制算法进行垃圾回收。

同时多条线程进行回收,所以会挂起所有用户的线程

造成应用停顿

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)

老年代收集器

serial old收集器

是jvm模式下的默认的老年代收集器

采用单线程+标记-整理算法来实现垃圾回收。

挂起所有用户线程

造成应用停顿

一般,老年代的容量都大于新生代,所以发成老年代垃圾回收时,stw精力时间会长

parallel old收集器

是parallel scavenge收集器的老年代版本

多线程+标记-整理算法

jdk1.6以后用到

cms收集器

真正实现了并发收集的老年代收集器

多线程并发以及标记-清除算法来实现垃圾回收

阶段:


(1) 初始化标记 (inital mark)

这个阶段仅仅是标记了GC Roots能够直接关联到的对象,速度很快,所以基本上感受不到STW带来的停顿。

(2) 并发标记 (concurrent mark)

并发标记阶段完成的任务是从第一阶段收集到的对象引用开始,遍历所有其他的对象引用,并标记所有需要回收的对象。这个阶段,收集线程与用户线程并发交替执行,不必挂起用户线程,所以并不会造成应用停顿。

(3) 并发预清除 (concurrent-pre-clean)

并发预清除阶段是为了下一个阶段做准备,为的是尽量减少应用停顿的时间。

(4) 重新标记 (remark)

这个阶段将会修正并发标记期间因为用户程序继续运作而导致标记产生变动的那部分对象的标记记录(有可能对象重新被引用或者新对象可以被回收)。这个阶段的停顿时间比初始标记阶段要长一些,但是远比并发标记的时间短。

(5) 并发清除 (concurrent sweep)

这个阶段将真正执行垃圾回收,将那些不被使用的对象内存回收掉。

(6) 并发重置 (concurrent reset)

收集器做一些收尾的工作,以便下一次GC周期能有一个干净的状态。

哪些情况会触发GC?会触发哪些GC?

判断是否要回收的方法:引用计数法+可达性分析法

引用计数法:被调用一次就加一。回收掉计数为0的对象(缺点:互相调用的不能检查出来)

可达性分析法:

minor GC:eden满了,无法分配新的对象

fullGC:老生代收集器满了

java GC root 有哪些?

方法区、JVM栈和Native栈不被GC所管理,因而选择这些非堆区的对象作为GC roots,被GC roots引用的对象不被GC回收。

jvm gc 如何调优?

  • 一个是将转移到老年代的对象数量降到最少
  • 另一个是减少Full GC的执行时间

猜你喜欢

转载自blog.csdn.net/sulu0416/article/details/96764998
今日推荐