【JVM】内存结构和JVM调优

内存结构

在这里插入图片描述

Name线程私有/共享设置参数Function
程序计数器(PC Registers)私有固定大小保存当前线程执行的方法
本地方法栈(Native Method Stack)私有固定大小JNI调取本地方法
方法区(Method Area)共享-XXstatic,class类结构,常量(池)
堆(Heap)共享-Xms:初始堆值;-Xmx:最大堆值new出来的实例对象和数组
JVM栈(JVM Stack)私有-Xss(每个线程)方法的栈帧、局部变量表、操作数栈

1.程序计数器(PC Registers)

  • 一块小内存,每个线程都有
  • PC存储当前方法:
    • 线程正在执行的方法称为该线程的当前方法
    • 当前方法为本地(native)方法时,pc值未定义(undefined)
    • 当前方法为非本地方法时,pc包含了当前正在执行指令的地址
  • 当前唯一一块不会引发OutOfMemoryError异常

2.本地方法栈(Native Method Stack)

  • 存储native方法的执行信息(Java调用C程序执行代码时的信息),线程私有
    (C函数就叫Native方法)
  • 引发的异常:
    • 栈的深度超过虚拟机规定深度,就会引发StackOverflowError异常
    • 无法扩展内存,OutOfMemoryError异常
  • 比如:安卓开发用到的C语言,都会使用Java语言JNI,就是Java语言去调用C语言

3.方法区(Method Area)

  • 存储JVM已经加载过类的结构
    (类加载器:在我们程序运行的时候,需要加载很多类进来,比如rt.jar里面那些基础的类都要加载进来,对于一个JVM来说,它加载的类是有限制的,一个JVM它可能不能负担很多很多类,这个就被方法区给受限,那么方法区时存储JVM已经加载过类的结构)
  • 所有线程共享,所有线程加载过的类都会放在方法区里面
  • 那么,运行时的常量池、类信息、常量、静态变量等
    • 在Java(JVM)启动的时候呢,会创建方法区,逻辑上属于堆内存的一部分
    • (当类的字节码文件被加载到内存(?这里指方法区)时,类的实例方法不会被分配入口地址,在该类创建对象后,类中的实例方法才分配入口地址(堆)。)
  • 很少做垃圾回收
  • 无法满足内存分配要求,OutOfMemoryError异常
  • “-XX”参数设置大小
  • static静态变量或方法不要定义太多,因为GC垃圾回收机制是不会回收的

(什么时候会加载几十万个类呢?比如说我们通常会将JSP编译成Servlet,那么,所有的网页页面都会被编译成一个Java类,如果一个系统里面,它的网页数量过多,它用到的Java类就会非常多,那么这个方法区的大小就会对程序的运行就有影响)

3-1.运行时的常量池(Run-Time Constant Pool)

  • Class文件中常量池的运行时表示
  • (每个Class里面都有一个Constant Pool,那么在运行的时候,这个类被加载进来,Class里的Constant Pool就会被映射到这块内存区域上)编译期确定的常量
    属于方法区的一部分
    动态性
  • Java语言并不要求常量一定只有在编译期产生(不一定都是编译期就能确定的常量)
  • 比如String.intern方法也可以将字符串放入运行时的常量池
    引发的异常:
    无法满足内存分配要求,OutOfMemoryError异常

4.堆(Heap)

  • 虚拟机启动时开始创建,
  • 什么时候会把数据放在堆内存上呢?
    • 当我们在程序里面采用new操作,或者定义一个数组的时候,那我们都是在堆上分配内存的
    • 对象实例和数组都是堆上分配内存的
  • 占了太大比例,因此,这一块也是垃圾回收的主要区域:没有用的垃圾对象进行回收
  • 设置大小
    “-Xms”初始堆值,“-Xmx”最大堆值
  • 引发的异常
    无法满足内存分配要求,OutOfMemoryError异常:
    1:程序消耗的性能太多,内存不够用
    2:内存泄漏,有些对象没有及时地被垃圾回收
    那么需要对程序进行细微的检查
  • 因为线程共享,这里定义的成员变量就是共享数据,存在线程安全的问题

4-1.堆内存结构(新生代和老年代)

5.JVM栈(JVM Stack)

  • (这个变量是放在栈里面的,这个变量是放在堆里面的,实际上就是指JVM栈包含的)

  • 每个线程有自己独立的一个Java虚拟机栈:有10个线程就有10个JVM栈

  • “-Xss”设置每个线程堆栈大小

  • Java方法的执行基于栈
    每个方法从调用到完成对应一个栈帧在栈中入栈、出栈的过程(压栈)

  • 栈帧存储局部变量表、操作数栈等
    局部变量表存放方法中存放在“栈”里面的东西,也就是局部变量之类的东西

  • (定义一个小int,一个byte这一类的,我们都是在JVM栈上面来定义)

  • 局部变量
    (在Java字节码那一章里面可以知道栈帧,我们在每一个栈帧里面,都存储着局部变量表、操作数栈等等,你把一个class文件进行反编译以后,你就会看到里面的Code区域都有很多指令,那么每个指令,实际上,都是从局部变量表里面,把数据搬到操作数栈上面处理,处理完都又把它放回局部变量表里面去)

  • 在多线程情况下,共享同一个局部变量,不会发生线程安全。因为栈就是每个线程都有自己的局部变量表

  • 引发的异常:
    栈的深度超过虚拟机规定深度,就会引发StackOverflowError异常
    (使用过多的变量)无法扩展内存,OutOfMemoryError异常

JVM调优

  • 一般:初始堆内存(-Xms)和最大堆内存(-Xmx)都是一致
  • 初始堆内存设置比较小的话,垃圾回收机制会比较频繁去回收,这样会耗程序。
  • -XMS和-XMX设置成一致,这样做的好处是可以减少程序运行时垃圾回收次数,从而提高效率
  • 最大堆内存跟电脑硬件配置:一般接近电脑配置
    • 操作系统分配给每个进程的内存是有限制的,譬如32位的Windows限制为2GB。
      虚拟机提供了参数来控制Java堆和方法区的这两部分内存的最大值。剩余的内存为2GB(操作系统限制) Xmx(最大堆容量),再MaxPermSize(最大方法区容量),程序计数器消耗内存很小,可以忽略掉
  • 设置新生代大小与老年代优化参数:
    • "-Xmn"新生代大小,一般设为整个堆的1/3到1/4左右
    • “-XX:SurvivorRatio”:设置新生代中edenhe from/to空间的比例关系n/1
    • “-XX:NewRatio”:老/新
    • 需求:可能会有很多经常使用的对象:这时,老年代越大越好,新生代就越小,尽量减少老年代的GC
    • 需求:有些对象不怎么经常使用,但是使用新对象特别多。这时新生代要大点好

内存模型

指针压缩

在这里插入图片描述
线性地址不开是8字节,开是4字节
线性地址8字节=64bit=48在用+16保留
2^48=256T
对象内存结构
在这里插入图片描述
空对象:
未开启指针压缩 16B
-XX:+/-UseCompressedOops
开启指针压缩:
16B=8B(Mark Word)+4b(KClass pointer)+0(实例数据)+4B(对齐填充)
普通对象:
在这里插入图片描述
对象头里面才有填充
在这里插入图片描述
内存地址
16=10
24从48算起=30
在这里插入图片描述
oop多少位 32+3=35
2^35
扩容补0
在这里插入图片描述
jol
在这里插入图片描述
虚拟机栈溢出

  • 调用链过长
  • 死循环
  • 无限递归
  • java -XX:+PrintFlagsFinal -version
    grep ThreadStack

-Xss160k

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值