发布于2021-06-08 11:44 阅读(953) 评论(0) 点赞(2) 收藏(4)
Java 内存模型(即 Java Memory Model,简称 JMM )本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
lock:作用于主内存,锁住主内存主变量。
unlock:作用于主内存,解锁主内存主变量。
read:作用主内存,主内存传递到工作内存。
load:作用于工作内存,主内存传递来的值赋给工作内存工作变量。
use:作用工作内存,工作内存工作变量值传给执行引擎。
assign:作用工作内存,引擎的结果值赋值给工作内存工作变量。
store:作用于工作内存的变量,工作内存工作变量传送到主内存中。
write:作用于主内存的变量,工作内存传来工作变量赋值给主内存主变量。
read and load 从主存复制变量到当前工作内存;
use and assign 执行代码,改变共享变量值;
store and write 用工作内存数据刷新主存相关内容。
其中use and assign 可以多次出现。
但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样。
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的。
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile 关键字会强制将修改的值立即写入主存。
2)禁止进行指令重排序。
volatile 不是原子性操作,只保证可见性和有序性,不保证原子性。
使用 volatile 一般用于状态标记量和单例模式的双检锁。
public class ThreadTest extends Thread {
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(1000L);//延迟一下让主线程循环跑起来
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
System.out.println(threadTest.flag);
while (true) {
if (threadTest.flag) {//主程读取
break;
}
}
System.out.println("do something.");
}
}
多核 CPU 下如果 flag 变量不加 volatile 关键字,主程读取 flag 变量这里可能一直读到的是 false,volatile 关键字会强制子线程将修改的值立即写入主存,并将其他线程下的缓存行失效,强制其他线程再访问此变量时从主内存中获取,达到可见性的效果。
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();//创建对象并不是一个原子操作
}
}
}
return instance;
}
}
单例模式用到,new 对象不是原子操作,分三步:
由于 CPU 可能的优化排序,第三步可能会先与第二步执行,这时其他线程读到就会由问题,可用 volatile 禁止指令重排序避免此问题。
volatile 关键字使用的是 Lock 指令,volatile 的作用取决于 Lock 指令。CAS 不是保证原子的更新,而是使用死循环保证更新成功时候只有一个线程更新,不包括主工作内存的同步。 CAS 配合 volatile 既保证了只有一个线程更新又保证了多个线程更新获得的是最新的值互不影响。
栈是线程私有,用来存放局部变量、对象引用和常量池引用。方法执行的时候会创建一个栈帧,存储了局部变量表、操作数栈、动态链接和方法出口信息。每个方法从调用到执行完毕,对应一个栈帧在虚拟机中的入栈和出栈。
Java 虚拟机栈会出现两种异常:
StackOverFlowError :若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
OutOfMemoryError : 若 Java 虚拟机栈的内存允许动态扩展,并且当线程请求栈时内存用完了,无法再动态扩展了,就会抛出 OutOfMemoryError 异常。
线程私有,和虚拟机栈类似,主要为虚拟机使用到的 Native 方法服务,也会抛出 StackOverFlowError
和 OutOfMemoryError
错误。
线程私有,是当前线程锁执行字节码的行号治时期,每条线程都有一个独立的程序计数器,这类内存也称为“线程私有”的内存。正在执行 Java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是 Native 方法,则为空。
线程共享,在虚拟机启动的时候创建,用于存放对象实例。通过-Xmx 和-Xms 来控制大小。
堆区域可分为新生代、老年代。(方法区中的永久代在 JDK 1.8及之后已经移除,实现为元空间)。
新生代可分为 Eden 空间、From Survivor 和 To Survivor 空间等。
堆容易出现的错误是 OutOfMemoryError
错误,表现有几种:
OutOfMemoryError: GC Overhead Limit Exceeded
:当JVM花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。java.lang.OutOfMemoryError: Java heap space
:假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发这个错误(和本机物理内存无关,和你配置的堆内存大小有关)。线程共享,用于存储已被 JVM 加载的类信息、静态变量、常量、属性和方法信息。
永久代和元空间都是方法区的一种实现,JDK1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小:
-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常 `java.lang.OutOfMemoryError: PermGen`
JDK 1.8及之后永久代被彻底移除了,取代的是元空间,元空间使用的是直接内存,常用设置参数:
-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
永久代 (PermGen) 替换为元空间 (MetaSpace) 的原因是永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,只受本机可用内存的限制。元空间也会溢出,但比原来出现的几率小。
常量池,即 class 文件常量池,是 class 文件的一部分,用于保存编译时确定的数据;
类加载后,常量池信息就会放入运行时常量池,并将常量池内符号引用替换成直接引用。运行时常量池是动态的,程序运行期间也可能产生新的常量,这些常量被放到运行时常量池中。
运行时常量池也是方法区的一部分。
字符串常量池是由 StringTable 实现的,其实就是一个 HashTable,存储在堆区。
字符串常量池保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。
String a = "123";
String b = "123";
String c = new String("123");
String d = new String("123");
String e = "12";
String f = "3";
String g = e + f;
g.intern();//池化后没返回
System.out.println(a == b);//true a 和 b 都指向同一引用对象
System.out.println(c == d);//false 在堆中是两个不同对象
System.out.println(a == c);//false a 在 String Pool 中,c 在堆中,不同对象
System.out.println(a == g);//false + 号是使用 StringBuilder 实现拼接的
g = g.intern();
System.out.println(a == g);//true g 池化后返回给 g,跟 a 指向同一对象
对以上各种 String 对象创建的解释:
String a = "123";
在堆中创建对象,这个 String 对象包含了各种指针类型的成员对象,其内部实现 value 则是指向了存储在字符串常量池的字符串“123”;
String c = new String("123");
如果字符串常量池中不存在“123”字符串,直接在堆中创建对象然后返回给变量;如果字符串常量池中存在“123”字符串,会先在堆中创建一个 c 变量的对象引用,再将引用指向已经存在的常量对象,其实就是上面的 a 指向的对象;
g = g.intern()
如果当前字符串内容存在于字符串常量池,那直接返回此字符串在常量池的引用;如果之前不在字符串常量池中,那么在常量池创建一个引用并且指向堆中已存在的字符串,然后返回常量池中的地址。
存储位置及区别区别
字符串常量池和运行时常量池是两个独立不同的东西。
JDK1.7 之前的运行时常量池,字符串常量池存放在方法区,JDK1.7 开始把字符串常量池方法区拿到了堆中,运行时常量池还在方法区。
到 JDK1.8 使用元空间替代永久区来实现方法区,此时运行时常量池在元空间,字符串常量池在堆。
其中验证,准备,解析一般合称链接。
Java 自带的加载器的类,在虚拟机的生命周期中是不会被卸载的,只有用户自定义的加载器加载的类才可以被卸载。
一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载。
优点:
Thread.currentThread().getContextClassLoader()
来加载实现类。线程上下文类加载器就是当前线程的 CurrentClassloader。Java 对象由三个部分组成:对象头、实例数据、对齐填充。
判断对象已死去的方法有引用计数法(已淘汰)和根搜索法(可达性分析算法)
年轻代分为 Eden 区和 Survivor 区(两块分别为 From Survivor 和 To Survivor 交替使用,哪个被回收了就由 From 变成 To),且 Eden:From:To = 8:1:1。
-XX:PretenureSizeThreshold
,大于该值的对象会直接进入年老代);因为新生代内存区域我们使用了复制算法,而使用复制算法的目的,也是为了消除内存碎片。
新建的对象放到 Eden 中,一旦 Eden 满了,触发 Minor GC,Eden 中的存活对象就会被移动到Survivor 区。下一次 Eden 满了的时候,再进行Minor GC,Eden 和 Survivor 各有一些存活对象,如果只有一个 Survivor,Eden 第二次的 GC 的存活对象也是放在唯一的一个 Survivor 区域中。但此时把 Eden 区的存活对象硬放到 Survivor 区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。
标准命令:- 开头,所有 HotSpot 都支持
非标准命名:-X 开头,特定版本 HotSpot 支持的命令
不稳定命令:-XX 开头,下个版本可能取消
设置值:-XX:+PrintFlagsFianl
默认值:-XX:+PrintFlagsInitial
启动命令行参数:-XX:+PrintCommandLineFlags
-Xms20m 设置堆最小内存20MB
-Xmx20M 设置堆最大内存20MB
当-Xms和-Xmx值相同表示不允许堆内存进行扩展
-Xmn5m 表示新生代内存为5MB
-XX:NewRatio=3 表示新生代占堆内存1/3
-XX:SurvivorRatio=8 表示Eden:2个Survivor = 8:2
DirectMemory 容量可通过 MaxDirectMemorySize 指定,如果不指定,则默认与 Java 堆最大值一样。虽然 DirectMemory 内存溢出时也会抛出内存溢出异常,但它抛出的异常时并没有真正向操作系统申请内存分配,于是手动抛出异常。一个明显的特征是在 Heap Dump 文件中不会看见明显的异常,如果发现 OOM 之后 Dump 文件很小,而且程序中又直接或者间接使用了 NIO,那就可以考虑检查一下是不是这方面的原因。
ulimit -a
;用户进程数增大 (-u) 1800;使用 -Xss 减小线程堆栈大小JVM调优是比较高深的学问,包括设置合理的内存参数,选择合理垃圾回收器甚至修改 JVM 代码等等。在一般项目中很少会去做具体调优。比较常用简单的优化:
Sun JDK监控和故障处理命令有 jps jstat jmap jhat jstack jinfo
常用调优工具分为两类,JDK 自带监控工具:jconsole 和 jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。
原文链接:https://blog.csdn.net/u012809062/article/details/117558017
作者:java小王子
链接:http://www.javaheidong.com/blog/article/219556/537752861afa409cc65c/
来源:java黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!