Java虚拟机复习

一、内存分配

1.1 JVM 内存结构

Java 虚拟机的内存空间分为 5 个部分:

  • 程序计数器
  • Java 虚拟机栈
  • 本地方法栈
  • 方法区

内存结构

程序计数器(PC)

为什么需要程序计数器

因为Java虚拟机的多线程是通过线程轮流切换分配处理器执行时间的方式来实现的。任意时刻,一个处理器只执行一条指令,为了进程切换后恢复到正确的执行位置,所以才有了程序计数器

作用

记录当前线程的执行字节码的位置,线程私有,也就是每个线程都有一个单独的计数器来记录。

特点

  • 不会发生OutOfMemoryError
  • 执行Native方法,计数器值为空
  • 线程私有

Java虚拟机栈

描述Java方法执行的内存模型。

方法在执行的时候会创建一个栈帧,方法的调用到完成,对应着栈帧在虚拟机栈中入栈出栈的过程。

一个线程的方法调用可能很长,很多方法都处于执行状态。对于执行引擎来说,只有处于栈顶的栈帧(当前栈帧)才有效,与之相关联的方法是当前方法

栈帧用于存储局部变量表操作数栈动态链接方法出口等信息。

栈帧

局部变量表

局部变量表存放编译期可知的基本数据类型对象引用return Address类型

局部变量表所需要的内存空间在编译期间完成分配。

特点

  • 线程私有
  • 线程请求的栈深度大于JVM允许深度,抛出StackOverflowError异常
  • 虚拟机栈动态扩展时,无法申请足够的内存,抛出OutOfMemoryError异常

本地方法栈

用来存放对象的内存空间,几乎所有的对象都存储在堆中。

堆的特点

  • 线程共享
  • 垃圾回收的主要场所

方法区

方法区的定义
运行时常量池

二、垃圾回收

垃圾回收主要解决三个问题(回收哪些Which,什么时候回收WHEN,如何回收HOW)

一、回收哪些

这三个问题,最主要的还是第一个,Which回收哪些,评断回收还是不回收的标准是看对象是否被引用

引用分为四种:

  • 强引用:一个对象被一个引用所指向。绝对不会被JVM回收的,即使内存不过用
  • 软引用:只有在堆内存不够用的时候才会被回收。使用SoftReference实现
  • 弱引用:弱引用相对于软引用,引用级别更低。只要垃圾回收器启动了回收,就会被回收掉。绝WeakReference实现
  • 虚引用:虚引用的主要作用是跟踪对象被垃圾回收的状态。虚引用对对象本身没有太大影响,必须和引用队列(ReferenceQueue)联合使用

1、引用计数法

如果两个对象相互引用,就不会被回收,当然,GC并没有采用这种算法

2、根搜索算法

从根开始,沿着整个对象图上的每条链接,确定可达的对象,如果对象不可达,则作为垃圾收集
是对(1)的改进,根对象到达某一对象不可达,GC就会对其回收。

public static void main(String[] args) {
		Node n1 = new Node();
		Node n2 = new Node();
		Node n3 = new Node();
		n1.next = n2;
		n3 = n2;
		n2 = null;
	}

内存分配图

二、何时回收,如何回收

这就需要垃圾收集算法来解决,但讲垃圾回收算法之前需要明白一个分代的概念

1、分代的策略

绝大多数的对象不会被长时间引用,这些对象在其Young期间就会被回收
很老的对象和很新的对象之间很少存在相互引用

堆结构图

Young代

大部分垃圾回收器对Young代都采用复制算法,为什么?因为Young代处于可达的对象数量少,所以复制成本不大
Young代由一个Eden区和2个Survivor区构成。绝大多数对象先分配到Eden区中,Survivor区中的对象都至少经历过一次垃圾回收
为什么要有2个Survivor区,是因为其中一个Survivor是空的,来存放Young代的对象。最后会清空Eden区和第一个Survivor区
思考:Survivor的大小设置的变化会产生什么影响

Old代

Young代的对象经过多次垃圾回收依然没有被回收,就会被转移到Old代
随着时间流逝,Old代的对象会越来越多,因此Old代的空间要与Young代的空间大
Old代的垃圾回收的两个特征:Old代垃圾回收的执行频率不需要太高,因为死的少。每次回收需要更长的时间来完成(如何理解,因为对象多吧)
垃圾回收通常会采用标记压缩算法。因为对象不会很快死亡,也不会大量产生内存碎片

Permanent代

主要用于装载Class、方法等信息
垃圾回收机制通常不会回收这一代的对象。
服务器程序通常会加载很多类,需要加大Permanent代内存。
OutOfMemoryError:PermGen space错误

2、垃圾回收算法

标记-清除算法

标记所有需要回收的对象,标记完成后,统一回收所有被标记的对象,
不足之处,标记和清除的效率不高,标记清除后会产生大量不连续的内存碎片

复制算法

为了解决效率问题,将内存分为大小相等的两块,每次使用一块,当这一块内存使用完了之后,将存活的对象放到另一块内存中,然后对原先那一半内存进行回收。实现简单,运行高效,不过代价是将内存缩小一半,代价过高。

标记-整理算法

是对标记清除算法的改进,进行标记好了之后,将存活的对象都向一边移动,然后直接清理掉端边界以外的内存

分代收集算法

商业虚拟机都采用“分代收集”算法,根据对象的存活周期将内存分为几块,一般是将java堆分成新生代和老年代,如果新生代有很少的存活对象,就用复制算法,老年代有很多存活对象,就用标记-清除或标记-整理算法

HotSpot虚拟机下的垃圾收集器

由于内存中的对象,是按存活周期存放在不同的内存块中的,所以,我们选择不同的算法来针对不同的内存块进行垃圾收集。从而,对于,不同的内存块,我们需要有不同的垃圾收集器。

新生代的垃圾收集器有:Serial收集器ParNew收集器Parallel Scavenge收集器

老年代的垃圾收集器有:Serial Old收集器Parallel Old收集器CMS收集器G1收集器

Serial收集器/Serial Old收集器

串行,是单线程的,使用“复制”算法。当它工作时,必须暂停其它所有工作线程。特点:简单而高效。一般用于Client模式的JVM中
Serial Old是老年代的单线程收集器,使用标记-整理算法。

ParNew收集器

ParNew收集器,是Serial收集器的多线程版。是运行在Server模式下的虚拟机中首选的新生代收集器。除了Serial收集器外,目前只有它能与CMS收集器配合工作。

Parallel Scavenge收集器

吞吐量优先收集器,吞吐量=程序运行时间/(JVM执行回收时间+程序运行时间),是server模式JVM的默认配置

Parallel Old收集器

老年代的多线程收集器,使用标记-整理算法,吞吐量优先,适合于Parallel Scavenge搭配使用

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,使用“标记-清除”算法。
回收线程数=(CPU核心数+3)/4
CMS收集器分4个步骤进行垃圾收集工作:
1、初始标记 2、并发标记 3、重新标记 4、并发清除
其中“初始标记”、“重新标记”是需要暂停其它所有工作线程的。

G1收集器

G1(Garbage First)收集器,基于“标记-整理”算法,可以非常精确地控制停顿。
可以不与其他收集器搭配,独立收集新生代和老年代。

三、内存管理技巧

1、尽量使用直接量:String str = "hello"

2、使用StringBuilder和StringBuffer进行字符串连接

3、尽早释放无用对象的引用

Object obj = new Object();obj = null;这行代码并不能发挥C++中的delete和free作用,其作用仅仅是断开obj 与new Object()的关联,new Object所占用的内存并没有释放掉。以此来诱发GC对其进行回收。如果是在方法中,其实要考虑两种情况,多数情况下不需要这么写,对象会随着因为方法调用的结束而结束。但如果obj =null之后,还有耗时耗内存的操作的话,就需要这样写。

四、设置Java虚拟机内存的一些参数

  • -Xmx 设置堆内存的最大容量
  • -Xms 设置堆内训初始容量
  • -XX:NewSize = size 设置Young代内存的默认容量
  • -XX:SurvivorRatio = 8 设置Young代中eden/survivor的比例
  • -XX:MaxNewSize = size 设置Young代内存的最大容量
  • -XX:PermSize = size 设置永久代的默认容量
  • -XX:MaxPermSize = size 设置永久代内存的最大容量

五、一次完整的GC流程

从ygc到fgc

六、监控工具

jstat

jstat -gc 14122 250 20

jstat -gcutil 14122 250 20

一、Class结构

dir

参考文档

[1]: JVM 解剖公园(19): 锁省略
[2]: Java性能调优实战视频全集

原文地址:https://www.cnblogs.com/fonxian/p/8192612.html