JVM内存结构、Java内存模型(JMM)、Java对象内存模型
1、JVM内存结构
Java虚拟机在运行时对该Java进程占用的内存进行的一种逻辑上的划分,包括方法区、堆内存(OOM)、虚拟机栈(StackOverFlowError,OOM)、本地方法栈、程序计数器。
1、局部变量表:一片连续的内存空间,用来存放方法参数,以及方法内定义的局部变量,存放着编译期间已知的数据类型(八大基本类型和对象引用(reference类型),returnAddress类型。
Java虚拟机栈可能出现两种类型的异常:
- 线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
- 虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。
2、Java堆,它是所有线程共享的,它的目的是存放对象实例(所有对象实例及数组都要在堆上分配内存)。
同时它也是GC所管理的主要区域,因此常被称为GC堆,又由于现在收集器常使用分代算法
CG:
①虚拟机栈(栈桢中的本地变量表)中的引用的对象。
②方法区中的类静态属性引用的对象,一般指被static修饰引用的对象,加载类的时候就加载到内存中。
③方法区中的常量引用的对象,
④本地方法栈中JNI(native方法)引用的对象
标记清除、标记整理、复制算法
分代收集算法的原理是采用复制算法来收集新生代,采用标记/清理算法或者标记/整理算法收集老年代
3、方法区同堆一样,是所有线程共享的内存区域,为了区分堆,又被称为非堆。
用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。
附:
我们知道是因为内存溢出导致的应用无法正常启动,也知道需要配置启动参数才能让应用正常启动,但是应该设置哪几个参数呢?这时就需要了解常用的参数了:
1. -Xmx3550m:设置JVM最大堆内存为3550M。
2. -Xms3550m:设置JVM初始堆内存为3550M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
3. -Xss128k:设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,之前每个线程栈大小为256K。应当根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。需要注意的是:当这个值被设置的较大(例如>2MB)时将会在很大程度上降低系统的性能。
4. -Xmn2g:设置年轻代大小为2G。在整个堆内存大小确定的情况下,增大年轻代将会减小年老代,反之亦然。此值关系到JVM垃圾回收,对系统性能影响较大,官方推荐配置为整个堆大小的3/8。
5. -XX:NewSize=1024m:设置年轻代初始值为1024M。
6. -XX:MaxNewSize=1024m:设置年轻代最大值为1024M。
7. -XX:PermSize=256m:设置持久代初始值为256M。
8. -XX:MaxPermSize=256m:设置持久代最大值为256M。
9. -XX:NewRatio=4:设置年轻代(包括1个Eden和2个Survivor区)与年老代的比值。表示年轻代比年老代为1:4。
10. -XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的比值。表示2个Survivor区(JVM堆内存年轻代中默认有2个大小相等的Survivor区)与1个Eden区的比值为2:4,即1个Survivor区占整个年轻代大小的1/6。
11. -XX:MaxTenuringThreshold=7:表示一个对象如果在Survivor区(救助空间)移动了7次还没有被垃圾回收就进入年老代。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代,对于需要大量常驻内存的应用,这样做可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代存活时间,增加对象在年轻代被垃圾回收的概率,减少Full GC的频率,这样做可以在某种程度上提高服务稳定性。
参考链接:https://blog.csdn.net/u013700110/article/details/79208681
参考:https://www.jianshu.com/p/bf158fbb2432
https://www.jianshu.com/p/76959115d486
2、Java内存模型(JMM)
Java内存模型是Java语言在多线程并发情况下对于共享变量读写(实际是共享变量对应的内存操作)的规范,
主要是为了解决多线程可见性、原子性的问题,解决共享变量的多线程操作冲突问题。
JMM的两条规定
1、线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写;
2、不同的线程之间无法直接访问其他线程工作内存中的变量,线程变量值的传递需要通过主内存来完成。
一、并发编程问题
多线程并发编程会涉及到以下的问题:
1)原子性:指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。
2)可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
3)有序性:程序执行的顺序按照代码的先后顺序执行,多线程中为了提高性能,编译器和处理器的常常会对指令做重排(编译器优化重排、指令并行重排、内存系统重排)。
二、解决并发编程
1)原子性:Java提供了两个高级字节码指令monitorenter和monitorexit,对应的是关键字synchronized,使用该关键字保证方法和代码块内的操作的原子性。
2)可见性:Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。
除了volatile,Java中的synchronized和final
两个关键字也可以实现可见性,只不过实现方式不同
3)有序性:用volatile关键字禁止指令重排,用synchronized关键字加锁。
3、Java对象模型
java对象模型其实就是JVM中对象的内存布局。一个对象本身内在结构的描述信息以字节码的方式存储在方法区中(参见java内存区域),说白了就是class文件。那么如何获取到对象的class信息呢?虚拟机使用对象头部的一个指针指向 Class 区域,找到对象的 Class 描述。在虚拟机中,对象在内存中的存储布局分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding):