JVM内存与GC简单理解

内存与GC

顺序是:Java程序编译为.class–>加载到运行时数据区–>最后执行
整个内存(运行时数据区)分为:一堆二栈一方法,方法内部常量池
JVM内存与GC简单理解

一堆:

  • 存放对象,GC堆。
  • 分为:新生代+老年代+永久代。新生代:老年代 = 1:2。永久代,是方法区的一种实现
  • 新生代又分Eden和两个幸存区。Eden:幸存区 = 8:1:1
    JVM内存与GC简单理解

GC

  • 可达性算法:GC Root引用链不可达的对象都是可回收的。
  • GC Root:可以作为GC Root的:
    1. 栈中引用的对象;
    2. 静态,常量引用的对象
    3. Native方法引用的对象

局部GC

  • 新生代回收:Minor GC
  • 老年代回收:垃圾收集器的CMS就是这种模式
  • 收集新生代和部分老年代
Minor GC触发:

当Eden区满的时候,触发

Minor GC表现:
  1. 将Eden和幸存区from中的对象复制到幸存区to中
  2. 将from和to换指针,此时,from内是存活的对象,to是空的
  3. Eden再满的时候执行第一步
对象晋升
  1. 对象到幸存区to的时候,年龄+1,默认15的时候进入老年代
  2. 幸存区使用50%的时候,高年龄对象进入老年代
  3. Minor GC触发,Eden+from对象大小 > to内存大小,溢出对象到老年代

全部GC

  • 全部收集:收集整个堆,Full GC
    触发条件:
  1. 老年代内存满了
  2. System.gc()。在没有线程运行的时候触发;内存大小不足的时候,触发

GC算法

  1. 标记清除:标记要回收的,进行清除。结果:效率低,空间不连续
  2. 复制算法:内存分两个区域A和B,将不回收的都移动到一个区域中,清空另一个区域
  3. 标记整理:标记不回收的,都整理到前面,清除后面的全部内存

新生代使用的就是复制算法:Eden+幸存区
老年代内存大,使用标记清除或者标记整理算法好

大对象,比如大的数组或者字符串等,需要大量连续空间。直接进入老年代

二栈:

Java虚拟机栈 + Native方法栈

  • 一个线程持有一个方法栈。
  • 栈内元素是栈帧。栈帧对应的是线程中调用的方法。方法执行的时候创建栈帧入栈
    JVM内存与GC简单理解
    可能异常:
    1. 线程调用方法过多,超出栈的深度。比如无限递归,报StackOverFlowErroe
    2. 栈要扩展的时候,申请不到足够的内存,OOM

一方法:

方法区,是JVM的规范。里面存放加载的类的信息,常量,静态变量等。是线程共享的。
前面提到的永久代就是方法区的一种实现。

  • 永久代:1.8以前,方法区的实现是永久代,是与老年代相连的内存
  • 元空间:1.8以后,方法区的实现是元空间,使用直接内存。只受本地内存影响

为什么用元空间替代永久代

  1. 字符串存在永久代中,容易出现性能问题和内存溢出。
  2. 类及方法的信息等比较难确定其大小,因此对于永久代的
    大小指定比较困难,太小容易出现永久代溢出,太大则容易导致
    老年代溢出。(这个可以理解为永久代大小不好确定,导致OOM)
  3. 永久代会为GC 带来不必要的复杂度,并且回收效率偏低。
  4. Oracle 可能会将HotSpot 与JRockit 合二为一。

常量池:

方法内部常量池,也就是说常量池存放在方法区内部
JVM内存与GC简单理解

  • class文件常量池:又称静态常量池,存放class文件中的基本类型常量,字符串,符号引用等
  • 运行时常量池:class文件加载结束后,将静态常量池内容移动到运行时常量池中。将静态常量池中的部分符号引用转换为直接引用。

将静态常量池中的部分符号引用转换为直接引用。比如一些静态方法,私有方法或者构造方法等。这些不能被重写或者改变的。就会转换为直接引用。

其他方法是在被第一次调用的时候将符号引用转换为直接引用。

符号引用可以理解为我们定义的名称,直接引用可以理解为内存地址。