JVM ~ 从入门到入坑。

JVM ~ 从入门到入坑。



从常见面试题引入。

请谈谈对 JVM 的理解。Java 8 的虚拟机有什么更新?
什么是 OOM?什么是 StackOverflowError?有哪些方法分析?
JVM 的常用参数调优你知道哪些?
谈谈 JVM 中对类加载器的理解。
内存快照如何抓取?怎么分析 dump 文件?
JVM 类加载器。

  • JVM 体系结构概述。
  • 堆体系结构概述。
  • 堆参数调优入门。
  • 总结。


JVM 体系结构。

JVM 位置。

在这里插入图片描述

JVM 是运行在操作系统之上的,ta 与硬件没有直接的交互。

JNI —> Java Native Interface。



JVM 体系结构图。

在这里插入图片描述

灰色 ~ 不会有垃圾回收。
JVM 调优 ~ 方法区和堆(主要)。

在这里插入图片描述

方法区就是元空间。



类装载器 ClassLoader。

负责加载 .class 文件,.class 文件在文件开头有特定的文件标示。将 .class 文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构并且 ClassLoader 只负责 .class 文件的加载。至于 ta 是否可以运行,则由 Exception Engine 决定。

在这里插入图片描述

package com.geek;

public class Car {

    public static void main(String[] args) {
        // 类是模板,对象是具体的。

        Car car1 = new Car();
        Car car2 = new Car();
        Car car3 = new Car();

        System.out.println(car1.hashCode());// 460141958
        System.out.println(car2.hashCode());// 1163157884
        System.out.println(car3.hashCode());// 1956725890

        Class<? extends Car> aClass1 = car1.getClass();
        Class<? extends Car> aClass2 = car2.getClass();
        Class<? extends Car> aClass3 = car3.getClass();
        System.out.println("aClass1 = " + aClass1);// aClass1 = class com.geek.Car
        System.out.println(aClass1.hashCode());// 685325104
        System.out.println("aClass2 = " + aClass2);// aClass2 = class com.geek.Car
        System.out.println(aClass2.hashCode());// 685325104
        System.out.println("aClass3 = " + aClass3);// aClass3 = class com.geek.Car
        System.out.println(aClass3.hashCode());// 685325104

        ClassLoader classLoader = aClass1.getClassLoader();
        System.out.println("classLoader = " + classLoader);// classLoader = sun.misc.Launcher$AppClassLoader@18b4aac2 ~ public abstract class ClassLoader {

        ClassLoader parent = classLoader.getParent();
        System.out.println("parent = " + parent);// parent = sun.misc.Launcher$ExtClassLoader@1540e19d ~ /jre/lib/ext
        ClassLoader parent1 = parent.getParent();// parent1 = null
        System.out.println("parent1 = " + parent1);
        ClassLoader parent2 = parent1.getParent();
        System.out.println("parent2 = " + parent2);// Exception in thread "main" java.lang.NullPointerException
        // rt.jar

    }

}

echo %JAVA_HOME%
echo %PATH%
echo %CLASSPATH%

  • java 相关文件说明。

*.class 文件用 Sublime Text 打开,可以看到 —> 开头都是:cafe babe
jvm 通过 cafe babe 识别 .class 文件)。

cafe babe 0000 0034 0022 0a00 0600 1409
0015 0016 0800 170a 0018 0019 0700 1a07
001b 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 154c 636f 6d2f 6765
656b 2f48 656c 6c6f 576f 726c 643b 0100
046d 6169 6e01 0016 285b 4c6a 6176 612f
6c61 6e67 2f53 7472 696e 673b 2956 0100
0461 7267 7301 0013 5b4c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b01 000a 536f
7572 6365 4669 6c65 0100 0f48 656c 6c6f
576f 726c 642e 6a61 7661 0c00 0700 0807
001c 0c00 1d00 1e01 0005 6865 6c6c 6f07
001f 0c00 2000 2101 0013 636f 6d2f 6765
656b 2f48 656c 6c6f 576f 726c 6401 0010
6a61 7661 2f6c 616e 672f 4f62 6a65 6374
0100 106a 6176 612f 6c61 6e67 2f53 7973
7465 6d01 0003 6f75 7401 0015 4c6a 6176
612f 696f 2f50 7269 6e74 5374 7265 616d
3b01 0013 6a61 7661 2f69 6f2f 5072 696e
7453 7472 6561 6d01 0007 7072 696e 746c
6e01 0015 284c 6a61 7661 2f6c 616e 672f
5374 7269 6e67 3b29 5600 2100 0500 0600
0000 0000 0200 0100 0700 0800 0100 0900
0000 2f00 0100 0100 0000 052a b700 01b1
0000 0002 000a 0000 0006 0001 0000 0003
000b 0000 000c 0001 0000 0005 000c 000d
0000 0009 000e 000f 0001 0009 0000 0037
0002 0001 0000 0009 b200 0212 03b6 0004
b100 0000 0200 0a00 0000 0a00 0200 0000
0600 0800 0700 0b00 0000 0c00 0100 0000
0900 1000 1100 0000 0100 1200 0000 0200
13

在这里插入图片描述



方法区:放类的描述信息,放类的模板。

在这里插入图片描述



类装载器 ClassLoader 种类。

在这里插入图片描述

  • 虚拟机自带的加载器。
    启动类加载器(Bootstrap)C± . . . . .(C++)-- = Java // java 底层 C++。
    扩展类加载器(Extension)Java
    应用程序类加载器(AppClassLoader) . . . . . . 一般自己 new 的使用 AppClassLoader。
    AppClassLoader 在 Java 中也叫系统类加载器,加载当前应用的 classpath 的所有类。
  • 用户自定义加载器。
    java.lang.ClassLoader 的子类,用户可以定制类的加载方式。
jdk 文件结构分析(rt.jar)。

$JAVAHOME/jre/lib/rt.jar —> rt.jar(runtime)(1.0 版本)。Bootstrap 启动类加载器加载 ta。
rt.jar 中有 /java/lang/Object.class/java/lang/String.class/java/util/ArrayList.class 等。

rt.jar 可以看作 Jdk 的第一代 1.0
后续发展了扩展包:$JAVAHOME/jre/lib/ext/*.jar。
ext —> extend —> javax.包。

在这里插入图片描述

package com.geek;

public class MyObject {

    public static void main(String[] args) {

        Object object = new Object();

        System.out.println(object.getClass());
        // class java.lang.Object
        // 对象实例的模板。(Object 类)。

        System.out.println(object.getClass().getClassLoader());
        // null
        // 找那个快递员。
        // Object 祖宗,是 Bootstrap 启动类加载器加载进 Runtime Data Area。
        // Bootstrap 是 C+- 语言写的,java 得到 null。

//        System.out.println(object.getClass().getClassLoader().getParent());
        // Exception in thread "main" java.lang.NullPointerException
        //	at com.geek.MyObject.main(MyObject.java:23)。——> Bootstrap 已经是祖宗了。
//        System.out.println(object.getClass().getClassLoader().getParent().getParent());
        // Exception in thread "main" java.lang.NullPointerException
        //	at com.geek.MyObject.main(MyObject.java:26)

        System.out.println("~ ~ ~ ~ ~ ~ ~");

        MyObject myObject = new MyObject();
        System.out.println(myObject.getClass());
        // class com.geek.MyObject
        System.out.println(myObject.getClass().getClassLoader());
        // sun.misc.Launcher$AppClassLoader@18b4aac2
        // 由`启动类加载器`。AppClassLoader 加载进 Runtime Data Area。

        // rt.jar/sun/misc/Launcher.class
        //  jvm 相关调用的入口程序。

        System.out.println(myObject.getClass().getClassLoader());
        // sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(myObject.getClass().getClassLoader().getParent());
        // sun.misc.Launcher$ExtClassLoader@74a14482
        System.out.println(myObject.getClass().getClassLoader().getParent().getParent());
        // null ——> Bootstrap。

    }
}

  • 用户自定义加载器。
public abstract class ClassLoader { ... }

ClassLoader 是一个抽象类。
Class xxx extends java.lang.ClassLoader {} 定制自己的加载器。



面试常问。

双亲委派机制。

我爸是李刚,有事找我爹。

我要用一个(A.java)的类,先去 Bootstrap Class Loader 找,找到就直接用;找不到就降一级,去 Extension Class Loader 中找,再找不到就报 ClassNotFoundException

在这里插入图片描述

理解:自己手动创建 java.lang.String。
package java.lang;

public class String {

    public static void main(String[] args) {
        System.out.println("hello");
    }

    /*
    Error: Main method not found in class java.lang.String, please define the main method as:
   public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application

Process finished with exit code 1

     */
}

根据双亲委派机制,jvm 会先去 Bootstrap 找 String 类(找到了 rt.jar/java/lang/String.class)。但 ta 没有 main() 方法。

双亲委派机制 —> 保证用户写的代码不污染 java 的源代码。保证沙箱安全。保证加载的 class 都是同一个。

当一个类收到了类加载的请求,ta 首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次加载器都是如此。因此所有的加载请求都应该传送到启动类加载器中。只有当父类加载器反馈自己无法完成这个请求的时候(在 ta 的加载路径下没有找到所需加载的 class),子类加载器才会尝试自己去加载。
采用双亲委派机制的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象。

Execution Engine 执行引擎负责解释命令,提交操作系统执行。

  • 类装载器子系统包含的知识点。
    类加载器。
    有几种。
    双亲委派。
    沙箱安全。


沙箱安全机制。

Java 安全模型的核心就是 Java 沙箱(sandbox)。什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在 jvm 特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

所有的 Java 程序运行都可以指定沙箱,可以定制安全策略。

在 Java 中将执行程序分成本地代码远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的 Java 实现中,安全依赖于沙箱(Sandbox)机制。如下图所示 JDK 1.0 安全模型。

在这里插入图片描述

但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的 Java 1.1 版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示 JDK 1.1 安全模型。

在这里插入图片描述

在 Java 1.2 版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示 JDK 1.2 安全模型。

在这里插入图片描述

当前最新的安全机制实现,则引入了域(Domain)的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain)对应不一样的权限(Permission)。存在于不同域中的类文件就具有了当前域的全部权限。最新的安全模型(JDK 1.6)。

在这里插入图片描述

  • 组成沙箱的基本组件。
  • 字节码校验器(bytecode verifier)。
    确保 Java 类文件遵循 Java 语言规范。这样可以帮助 Java 程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
  • 类装载器(class loader)。
    其中类装载器在 3 个方面对 Java 沙箱起作用。
  • 它防止恶意代码去干涉善意的代码。
  • 它守护了被信任的类库边界。
  • 它将代码归入保护域,确定了代码可以进行哪些操作。

虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由 Java 虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。
类装载器采用的机制是双亲委派模式。

  • 从最内 JVM 自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用。
  • 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
  • 存取控制器(access controller)。
    存取控制器可以控制核心 API 对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
  • 安全管理器(security manager)。
    是核心 API 和操作系统之间的主要接囗。实现权限控制,比存取控制器优先级高。
  • 安全软件包(security package)。
    java.security 下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:
    安全提供者。
    消费摘要。
    数字签名。
    加密。
    鉴别。


本地接口 ~ JNI ~ Java Native Interface。

多线程问题引入。

进程、线程问题和编程语言无关。

package com.geek.thread;

public class ThreadDemo {

    public static void main(String[] args) {
        Thread t1 = new Thread();

        t1.start();
    }

}

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

t1.start(); —> 实际是调用 start0();

public
class Thread implements Runnable {
    private native void start0();

}

class 中的 start0(); 只有方法的声明,没有方法的实现。
调用底层操作系统或 C 语言函数库。

native 方法 —> 本地方法栈(Native Method Stack)。
java 方法 —> Java 栈(Java Stack)。

Java 1995 年诞生。C 语言称霸。
要向 C 语言“交保护费”。
在 C 语言的淫威下,Java 可耻的屈服了。
——> native。

Native Interface 本地接口。

本地接口的作用是融合不同的编程语言为 Java 所用,ta 的初衷是融合 C / C± 程序,Java 诞生的时候是 C / C± 横行的时候,要想立足,必须有调用 C / C± 程序。于是就在内存中专门开辟了一块区域处理标记为 native 的代码。ta 的具体做法是 Native Method Stack 中登记 native 方法,在 Execution Engine 执行时加载 native libraries。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过 Java 程序驱动打印机或者 Java 系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达,比如可以使用 Socket 通信,也可以使用 Web Service 等等。

Native Method Stack。

ta 的具体做法是 Native Method Stack 中登记 native 方法,在 Execution Engine 执行时加载本地方法库。

ps:t1.start(); 只能调用一次。

         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();



程序计数器 ~ Program Counter Register。

register
(in electronic devices) a location in a store of data, used for a specific purpose and with quick access time.

程序计数器是 CPU 的一部分。
—> 指针。—> 指向下一个要执行方法的地址。(课程表、值班表)。

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
这块内存区域很小,ta 是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器来读取下一条需要执行的字节码指令。
如果执行的是一个 native 方法,那这个计数器是空的。
用以完成分支、循环、跳转异常处理、线程恢复等基础功能。不会发生内存溢出(OutOfMemory=OOM)错误。

在这里插入图片描述

Runtime Data Area 中,
灰色 —> 线程私有,内存占用非常少,几乎不存在 gc 垃圾回收机制。



summary.
  1. JVM 系统架构图。
  2. 类加载器。
    2.1. 加载器种类。
    2.2. 双亲委派。
    2.3. 沙箱安全机制。
  3. native。

.start() 以后,新建完成到就绪状态。待底层操作系统和 CPU 的调度。不一定立刻马上启动。
native 是一个关键字,有声明,无实现。
Native Method Stack。

  1. PC 寄存器。
    4.1. 记录了方法之间的调用和执行情况,类似排班值日表。

    用来存储指向下一条指令的地址,也即将要执行的指令代码,ta 是当前线程所执行的字节码的行号指示器。

  2. 方法区。

在这里插入图片描述



Method Area ~ 方法区。

亮色。—> 所有线程共享。存在垃圾回收。

Method Area 方法区。

方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,eg. 构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区过属于共享区间。静态变量、常量、类信息(构造方法、接囗定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关。
static、final、class、常量池(1.7 之前,1.8 后在堆)。

供各线程共享的运行时内存区域。ta 存储了每一个类的结构信息,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容。上面讲的是规范,在不同虚拟机里实现是不一样的,最典型的就是永久代(PermGen)和元空间(MetaSpace)。

实例变量存在堆内存中,和方法区无关。

在这里插入图片描述

package com.geek;

public class Test {

    private int age;
    private String name = "geek";// 常量池。

    public static void main(String[] args) {
        Test test1 = new Test();
        test1.age = 3;
        test1.name = "geek";// 堆。
    }

}

在这里插入图片描述

Car Class —> 类的结构信息(模板)(变量、方法)。

空调 kt = new 格力();
LIst list = new ArrayList();
规范 <—> 不同实现。

天上飞的理念,必然有不同的落地实现。

汉堡 <—> 麦当劳,KFC,德克士、汉堡王。
空调 <—> 格力、海尔、小米。

方法区 f = new 永久带;(java 7 以前)。
方法区 f = new 元空间;(java 8).



Stack ~ 栈。

栈管运行,堆管存储。

e.printStackTrace();// 栈管运行,打印运行中的异常。

try {
    Thread.sleep(1);
} catch (InterruptedException e) {
    e.printStackTrace();
}

程序 = 算法 + 数据结构。—— 学院派。
程序 = 框架 + 业务逻辑。

栈。FIFO。
队列。FILO。

栈也叫栈内存,主管 Java 程序的运行,是在线程创建时创建,ta 的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就 over,生命周期和线程一致,是线程私有的。8 种基本类型的变量 + 对象的引用变量 + 实例方法都是在函数的栈内存中分配的。线程私有。

栈帧中主要保存 3 类数据。

  • 本地变量(Local Variable):输入参数和输出参数以及方法内的变量。
    栈操作(Operand Stack):记录出栈、入栈的操作。
    栈帧数据(Frame Data):包括类文件、方法等。
  • 栈帧:Java 方法进入 JVM 中就叫栈帧。
package com.geek;

public class StackDemo {

    private static void m1() {
        System.out.println("222");
        System.out.println("~~~1");
        System.out.println("333");
    }

    public static void main(String[] args) {
        System.out.println("111");
        m1();
        System.out.println("444");
    }
}

~~~

111
222
~~~1
333
444

Process finished with exit code 0

package com.geek;

public class StackDemo {

    private static void m1() throws InterruptedException {
        System.out.println("222");
        System.out.println("~~~1");
        System.out.println("333");

        Thread.sleep(Integer.MAX_VALUE);
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("111");
        m1();
        System.out.println("444");
    }
}


~~~

111
222
~~~1
333

...
...

  • 栈运行原理。

栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法 A 被调用时就产生了一个栈帧 F,并被压入到栈中。
A 方法又调用了 B 方法,于是产生 栈帧 F2 也被压入栈,
B 方法又调用了 C 方法,于是产生 栈帧 F3 也被压入栈,

执行完毕后,先弹出 F3 栈帧,再弹出 F2 栈帧,再弹出 F1 栈帧…
遵循先进后出 / 后进先出原则。
每个方法执行的同事都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体 JVM 的实现有关,通常在 256K ~ 756K 之间,约等于 1 MB 左右。

在这里插入图片描述

package com.geek;

public class StackDemo {

    private static void m1() throws InterruptedException {
        m1();
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("111");
        m1();
        System.out.println("444");
    }
}

~~~

Exception in thread "main" java.lang.StackOverflowError

StackOverflowError 算错误,不是异常。
public class StackOverflowError extends VirtualMachineError {
abstract public class VirtualMachineError extends Error {
public class Error extends Throwable {
public class Throwable( implements Serializable { )



堆、栈、方法区的交互。

在这里插入图片描述

geek@geek-PC:~$ java -version
openjdk version "1.8.0_181"
OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13)
OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)

geek@geek-PC:~/geek/tools_my/jdk1.8.0_241/bin$ ./java -version
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)

HotSpot —> jdk 的名字。
Goslin 提出了 jdk 的规范,只要把接口都实现,就可以自成一派。
Sun 公司 —> HotSpot。
BEA 公司 —> JRocket。
Oracle 收购了 Sun 和 BEA 全部软件、硬件。(两套 JVM 规范)。
合二为一 —> HotSpot。

还有 IBM 的 J9 Virtual Machine



Heap ~ 堆。

n. (凌乱的)一堆;许多;大量;破旧的汽车;老爷车
v. 堆积(东西);堆置;在…上放很多(东西);对(某人)大加赞扬(或批评等)

一个 JVM 只有一个堆内存。堆内存的大小是可以调节的。

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

物理上:新生 + 养老。
逻辑上:新生 + 养老 + 永久。

在这里插入图片描述

  • 永久区。

这个区域常驻内存。用来存放 jdk 自身携带的 Class 对象,Interface 元数据,存储的是 Java 运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭 VM 虚拟机就会释放这个区域的内存。

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

jdk 1.6 之前 ~ 永久代。常量池在方法区。
java 7 —> 永久代。慢慢退化,“去永久代”,常量池在堆中。
java 8 —> 元空间。无永久代,常量池在元空间。

在这里插入图片描述



引用类型 ~ 对象和 String 的区别。

对象和 String 的区别。

对象、String 的引用都在栈内存中。
但对象的实例在堆内存中,
而 String 的实例在常量池中。如果有就直接复用,如果没有则新建。

在这里插入图片描述

package com.geek.transferValue;

public class TransferValueDemo {

    public void changeValue01(int age) {
        age = 30;
    }

    public void changeValue02(Person person) {
        person.setName("xxx");
    }

    public void changeValue03(String string) {
        string = "xxx";
    }


    public static void main(String[] args) {

        TransferValueDemo test = new TransferValueDemo();

        int age = 20;
        test.changeValue01(age);
        System.out.println("age = " + age);

        Person person = new Person("Geek");
        test.changeValue02(person);
        System.out.println("person _ name = " + person.getName());

        String string = "geek";
        test.changeValue03(string);
        System.out.println("string = " + string);
    }
}

~~~

age = 20
person _ name = xxx
string = geek

Process finished with exit code 0



对象生命周期和 GC。

gc 之后有交换,谁空谁是 to。

在这里插入图片描述



MinorGC 的过程。(复制 —> 清空 —> 互换)。
  • eden, survivorFrom 复制到 SurviorTo,年龄 + 1。

首先,当 Eden 区満的时候会触发第一次 GC,把还活着的对象拷贝到 SurvivorFrom 区,当 Eden 区再次触发 GC 的时候会扫描 Eden 区和 From 区,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到 To 区域(如果有对象已经达到老年的标准,则复制到老年代区),同时把这些对象的年龄 + 1。

  • 清空 Eden, SurvivorFrom。

然后,清空 Eden 和 Survivor 中的对象,也即复制之后有交换,谁空谁是 To。

  • SurvivorrTo 和 SurvivorFrom 互换。

最后,SurvivorTo 和 SurvivorFrom 互换,原 SurvivorTo 成为下一次 GC 时的 SurvivorFrom 区。部分对象会在 From 和 To 区域复制来复制去,如此交换 15 次(由 JVM 参数 MaxTenuringThreshold 决定,这个参数默认是 15),最终如果还是存活就进入老年代。

在这里插入图片描述

在这里插入图片描述



永久带。

永久存储区是一个常驻内存区域,用于存放 JDK 自身所携带的 Class,Interface 的元数据,也就是说 ta 存储的是运行环境必须的类的信息,被装载进此区域的数据是不会被垃圾回收器回收的,关闭 JVM 才会释放此区域所占用的内存。

eg.
使用 Spring 时加的 Spring jar 包,
使用 SpringMVC 时加的 SpringMVC jar 包。



堆参数调优。

在 Java 8 中,永久代已经被消除,被一个称为元空间的区域所取代。元空间的本质和永久代类似。
元空间永久代最大区别:

永久带使用的是 JVM 堆内存,但是 java 8 以后的元空间并不在虚拟机中而是使用本机物理内存

因此,默认情况下,元空间的大小受本地内存限制。类的元数据放入 native memory,字符串池和类的静态变量放入 Java 堆中,这样可以加载多少类的元数据就不再由 MaxPermSize 控制,而由系统的实际可用空间来控制。

在这里插入图片描述

常量池元空间
JVM 默认使用物理内存的 1 / 4



堆内存调优。
-Xms设置初始分配大小。默认为物理内存的 1 / 64
-Xmx最大分配内存。默认为物理内存的 1 / 4
-XX:+PrintGCDetails输出详细的 GC 处理日志。
package com.geek;

public class JVMDemo {

    public static void main(String[] args) {

        // 返回 Java 虚拟机试图使用的最大内存。
        long maxMemory = Runtime.getRuntime().maxMemory();

        System.out.println("(-Xmx)maxMemory = " + maxMemory + "(字节)。" + (maxMemory / (double) 1024 / 1024) + " MB");

        // 返回 Java 虚拟机中的内存总量。
        long totalMemory = Runtime.getRuntime().totalMemory();

        System.out.println("(-Xmx)totalMemory = " + totalMemory + "(字节)。" + (totalMemory / (double) 1024 / 1024) + " MB");

        System.out.println("~ ~ ~ ~ ~ ~ ~");

        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}


~~~

(-Xmx)maxMemory = 1832910848(字节)。1748.0 MB-Xmx)totalMemory = 124780544(字节)。119.0 MB
~ ~ ~ ~ ~ ~ ~
4

Process finished with exit code 0

  • 调整。

一般要将 JVM 初始内存和最大内存调整为一样。避免怱高怱低。

在这里插入图片描述

VM Options

-Xms1024m -Xmx1024m -XX:+PrintGCDetails

在这里插入图片描述

再次运行,可以发现 maxMemorytotalMemory 已变化。

-Xmx)maxMemory = 1029177344(字节)。981.5 MB-Xmx)totalMemory = 1029177344(字节)。981.5 MB
~ ~ ~ ~ ~ ~ ~
4
Heap
 PSYoungGen      total 305664K, used 20971K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
  eden space 262144K, 8% used [0x00000000eab00000,0x00000000ebf7afb8,0x00000000fab00000)
  from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
  to   space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
 ParOldGen       total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
  object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
 Metaspace       used 3081K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 335K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

物理上,JVM 内存由 PSYoungGen(total 305664K)和 ParOldGen(total 699392K)组成。

305664 K + 699392 K = 1,005,056 K = 981.5 M
(-Xmx)maxMemory = 1029177344(字节)。981.5 MB
(-Xmx)totalMemory = 1029177344(字节)。981.5 MB

Metaspace 是逻辑上的。

package com.geek;

public class JvmDemo {

    public static void main(String[] args) {
        // 虚拟机试图使用的最大内存。字节。(电脑内存 1 / 4)。
        long maxMemory = Runtime.getRuntime().maxMemory();
        System.out.println("maxMemory = " + maxMemory);
        System.out.println(maxMemory / 1024 / 1024 + " MB");
        // jvm 初始化总内存。(电脑内存 1 / 16)。
        long totalMemory = Runtime.getRuntime().totalMemory();
        System.out.println("totalMemory = " + totalMemory);
        System.out.println(totalMemory / 1024 / 1024 + " MB");

        // maxMemory = 3779067904
        //3604 MB
        //totalMemory = 255328256
        //243 MB

        // -Xms1024m -Xmx1024m -XX:+PrintGCDetails

        // PSYoungGen(305664K) + ParOldGen(699392K) = 1,005,056k = 981M (元空间物理上不存在)。

        // maxMemory = 1029177344
        //981 MB
        //totalMemory = 1029177344
        //981 MB
        //Heap
        // PSYoungGen      total 305664K, used 15729K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
        //  eden space 262144K, 6% used [0x00000000eab00000,0x00000000eba5c420,0x00000000fab00000)
        //  from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
        //  to   space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
        // ParOldGen       total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
        //  object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
        // Metaspace       used 3148K, capacity 4496K, committed 4864K, reserved 1056768K
        //  class space    used 343K, capacity 388K, committed 512K, reserved 1048576K
        //
        //Process finished with exit code 0
    }

}



OOM。堆内存溢出。

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

模拟演示堆内存溢出

将 JVM 内存调整为 10M。

-Xms10m -Xmx10m -XX:+PrintGCDetails

测试代码。

package com.geek;

import java.util.Random;

public class OOMTest {

    public static void main(String[] args) {

        String str = "geek。";

        while (true) {
            str += str + new Random().nextInt(77777777) + new Random().nextInt(999999999);
        }
    }
}

GC … GC … GC … FULLGC … GC … FULLGC … GC … GC … FULLGC …
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

[GC (Allocation Failure) [PSYoungGen: 2013K->507K(2560K)] 2013K->627K(9728K), 0.0030688 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1953K->511K(2560K)] 2073K->916K(9728K), 0.0024636 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1958K->496K(2560K)] 2362K->1652K(9728K), 0.0014568 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1943K->144K(2560K)] 4506K->3411K(9728K), 0.0035471 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 887K->144K(2560K)] 5563K->4819K(9728K), 0.0014242 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 144K->144K(1536K)] 4819K->4819K(8704K), 0.0016892 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 144K->0K(1536K)] [ParOldGen: 4675K->2470K(7168K)] 4819K->2470K(8704K), [Metaspace: 3035K->3035K(1056768K)], 0.0117442 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 40K->32K(2048K)] 6735K->6726K(9216K), 0.0008385 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(2048K)] [ParOldGen: 6694K->1766K(7168K)] 6726K->1766K(9216K), [Metaspace: 3035K->3035K(1056768K)], 0.0100738 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 19K->0K(2048K)] 6010K->5990K(9216K), 0.0006650 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 5990K->5990K(9216K), 0.0005609 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 5990K->4582K(7168K)] 5990K->4582K(9216K), [Metaspace: 3035K->3035K(1056768K)], 0.0038578 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4582K->4582K(9216K), 0.0034333 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4582K->4563K(7168K)] 4582K->4563K(9216K), [Metaspace: 3035K->3035K(1056768K)], 0.0075858 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
	at java.lang.StringBuilder.append(StringBuilder.java:208)
	at com.geek.OOMTest.main(OOMTest.java:12)
Heap
 PSYoungGen      total 2048K, used 60K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 1024K, 5% used [0x00000000ffd00000,0x00000000ffd0f248,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 7168K, used 4563K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 63% used [0x00000000ff600000,0x00000000ffa74da0,0x00000000ffd00000)
 Metaspace       used 3067K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 334K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 1
package com.geek;

public class OOMTest {

    public static void main(String[] args) {

/*        String str = "geek。";

        while (true) {
            str += str + new Random().nextInt(77777777) + new Random().nextInt(999999999);
        }*/

        byte[] bytes = new byte[30 * 1024 * 1024];
    }
}

[GC (Allocation Failure) [PSYoungGen: 1257K->496K(2560K)] 1257K->504K(9728K), 0.0023745 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 496K->368K(2560K)] 504K->376K(9728K), 0.0052104 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 368K->0K(2560K)] [ParOldGen: 8K->336K(7168K)] 376K->336K(9728K), [Metaspace: 2993K->2993K(1056768K)], 0.0085719 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 336K->336K(9728K), 0.0025985 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 336K->318K(7168K)] 336K->318K(9728K), [Metaspace: 2993K->2993K(1056768K)], 0.0100414 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.geek.OOMTest.main(OOMTest.java:13)
Heap
 PSYoungGen      total 2560K, used 145K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 7% used [0x00000000ffd00000,0x00000000ffd247a0,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7168K, used 318K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 4% used [0x00000000ff600000,0x00000000ff64fb70,0x00000000ffd00000)
 Metaspace       used 3051K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 333K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 1


Jprofiler 软件。
package com.geek;

import java.util.ArrayList;
import java.util.List;

// -XX:+PrintGCDetails
// -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

// java.lang.OutOfMemoryError: Java heap space
//Dumping heap to java_pid8160.hprof ...
//Heap dump file created [6729355 bytes in 0.031 secs]
//Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
//	at com.geek.JprofilerTest.<init>(JprofilerTest.java:8)
//	at com.geek.JprofilerTest.main(JprofilerTest.java:16)
//
//Process finished with exit code 1

// 在目录下生成了 .hprof 文件。用 Jprofiler 软件打开。

// 04/11/2020  22:47    <DIR>          .
//04/11/2020  22:47    <DIR>          ..
//04/11/2020  22:51    <DIR>          .idea
//04/11/2020  22:47         6,729,355 java_pid8160.hprof
//03/11/2020  18:21    <DIR>          juc
//04/11/2020  00:47    <DIR>          jvm

public class JprofilerTest {

    byte[] array = new byte[1024 * 1024];// 1m。

    public static void main(String[] args) {
        List<JprofilerTest> list = new ArrayList<>();
        int iCount = 0;

        try {
            while (true) {
                list.add(new JprofilerTest());
                iCount += 1;
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("e = " + e);
            System.out.println("iCount = " + iCount);
        }
    }

}

在这里插入图片描述

在这里插入图片描述



GC 收集日志信息。
[GC (Allocation Failure)  // 分配失败。

[PSYoungGen: 1257K->496K(2560K)] 1257K->504K(9728K), 0.0023745 secs] 

[Times: user=0.01 sys=0.00, real=0.00 secs] 

在这里插入图片描述

在这里插入图片描述



GC ~ 分代收集算法。

次数上频繁收集 Young 区。
次数上较少收集 Old 区。
基本不动元空间。

gc 作用区域 ~ 方法区和堆。

在这里插入图片描述



GC 四大算法。

总体概述 ~ Minor GC 和 Full GC 的区别。
  • Minor GC(普通 GC、轻 GC)。

只针对新生代区域的 GC,指发生在新生代的垃圾收集动作,因为大多数 Java 对象存货率都不高,所以 Minor GC 非常频繁,一般回收速度也比较快。

  • Major GC or Full GC(全局 GC、重 GC)。

指发生在老年代的垃圾收集动作,出现了 Major GC,经常会伴随至少一次的 Minor GC(但并不是绝对 )。Major GC 的速度一般要比 Minor GC 慢 10 倍以上。



引用计数法。

应用:微软的 COM / ActionScript 3 / Python。
JVM 的实现基本不用。

缺点。

  • 每次对对象赋值时都要维护引用计数器,且计数器本身也有一定的消耗。
  • 较难处理循环引用。
package com.geek.gc;

public class RefCountGC {

    Object instance = null;
    private byte[] bigSize = new byte[2 * 1024 * 1024];// 这个成员属性的唯一作用就是占用一点内存。

    public static void main(String[] args) {
        RefCountGC objectA = new RefCountGC();
        RefCountGC objectB = new RefCountGC();
        objectA.instance = objectB;
        objectB.instance = objectA;
        objectA = null;
        objectB = null;

        System.gc();// 手动挡,手动唤起 GC。
        // 并不是立刻执行。
        // 开发中一般不要使用。
    }
}



补充。(HelloWorld 有几个线程?——> 2 个。main 线程和 GC)。
public class HelloWorld {
	
	public static void main(String[] args) {

		System.out.println("Hello World,!");
	}

}


复制算法(Copying)。

“每次 GC 会把伊甸园区的和 From 区的对象拷贝到 To 区,一但 Eden 被 GC”。此时用的就是 Copying 算法。From 区和 To 区发生交换,交换后谁空谁是 To 区。

年轻代中使用的是 Minor GC —> 复制算法。

-XX:MaxTenuringThreshold ——> 设置对象在新生代中存活的次数。
// java 8 不能超过 15。

不想自己画图了,参考:https://www.jianshu.com/p/9e6841a895b4

在这里插入图片描述

  • 优点。

无碎片。

  • 缺点。
  • 浪费了一半的内存 ——> 致命。
  • 如果对象的存活率很高,(稍微极端一点,假设 100% 存活),那么所有的对象都要复制一遍,并将所有的引用地址重置一遍,复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变得不可忽略。——> 复制算法要想使用,最起码对象的存活率要非常低才行,more important, 我们必须要克服 50% 的内存浪费。


标记清除(Mark-Sweep)。

复制算法(Copying)[浪费空间 ——> ] 标记清除(Mark-Sweep)。

老年代一般是由标记清除或者是标记清除和标记整理的混合实现。

算法分成标记清除两个阶段。

先标记出要回收的对象,然后统一回收这些对象。

  • 优点。

不需要额外空间。

  • 缺点。

两次扫描,耗时严重。
会产生内存碎片。

在这里插入图片描述



标记压缩(Mark-Compact)。

解决碎片问题。

标记清除 + 整理。

慢工出细活。(耗时)。

  • 优点。

没有内存碎片,可以利用 bump-the-pointer。

  • 缺点。

需要移动对象的成本。

在这里插入图片描述



引申:标记 + 清除 + 压缩。(Mark-Sweep-compact)。

Mark-Sweep 和 Mark-Compact 的结合。
和 Mark-Sweep 一样,当进行多次 GC 后才 Compact。

没有最好的方法,分代收集算法。——> 根据不同代的特性选择。
  • 年轻代。

存活率低 -> 复制算法。

  • 老年代。

区域大,存活率高 -> 标记清除 + 标记压缩。

内存效率:

复制算法 > 标记清除算法 > 标记整理算法。(简单对比时间复杂度,实际情况不一定)。

内存整齐度:

复制算法 = 标记清除算法 > 标记清除算法

内存利用率:

标记清除算法 = 标记整理算法 > 复制算法



JMM。Java memory model.

Java 内存模型。

volatile 是 Java 虚拟机提供的轻量级的同步机制。(低配版 sychronized)。

保证可见性。
保证原子性。
禁止指令重排。

  • JMM.

JMM (Java 内存模型,Java Memory Model,简称 JMM)本身是一种抽象的概念并不真实存在,ta 描述的是一组规则或规范,通过这组规范定义了程序中各个变量包括实例字段,静态字段和构成数组对象的元素)的访问方式。

由于 JVM 运行程序的实体是线程,而每个线程创建对 JVM 都会为其创建一个工作内存(有些地方称其为栈空间)。工作内存是每个线程的私有数据区域,而 Java 内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程堆变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存中拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如图。

在这里插入图片描述

可见性。

A B 两线程。有一瓶水,B 把水换成了可乐,有一种机制可以让 A 知道”水已经被换成了可乐“。



原子性。

事务要么一起成功,要么一起失败。
A 给 B 转账 100,A 账户已经 -100,中途出现 bug,B 账户没有 +100,那么 A 账户也不会 -100。



VolatileDemo 代码演示可见性 + 原子性。
package com.geek;

class MyNumber {
    int number = 10;
//    volatile int number = 10;

    void addTo1024() {
        this.number = 1024;
    }
}

/**
 * JMM ——> 可见性(通知机制)。
 */
public class JMMDemo {

    public static void main(String[] args) {

        MyNumber myNumber = new MyNumber();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " ~ ~ ~ comes in.");
            try {
                // 让 main() 线程启动往下执行。
                Thread.sleep(3000);

                myNumber.addTo1024();// 将 10 修改为 1024。
                System.out.println(Thread.currentThread().getName() + "\tupdate number, number value: " + myNumber.number);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();
        // 产生共享变量 number = 10。

        // main 线程直接运行至此。
        while (myNumber.number == 10) {
            // 需要有一种机制告诉 main 线程,number 已经修改为 1024,跳出 while。就会打印 mission is over.

        }

        System.out.println(Thread.currentThread().getName() + "\t mission is over.");
    }

}

// A ~ ~ ~ comes in.
//A	update number, number value: 1024

// A 线程已经把 a 改为了 1024,但并没有通知了 main 线程。
// 所以 A 认为 myNumber.number == 10 是 true,不会跳出循环,程序卡住。
// 给 int a 加上 volatile 即可。对其他线程的可见性通知。


~~~

A~ ~ ~ comes in.
A	update number, number value: 1024

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

// 此时程序永不会结束。
// A 线程已经把 number 值改为 1024。(A	update number, number value: 1024)
// 但 main 线程并不知道。

给 int number = 10; 加上关键字 volatile
volatile 使变量天然对其他线程具有可见性。
线程在自己的工作内存改变变量后,立刻写回主内存并通知其他线程。

volatile int number = 10;

A~ ~ ~ comes in.
A	update number, number value: 1024
main	 mission is over

Process finished with exit code 0



有序性。

按照代码的顺序执行。



静态代码块 > 静态方法 > 构造块 > 构造方法。

package com.geek;

class CodeGeek {

    static {
        System.out.println("code 的静态代码块 333。");
    }

    {
        System.out.println("code 的构造块 222。");
    }

    public CodeGeek() {
        System.out.println("code 的构造方法 111。");
    }

}

/**
 * 主类。
 * 先有 CodeBlockDemo.class,再有 main(); 方法。
 * (
 * ~ CodeBlockDemo 的静态代码块 555。
 * ~ ~ ~ ~ ~ ~ ~ CodeBlockDemo 的 main() 方法 777。
 * )
 */
public class CodeBlockDemo {

    // 静态块 > 构造块 > 静态方法。
    // code 的静态代码块 333。
    //code 的构造块 222。
    //code 的构造方法 111。

    static {
        System.out.println("CodeBlockDemo 的静态代码块 555。");
    }

    {
        System.out.println("CodeBlockDemo 的构造块 444。");
    }

    public CodeBlockDemo() {
        System.out.println("CodeBlockDemo 的构造方法 666。");
    }

    public static void main(String[] args) {
        System.out.println("~ ~ ~ ~ ~ ~ ~ CodeBlockDemo 的 main() 方法 777。");

        new CodeGeek();
        System.out.println("~ ~ ~ ~ ~ ~ ~");
        new CodeGeek();
        // 静态只一次。
        // code 的构造块 222。
        //code 的构造方法 111。
        System.out.println("~ ~ ~ ~ ~ ~ ~");
        new CodeBlockDemo();
        // CodeBlockDemo 的构造块 444。
        //CodeBlockDemo 的构造方法 666。
        new CodeBlockDemo();
        // CodeBlockDemo 的构造块 444。
        //CodeBlockDemo 的构造方法 666。
    }

}

/*
CodeBlockDemo 的静态代码块 555。
~ ~ ~ ~ ~ ~ ~ CodeBlockDemo 的 main() 方法 777。
code 的静态代码块 333。
code 的构造块 222。
code 的构造方法 111。
~ ~ ~ ~ ~ ~ ~
code 的构造块 222。
code 的构造方法 111。
~ ~ ~ ~ ~ ~ ~
CodeBlockDemo 的构造块 444。
CodeBlockDemo 的构造方法 666。
CodeBlockDemo 的构造块 444。
CodeBlockDemo 的构造方法 666。

Process finished with exit code 0

 */

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lyfGeek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值