返回目录
全称是Java Memory Model。JMM关键技术点都是围绕着多线程的原子性、可见性、有序性来建立的。
原子性
原子性是指操作是不可分的,要么全部一起执行,要么不执行
可见性
可见性是指一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的。
Volatile关键字可以使工作内存中的数据更新到主内存,这样,在其他线程使用此数据时不会造成脏数据
有序性
有序性指的是程序按照代码的先后顺序执行。
JMM抽象模型分为主内存、工作内存
主内存是所有线程共享的,一般是实例对象、静态字段、数组对象等存储在堆内存中的变量。
工作内存是每个线程独占的,线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存中的变量,线程之间的共享变量值的传递都是基于主内存来完成。
JMM是如何解决可见性有序性问题的
简单来说,JMM通过使用内存屏障,提供了一些禁用缓存以及禁止重排序的方法,来解决可见性和有序性问题。这些方法大家都很熟悉:volatile、synchronized、final;
JVM内存结构
包括:
• 程序计数器(PC)
• java虚拟机栈
• 本地方法栈
• java堆
• 方法区
方法区(MethodArea)也称"永久代“
方法区是被所有线程共享的内存区域,用来存储已被虚拟机加载的类信息、常量、静态变量、JTI(just in time,即时编译技术)编译后的代码等数据。
运行时常量池是方法区的一部分,用于存放编译期间生成的各种字面常量和符号引用。
当存放方法区数据的内存溢出时,会报OutOfMemoryError异常,在jdk1.8中也就是Metaspace内存溢出。
可以通过参数JVM参数-XX:MetaspaceSize和-XX:MaxMetaspaceSize设置Metaspace的空间大小。
随着JDK8的到来,JVM不再有 永久代(PermGen)。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)。
java堆(Heap)
java堆是被所有线程共享的内存区域,也是jvm管理的最大一块内存,几乎所有的对象实例都在heap中分配。
程序计数器
程序计数器是线程独享的,指向当前线程正在执行的字节码指定的地址。
虚拟机栈
虚拟机栈是线程独享的,存储每个线程中运行的各个方法所需要的数据、指令、返回地址等信息,用于描述java方法执行的内存模型。
-Xss用于指定栈的深度(栈帧的个数),如果栈深度太深,则会报异常StackOverFlowException
本地方法栈
本地方法栈与虚拟机栈类似,区别在于虚拟机栈执行的是java方法,而本地方法栈则为执行虚拟机中的native本地方法。
JVM优化
Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 )
• -Xms设置堆的最小空间大小。
• -Xmx设置堆的最大空间大小。
• -Xmn:设置年轻代大小
• -XX:NewSize设置新生代最小空间大小。
• -XX:MaxNewSize设置新生代最大空间大小。
• -XX:PermSize设置永久代最小空间大小。
• -XX:MaxPermSize设置永久代最大空间大小。
• -Xss设置每个线程的堆栈大小
• -XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
• -XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
常量池
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
静态常量池:
主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:
类和接口的全限定名
字段名称和描述符
方法名称和描述符
运行时常量池
是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
常量池的好处
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。