其他分享
首页 > 其他分享> > 第八章 - 堆

第八章 - 堆

作者:互联网

月亮正亮得起劲,若此刻不想你倒显得我不解风情

1.堆的核心概述

1.1 认识堆内存

堆与进程、线程

对堆的认识

1.2 查看堆内存

一个JVM实例只存在一个堆内存,并且堆内存的大小是可以调节的
public class HeapDemo {

   public static void main(String[] args) {
       System.out.println("start...");
       try {
           Thread.sleep(1000000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }

       System.out.println("end...");
  }

}
public class HeapDemo1 {

   public static void main(String[] args) {
       System.out.println("start...");
       try {
           Thread.sleep(1000000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }

       System.out.println("end...");
  }

}

使用jdk自带的Java VisualVM查看堆内存

1.3 堆内存分区

堆内存细分

2.设置堆内存大小与OOM

2.1 设置堆空间大小

代码示例
/**
* 1. 设置堆空间大小的参数
* -Xms 用来设置堆空间(年轻代+老年代)的初始内存大小
*     -X 是jvm的运行参数
*     ms 是memory start
* -Xmx 用来设置堆空间(年轻代+老年代)的最大内存大小
*
* 2. 默认堆空间的大小
*     初始内存大小:物理电脑内存大小 / 64
*     最大内存大小:物理电脑内存大小 / 4
*
* 3. 手动设置:-Xms600m -Xmx600m
*     开发中建议将初始堆内存和最大的堆内存设置成相同的值。
*
* 4. 查看设置的参数:方式一: jps   / jstat -gc 进程id
*                 方式二:-XX:+PrintGCDetails
*/
public class HeapSpaceInitial {
   public static void main(String[] args) {

       //返回Java虚拟机中的堆内存总量
       long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
       //返回Java虚拟机试图使用的最大堆内存量
       long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

       System.out.println("-Xms : " + initialMemory + "M");
       System.out.println("-Xmx : " + maxMemory + "M");
  }
}

2.2 OOM举例

/**
* -Xms600m -Xmx600m
*/
public class OOMTest {
   public static void main(String[] args) {
       ArrayList<Picture> list = new ArrayList<>();
       while(true){
           try {
               Thread.sleep(20);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           list.add(new Picture(new Random().nextInt(1024 * 1024)));
      }
  }
}

class Picture {
   private byte[] pixels;

   public Picture(int length) {
       this.pixels = new byte[length];
  }
}
-Xms600m -Xmx600m

3.年轻代与老年代

3.1 Java 对象分类

3.2 配置新老比例

配置新生代与老年代的比例

配置新生代与老年代在堆结构的占比(下面这些参数在开发中一般不会调)

新生区中的比例

代码示例
/**
* -Xms600m -Xmx600m
*
* -XX:NewRatio : 设置新生代与老年代的比例。默认值是2.
* -XX:SurvivorRatio :设置新生代中Eden区与Survivor区的比例。默认值是8
* -XX:-UseAdaptiveSizePolicy :关闭自适应的内存分配策略 (暂时用不到)
* -Xmn:设置新生代的空间的大小。 (一般不设置)
*/
public class EdenSurvivorTest {
   public static void main(String[] args) {
       System.out.println("我只是来打个酱油~");
       try {
           Thread.sleep(1000000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
  }
}
-Xms600m -Xmx600m -XX:NewRatio=2 -XX:SurvivorRatio=8

4.图解对象分配过程

4.1 对象分配过程

为新对象分配内存是一件非常严谨和复杂的任务,JVM的设计者们不仅需要考虑内存如何分配、在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还需要考虑GC执行完内存回收后是否会在内存空间中产生内存碎片。

4.2 图解对象分配

4.3 特殊情况说明

思考:幸存区满了咋办?
对象分配的特殊情况

4.4 常用调优工具

Jprofiler 基本使用

总结

5.GC垃圾回收器

5.1 分代收集思想

Minor GC、Major GC、Full GC

我们都知道,JVM调优的一个环节,也就是垃圾收集,我们需要尽量的避免垃圾回收,因为在垃圾回收的过程中,容易出现STW(Stop the World)的问题,而 Major GC 和 Full GC出现STW的时间,是Minor GC的10倍以上

JVM在进行GC时,并非每次都对上面三个内存( 新生代、老年代;方法区 )区域一起回收的,大部分时候回收的都是指新生代。针对Hotspot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC),一种是整堆收集(FullGC)

5.2 Young/Minor GC

年轻代 GC(Minor GC)触发机制

5.3 Major GC

老年代 GC(MajorGC/Full GC)触发机制

5.4 Full GC

Full GC 触发机制

触发Full GC执行的情况有如下五种:

Full GC 是开发或调优中尽量要避免的。这样STW时间会短一些。

6.堆空间分配思想

为什么要把Java堆分代?不分代就不能正常工作了吗?

7.内存分配策略

内存分配策略或对象提升(Promotion)规则

针对不同年龄段的对象分配原则如下所示:

8.为对象分配内存: TLAB

8.1 为什么有 TLAB

问题:堆空间都是共享的么?

不一定,因为还有TLAB这个概念,在堆中划分出一块区域,为每个线程所独占

为什么有TLAB(Thread Local Allocation Buffer)?

8.2 什么是 TLAB

8.3 TLAB 分配过程

默认开启 TLAB

9.堆空间参数设置

9.1 常用参数设置

常用参数设置

9.2 空间分配担保

在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。

历史版本

10.面试题

堆是分配对象存储的唯一选择吗?

在《深入理解Java虚拟机》中关于Java堆内存有这样一段描述:

10.1 逃逸分析

如何将堆上的对象分配到栈,需要使用逃逸分析手段。

举例
public void my_method() {
   V v = new V();
   // use v
   // ....
   v = null;
}
public static StringBuffer createStringBuffer(String s1, String s2) {
   StringBuffer sb = new StringBuffer();
   sb.append(s1);
   sb.append(s2);
   return sb;
}
public static String createStringBuffer(String s1, String s2) {
   StringBuffer sb = new StringBuffer();
   sb.append(s1);
   sb.append(s2);
   return sb.toString();
}
/**
* 逃逸分析
*
* 如何快速的判断是否发生了逃逸分析,大家就看new的对象实体是否有可能在方法外被调用。
*/
public class EscapeAnalysis {

   public EscapeAnalysis obj;

   /*
   方法返回EscapeAnalysis对象,发生逃逸
    */
   public EscapeAnalysis getInstance(){
       return obj == null? new EscapeAnalysis() : obj;
  }

   /*
   为成员属性赋值,发生逃逸
    */
   public void setObj(){
       this.obj = new EscapeAnalysis();
  }
   //思考:如果当前的obj引用声明为static的? 仍然会发生逃逸。

   /*
   对象的作用域仅在当前方法中有效,没有发生逃逸
    */
   public void useEscapeAnalysis(){
       EscapeAnalysis e = new EscapeAnalysis();
  }

   /*
   引用成员变量的值,发生逃逸
    */
   public void useEscapeAnalysis1(){
       EscapeAnalysis e = getInstance(); //这个e对象,本身就是从外面的方法逃逸进来的
       //getInstance().xxx()同样会发生逃逸
  }
}
逃逸分析参数设置
结论

开发中能使用局部变量的,就不要使用在方法外定义

逃逸分析之代码优化

使用逃逸分析,编译器可以对代码做如下优化:

10.2 栈上分配

代码举例
/**
* 栈上分配测试
* -Xmx256m -Xms256m -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
*/
public class StackAllocation {
   public static void main(String[] args) {
       long start = System.currentTimeMillis();

       for (int i = 0; i < 10000000; i++) {
           alloc();
      }
       // 查看执行时间
       long end = System.currentTimeMillis();
       System.out.println("花费的时间为: " + (end - start) + " ms");
       // 为了方便查看堆内存中对象个数,线程sleep
       try {
           Thread.sleep(1000000);
      } catch (InterruptedException e1) {
           e1.printStackTrace();
      }
  }

   private static void alloc() {
       User user = new User(); //未发生逃逸
  }

   static class User {

  }
}

未开启逃逸分析的情况

-Xmx256m -Xms256m -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
[GC (Allocation Failure) [PSYoungGen: 65536K->560K(76288K)] 65536K->568K(251392K), 0.0017179 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 66096K->464K(76288K)] 66104K->480K(251392K), 0.0017602 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
花费的时间为: 74 ms

开启逃逸分析的情况

-Xmx256m -Xms256m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
花费的时间为: 4 ms

10.3 同步省略

例如下面的代码,根本起不到锁的作用

public void f() {
   Object hellis = new Object();
   synchronized(hellis) {
       System.out.println(hellis);
  }
}
public void f() {
Object hellis = new Object();
System.out.println(hellis);
}
字节码分析
public class SynchronizedTest {
   public void f() {
       Object hellis = new Object();
       synchronized(hellis) {
           System.out.println(hellis);
      }
  }
}

10.4 分离对象或标量替换

标量替换举例
public static void main(String args[]) {
   alloc();
}
class Point {
   private int x;
   private int y;
}
private static void alloc() {
   Point point = new Point(1,2);
   System.out.println("point.x" + point.x + ";point.y" + point.y);
}
private static void alloc() {
   int x = 1;
   int y = 2;
   System.out.println("point.x = " + x + "; point.y=" + y);
}

结论:

标量替换参数设置

参数 -XX:+ElimilnateAllocations:开启了标量替换(默认打开),允许将对象打散分配在栈上。

逃逸分析的不足

11.堆小结

标签:Java,对象,第八章,XX,逃逸,GC,内存
来源: https://www.cnblogs.com/l12138h/p/16593116.html