读书笔记-实战jvm虚拟机

第一章 初探java虚拟机

系统虚拟机: Visual Box Vmware
程序虚拟机: JVM

类加载子系统: 负责从文件系统或者网络加载Class信息,加载的类信息放在了方法区,方法区还会存一些运行时的常量信息,包括字符串常量和数字常量
java堆: 虚拟机启动时建立,是java程序最主要的工作区域,存放所有的类实例,堆空间是所有线程共享的
直接内存: java中的nio库允许java程序使用直接内存, 读写频繁的场合适合使用直接内存
垃圾回收系统: 对方法区,java堆和直接内存进行回收
java栈: 每个线程有一个私有的java栈, java栈保存局部变量,方法参数等
本地方法栈: 用于本地方法调用
PC寄存器: 每个线程的私有空间,一般pc寄存器会指向当前正在被执行的指令,如果当前执行的是本地方法,则寄存器就是undefined

  • java堆
    常见构成:将java堆分成新生代+老年代
    新生代: eden区,s0区,s1区,也叫from区和to区
public class SimpleHeap {
    private int id;

    public SimpleHeap(int id) {
        this.id = id;
    }

    public static void main(String[] args) {
        SimpleHeap s1 = new SimpleHeap(1);
        SimpleHeap s2 = new SimpleHeap(2);
        s1.show();
        s2.show();
    }

    public void show() {
        System.out.println("My ID is " + id);
    }
}
  • 堆,方法区,栈的关系

  • 线程执行的基本行为是函数调用,每次函数调用的数据都是通过java栈传递的

public class TestStackDeep {
    private static int count = 0;

    public static void recursion(long a, long b, long c) {
        long e = 1, f = 2, g = 3, h = 4, i = 5, k = 6, q = 7, x = 8, y = 9, z = 10;
        count++;
        recursion(a, b, c);
    }

    public static void recursion() {
        count++;
        recursion();
    }

    public static void main(String args[]) {
        try {
            recursion(0L, 0L, 0L);//1
            //recursion();//2
        } catch (Throwable e) {
            System.out.println("deep of calling = " + count);
            e.printStackTrace();
        }
    }
}
  • 当方法中的参数比较多时,能够递归的层数就变少
  • 局部变量表: 用于保存函数的参数以及局部变量,函数变量表只在当前函数调用中有效,函数调用结束后,局部变量表就销毁
  • 相同栈容量下,局部变量少的函数可以支持更深的函数调用

第三章 常用Java虚拟机参数

  • -XX:+PrintGC 简单的GC日志输出
[GC (Allocation Failure)  53572K->8476K(182272K), 0.0054765 secs]
[GC (Metadata GC Threshold)  50764K->9340K(178176K), 0.0085938 secs]
[Full GC (Metadata GC Threshold)  9340K->8998K(139776K), 0.1062219 secs]
  • -XX:+PrintGCDetails 详细日志输出
[GC (Allocation Failure) [PSYoungGen: 67696K->7344K(80896K)] 76693K->16341K(172032K), 0.0067267 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Metadata GC Threshold) [PSYoungGen: 29302K->7728K(81920K)] 38299K->16725K(173056K), 0.0072769 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 7728K->0K(81920K)] [ParOldGen: 8997K->16190K(141312K)] 16725K->16190K(223232K), [Metaspace: 35128K->35128K(1075200K)], 0.0834686 secs] [Times: user=0.50 sys=0.00, real=0.08 secs] 

  • 类加载/卸载 跟踪: 有的class文件是利用反射产生,无法通过文件系统找到

  • -XX:+TraceClassUnloading 和 -XX:+TraceClassLoading 跟踪类的加载和卸载过程

    首先加载Object类

    对Example类进行反复加载和卸载

  • -XX:+PrintVMOptions 程序运行时,打印虚拟机接收到的命令行参数

输出格式:
VM option '+PrintVMOptions'
VM option '+PrintGC'
  • -XX:+PrintCommandLineFlags 可以打印显式和隐式参数,其中隐式参数是虚拟机自己设置的
-XX:InitialHeapSize=199409344 -XX:MaxHeapSize=3190549504 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
其中大部分参数都是虚拟机设置的
  • 新生代的配置:新生代的大小一般设置为整个堆空间的1/3到1/4左右

  • -XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间比例关系 eg: -XX:SurvivorRatio=eden/from=eden/to

  • 使用不同的堆分配参数执行该段程序,虚拟机的表现不一样

  • 基本策略: 尽可能将对象留在新生代,减少老年代GC次数

  • -Xss 指定栈大小

  • 直接内存读写比jvm内存快,但是申请的时候比jvm内存慢

  • 虚拟机工作模式:client和server, 用java -version可以看到是哪种模式

第四章 垃圾回收概念与算法

引用计数法: 只要有任何一个对象引用了A,则A的引用计算器就加1,引用失效后就减1,只要A的引用计算器值为0,则A就不可能再被使用
缺点: 1)无法统计循环引用 2)每次增加和减少引用都要伴随一个加法或者减法操作

标记清除: 两个阶段: 标记阶段和清除阶段 标记:通过根节点,标记所有从根节点开始的可达对象, 清除:清除所有未被标记的对象
缺点: 产生空间碎片

复制算法: 将空间分为两块,每次只使用其中一块,垃圾回收时,将存活对象复制到未被使用的内存块中,然后清除原来的内存块

  • 一般复制算法用在年轻代,因为年轻代对象存活数量少,但是老年代不适合用复制算法,因为老年代存活的对象多,复制代价大

标记压缩法: 从根节点出发,堆可达对象做标记,然后将这些对象压缩到内存的另外一边,将剩余的对象清除,可以避免碎片的产生,也不需要两块相同的内存空间

  • 四个级别的引用: 强引用,软引用,弱引用,虚引用
  1. 强引用: StringBuffer str = new StringBuffer("Hello World");
  2. 软引用:
public class SoftRef {
    public static void main(String[] args) {
        User u = new User(1, "geym");
        SoftReference<User> userSoftRef = new SoftReference<User>(u);
        u = null;

        System.out.println(userSoftRef.get());
        System.gc();
        System.out.println("After GC:");
        System.out.println(userSoftRef.get());

        byte[] b = new byte[1024 * 925 * 7];
        System.gc();
        System.out.println(userSoftRef.get());
    }

    public static class User {
        public int id;
        public String name;

        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }

        @Override
        public String toString() {
            return "[id=" + String.valueOf(id) + ",name=" + name + "]";
        }
    }
}

软引用会在内存不足的时候被回收

  • 每一个软引用都可以附带一个引用队列,当对象可达性发生改变时,由可达变成不可达,软引用对象会进入引用队列,通过这个引用队列,可以跟踪对象的回收情况

弱引用-发现即回收,在系统GC时,只要发现弱引用,马上回收,由于垃圾回收器的线程级别低,不一定马上能回收,但是一旦开始回收,就会将这个对象放入引用队列中

虚引用:随时都可以能被垃圾回收器回收,当试图通过get()方法取得强引用会失败
当垃圾回收器准备回收一个对象时,如果发现还有虚引用,就会在回收对象后将这个虚引用加入到引用队列,以便通知程序对象回收情况
如果软引用和弱引用被GC回收,JVM就会把这个引用加到引用队列里,如果是虚引用,在回收前就会被加到引用队列里。

垃圾回收器种类

  1. 串行收集器
  2. 并行收集器
  3. CMS回收器
  4. G1回收器
  • 串行收集器: 使用单线程进行垃圾回收的回收器,每次回收只有一个工作线程,对并行性能弱的机器性能表现更好,
  • 串行收集器可以分为新生代和老年代串行回收器

新生代串行回收器: 使用单线程回收,独占式回收,在回收的时候程序都要暂停,使用复制算法 -XX:+UseSerialGC 指定使用新生代和老年代串行收集器
老年代串行回收器: 使用标记压缩算法,因为老年代对象比较多,所以启动后可能停顿时间比较长

-XX:+UseSerialGC 新生代和老年代都用串行回收器
-XX:+UseParNewGC 新生代用ParNew,老年代用串行回收器
-XX:+UseParallelGC 新生代使用ParallelGC回收器,老年代用串行收集器
-XX:+UseConcMarkSweepGC 新生代使用ParNew回收器,老年代用CMS

新生代ParNew回收器: 只是简单的将串行收集器并行化

新生代ParallelGC回收器:也是使用复制算法的回收器,比ParNew更关注吞吐量,支持自适应的GC调节策略

-XX:+UseParallelGC: 新生代用ParallelGC 老年代用串行回收器
-XX:+UseParallelOldGC: 新生代用ParallelGC回收器,老年代用ParallelOldGC回收器
控制吞吐量的参数:
-XX:MaxGCPauseMillis: 设置最大垃圾收集停顿时间
-XX:GCTimeRatio: 设置吞吐量大小,0到100之间

老年代ParallelOldGC回收器

跟新生代的ParallelGC一样也是关注吞吐量的收集器: 使用标记压缩算法
-XX:ParallelGCThreads: 设置垃圾回收时的线程数量

CMS收集器: 主要关注停顿时间的收集器

并发标记删除: 初始标记,并发标记,预清理,重新标记,并发清除,并发重置.其中初始标记,并发标记是独占系统资源,预清理,重新标记,并发清除,并发重置是可以和用户线程一起执行

-XX:+UseConcMarkSweepGC 启用CMS回收器


日志:
[CMS-concurrent-abortable-preclean: 0.420/0.424 secs] [Times: user=0.42 sys=0.00, real=0.42 secs] 
[GC (CMS Final Remark) [YG occupancy: 33152 K (59008 K)][Rescan (parallel) , 0.0021553 secs][weak refs processing, 0.0000082 secs][class unloading, 0.0003782 secs][scrub symbol table, 0.0003757 secs][scrub string table, 0.0001182 secs][1 CMS-remark: 141244K(150496K)] 174397K(209504K), 0.0031253 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-sweep-start]
  • CMS-concurrent-abortable-preclean 表示进行一次新生代回收

G1收集器: 为了替代CMS收集器

  • G1收集器使用分区算法,G1将堆分成一个个区,每次收集的时候只收集其中几个区域,所以可以控制每次收集的停顿时间
  • G1收集4个阶段:
  1. 新生代GC
  2. 并发标记周期
  3. 混合收集
  4. 如果需要,则进行FullGC


2)

相关参数汇总:





第八章 锁与并发

  • 偏向锁: 一个锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时无需再进行同步操作,如果在此期间有其他线程进行了锁请求,则退出偏向锁模式
  • -XX:+UseBiasedLocking 开启偏向锁
public class Biased {
    public static List<Integer> numberList = new Vector<Integer>();

    public static void main(String[] args) throws InterruptedException {
        long begin = System.currentTimeMillis();
        int count = 0;
        int startnum = 0;
        while (count < 10000000) {
            numberList.add(startnum);
            startnum += 2;
            count++;
        }
        long end = System.currentTimeMillis();
        System.out.println(end - begin);
    }
}


启动参数:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 -client -Xmx512m -Xms512m


  • 自旋锁


  • 应用层锁优化策略
    1.只有在必要的时候才使用锁,减少使用锁的代码面积
    2.减少锁粒度,如使用ConcurrentHashMap
    3.锁分离

    4.锁粗化

  • 无锁

  • 原子操作

  • java内存模型:规范多线程下对共享资源的操作方法

第九章 Class文件结构

posted @ 2020-12-03 19:23  余***龙  阅读(116)  评论(0编辑  收藏  举报