和虚拟机谈恋爱的那些事儿

java程序员离不开虚拟机,一个好的程序必须是和虚拟机配合很好的程序,程序写的再牛逼,和虚拟机的特性不匹配,结果是弄巧成拙。你必须和虚拟机谈一场恋爱,要想谈好恋爱,你就得了解虚拟机的结构、原理、特性。

一、虚拟机基础知识

1、虚拟机的分类

系统虚拟机和程序虚拟机;大名鼎鼎的Visual Box和VMware就是系统虚拟机,而JVM,也就是java虚拟机其实式程序虚拟机。

2、java虚拟机不止一种

Classic、Exact VM、HotSopt等都是JVM

3、查看自己电脑上虚拟机的默认参数

4、虚拟机的工作模式

Client和Server二选一。

默认情况下虚拟机会根据计算机操作系统的情况自动选择运行模式。

与Client相比,Server模式启动更慢,因为Server模式会尝试收集更多的系统性能信息,使用更复杂的优化算法对程序进行优化。

5、锁在虚拟机中的实现和优化

  • 偏向锁
  • 轻量级锁
  • 膨胀锁
  • 自旋锁
  • 消除锁

在了解虚拟机内部对锁的支持之前,我们先了解一个概念:对象头。

什么是对象头?

在Java虚拟机的实现中每一个对象都有一个对象头,用来用来保存对象的系统信息。对象头中有一个Mark Word的部分,它是实现锁的关键。

以32位系统为例,普通对象的对象头如下所示:

hash:25 ---------------->| age:4  biased_lock:1 lock:2

它表示在Mark Word中有25位比特位表示对象的哈希值,4位表示对象的年龄,1位表示是否位偏向锁,2位表示锁的信息。

1 01 可偏向/未锁定
0 00 对象处于轻量级锁定
0 10 对象处于重量级锁定
0 01 普通的未锁定状态

偏向锁的核心思想是:如果程序没有竞争,则取消已经取得锁的线程同步操作。当某一锁被线程获取后,便进入偏向模式,当该线程再请求这个锁是,无需再进行相关的同步操作,从而节省了操作时间。如果在此之间其他线程进行锁的请求,则锁退出偏向模式。

举个例子:Boy AA(线程AA)追求一个Gril BB(资源),第一次追求需要写一份情书(锁),Gril BB,同意后Gril BB在心里将Boy AA设置成男朋友,并设置成恋爱状态(偏向模式)。接下来Boy AA就可以使用这个Gril BB的内部资源(牵手、接吻等)。下次再牵手啥的接不用再写情书了。

但是如果Gril BB被另一个Boy CC给追求了,而且Gril BB同意了。那么她就退出了恋爱状态(偏向模式),因为她处于劈腿模式。

当锁对象处于偏向模式时,对象头会被记录获得锁的线程。这样,当该线程再次尝试获得锁时,通过Mark Word的线程信息就可以判断当前线程是否持有偏向锁。

偏向锁在少竞争的情况下,对系统吸能有一定的帮助。但是在竞争激烈的场景下,可以禁止使用偏向锁。

禁止偏向锁的设置方式:-XX:-UseBiasedLocking 

二、分析java堆

本小节包括的内容:

  • 常见的内存溢出原因及解决思路
  • 有关java.lang.String的探讨
  • 使用Visual VM分析堆
  • 使用MAT分析堆

常见的内存溢出原因及解决思路

1、堆溢出

/**
 * @program: data-structure
 * @description: 堆溢出
 * @author: Miller.FAN
 * @create: 2019-12-04 20:38
 **/
public class HeapOOM {
    public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<>();
        for (int i = 0;  i<100000; i++) {
            list.add(new byte[1024*1024]);
        }
    }
}

堆溢出:com.miller.datastructure.gcc.HeapOOM
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.miller.datastructure.gcc.HeapOOM.main(HeapOOM.java:15) 

2、直接内存溢出

        for (int j =0; j< 10000; j++) {
            ByteBuffer.allocateDirect(1024*1024*1024);
            System.out.println(j);
            //System.gc();
        }

 Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory

3、过多线程导致OOM

由于线程的栈空间也是在堆外分配的,因此和直接内存非常相似,如果想让系统支持更多的线程,那么应该使用一个较小的堆空间。

另一个方法是使用参数:-Xss128k 指定线程的栈空间,如果一个线程的栈空间分配比较小,那么理应容纳更多的线程。前提是保证单个线程分配的栈空间是足够的。

4、永久区溢出

永久区是存放类元数据的区域。如果一个系统定义了太多的类型,那么永久区是有可能溢出的。JDK1.8中永久区被一块被称为元数据的区域代替,但是功能是类似的。

要解决元数据区溢出的问题,可以从以下三方面入手:

  • 增加MaxPermSize的值;
  • 减少系统需要的类的数量;
  • 使用ClassLoader合理地装载各个类,并定期进行回收。

5、GC效率低下引起的OOM

此时,虚拟机会抛出的异常: GC overhead limit exceeded

如何解决GC效率低下造成的OOM问题呢?

  • 更换虚拟机垃圾回收策略;
  • 检查代码中是不是有过多的强引用;
发布了85 篇原创文章 · 获赞 21 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41670928/article/details/103384813