精华内容
下载资源
问答
  • 1.什么是jvm?(1)jvm是一种用于计算设备的规范,它是一个虚构出来的机器,是通过在实际的计算机上仿真模拟各种功能实现的。(2)jvm包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域。(3)JVM...

    1.什么是jvm?

    (1)jvm是一种用于计算设备的规范,它是一个虚构出来的机器,是通过在实际的计算机上仿真模拟各种功能实现的。

    (2)jvm包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域。

    (3)JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

    JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

    2.jdk、jre、jvm是什么关系?

    (1)JRE(Java Runtime Environment),也就是java平台。所有的java程序都要在JRE环境下才能运行。

    (2)JDK(Java Development Kit),是开发者用来编译、调试程序用的开发包。JDK也是JAVA程序需要在JRE上运行。

    (3)JVM(Java Virtual Machine),是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

    JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。

    Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。

    3.JVM原理

    (1)jvm是java的核心和基础,在java编译器和os平台之间的虚拟处理器,可在上面执行字节码程序。

    (2)java编译器只要面向jvm,生成jvm能理解的字节码文件。java源文件经编译成字节码程序,通过jvm将每条指令翻译成不同的机器码

    ,通过特定平台运行。

    4fe980c4b6ad0277e6bda2432d716b7a.png

    4. JVM执行程序的过程

    1) 加载.class文件

    2) 管理并分配内存

    3) 执行垃圾收集

    JRE(java运行时环境)由JVM构造的java程序的运行环,也是Java程序运行的环境,但是他同时一个操作系统的一个应用程序一个进程,

    因此他也有他自己的运行的生命周期,也有自己的代码和数据空间。

    JVM在整个jdk中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,

    提供一个完整的Java运行环境,因此也就虚拟计算机。

    操作系统装入JVM是通过jdk中Java.exe来完成,

    通过下面4步来完成JVM环境:

    1) 创建JVM装载环境和配置

    2) 装载JVM.dll

    3) 初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例

    4) 调用JNIEnv实例装载并处理class类。

    5. JVM的生命周期

    1) JVM实例对应了一个独立运行的java程序它是进程级别

    a) 启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void

    main(String[] args)函数的class都可以作为JVM实例运行的起点

    b) 运行。main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以表明自己创建的线程是守护线程

    c) 消亡。当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出

    2) JVM执行引擎实例则对应了属于用户运行程序的线程它是线程级别的

    6、JVM内存模型

    (1)java代码具体执行过程如下图,

    8dd3d27afa4d6591d64d270ff09e9240.png

    (2)运行时数据区,即jvm内存结构图如下图

    3c62f8d0b3ed1ece0988503b2d31057e.png

    (3)运行时数据区存储了哪些数据?

    a) 程序计数器(PC寄存器)

    由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,

    因此,为了能够使得每个线程都在线程切换后能够恢复在切 换 之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,

    否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,

    因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。

    b) java栈

    Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、

    指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、

    方法返回地址(Return Address)和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。

    cd9cdc470764001342c5e2b98e212a24.png

    c)本地方法栈

    本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的

    d)堆

    Java中的堆是用来存储对象本身的以及数组(数组引用是存放在Java栈中的)。堆是被所有线程共享的,在JVM中只有一个堆。

    e)方法区

    与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。

    在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。

    在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,

    对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。

    7、JVM内存溢出的情况

    43fe416a304036e8f52ce4a89ba553cc.png

    a) 程序计数器(Program Counter Register)

    每条线程都有一个独立的的程序计数器,各线程间的计数器互不影响,因此该区域是线程私有的。该内存区域是唯一一个在Java虚拟机规范中没有规定任何OOM(内存溢出:OutOfMemoryError)情况的区域。

    b)Java虚拟机栈(Java Virtual Machine Stacks)

    在Java虚拟机规范中,对这个区域规定了两种异常情况:

    1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。

    2、如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

    这两种情况存在着一些互相重叠的地方:当栈空间无法继续分配时,到底是内存太小,还是已使用的栈空间太大,其本质上只是对同一件事情的两种描述而已。

    在单线程的操作中,无论是由于栈帧太大,还是虚拟机栈空间太小,当栈空间无法分配时,虚拟机抛出的都是StackOverflowError异常,而不会得到OutOfMemoryError异常。

    而在多线程环境下,则会抛出OutOfMemoryError异常。

    c)堆Java Heap

    Java Heap是Java虚拟机所管理的内存中最大的一块,它是所有线程共享的一块内存区域。几乎所有的对象实例和数组都在这类分配内存。Java Heap是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”。

    根据Java虚拟机规范的规定,Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存可分配时,并且堆也无法扩展时,将会抛出OutOfMemoryError异常。

    d)方法区域,又被称为“永久代”,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

    展开全文
  • 1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如...

    1、堆大小设置。

    2、回收器选择。

    1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。

    2、对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注Full GC,因为它会对整个堆进行整理,导致Full GC一般由于以下几种情况:

    一、导致Full GC一般由于以下几种情况:

    1、旧生代空间不足

    调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象

    2、Pemanet Generation空间不足

    增大Perm Gen空间,避免太多静态对象

    统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间

    控制好新生代和旧生代的比例

    3、System.gc()被显示调用

    垃圾回收不要手动触发,尽量依靠JVM自身的机制

    二、主要的调优手段

    调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现,内存比例不良设置会导致一下不良后果:

    1). 新生代设置过小

    一是新生代GC次数非常频繁,增大系统消耗;二是导致大对象直接进入旧生代,占据了旧生代剩余空间,诱发Full GC

    2). 新生代设置过大

    一是新生代设置过大会导致旧生代过小(堆总量一定),从而诱发Full GC;二是新生代GC耗时大幅度增加

    一般说来新生代占整个堆1/3比较合适

    3). Survivor设置过小

    导致对象从eden直接到达旧生代,降低了在新生代的存活时间

    4). Survivor设置过大

    导致eden过小,增加了GC频率。

    另外,通过-XX:MaxTenuringThreshold=n来控制新生代存活时间,尽量让对象在新生代被回收

    三、新生代和旧生代GC策略和组合搭配:

    由内存管理和垃圾回收可知新生代和旧生代都有多种GC策略和组合搭配,选择这些策略对于我们这些开发人员是个难题,JVM提供两种较为简单的GC策略的设置方式。

    1). 吞吐量优先

    JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,来达到吞吐量指标。这个值可由-XX:GCTimeRatio=n来设置

    2). 暂停时间优先

    JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。这个值可由-XX:MaxGCPauseRatio=n来设置

    四、几种常见的JVM配置原则

    1、堆设置

    -Xms:初始堆大小

    -Xmx:最大堆大小

    -XX:NewSize=n:设置年轻代大小

    -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

    -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2一个Survivor区占整个年轻代的1/5

    -XX:MaxPermSize=n:设置持久代大小

    2、收集器设置

    -XX:+UseSerialGC:设置串行收集器

    -XX:+UseParallelGC:设置并行收集器

    -XX:+UseParalledlOldGC:设置并行年老代收集器

    -XX:+UseConcMarkSweepGC:设置并发收集器

    3、垃圾回收统计信息

    -XX:+PrintGC

    -XX:+PrintGCDetails

    -XX:+PrintGCTimeStamps

    -Xloggc:filename

    4、并行收集器设置

    -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。

    -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

    -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

    5、并发收集器设置

    -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。

    -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

    https://blog.csdn.net/LeegooWang/article/details/88696195

    展开全文
  • JVM内存回收机制

    2021-03-15 12:50:29
    JVM内存回收机制标签: JVM GC 垃圾回收 内存管理0.说明当JVM创建对象遇到内存不足的时候,JVM会自动触发垃圾回收garbage collecting(简称GC)操作,将不再使用但仍存在JVM内存中的对象当做垃圾一样直接清理掉,释放...

    JVM内存回收机制

    标签: JVM GC 垃圾回收 内存管理

    0.说明

    当JVM创建对象遇到内存不足的时候,JVM会自动触发垃圾回收garbage collecting(简称GC)操作,将不再使用但仍存在JVM内存中的对象当做垃圾一样直接清理掉,释放被占用的内存空间,供新创建的对象使用。

    那么问题来了,要让系统能够自动实现不被引用对象的回收,有几个问题需要解决:

    Who:哪些是不再使用要被当做“垃圾”回收处理的对象?也就是要确定垃圾对象。

    Where:在哪里执行垃圾回收?明确要清理的内存区域。

    When:什么时候执行GC操作?即JVM触发GC的时机。

    How:怎么样进行垃圾对象处理?即GC的实现算法。

    关于第二点,对于oracle Hotspot VM的 GC操作主要回收的内存区域是JVM中的堆(分为年轻代和年老代,年轻代又分Eden和两个survivor区域),JVM内存结构不是本文的重点,我们在看本文前要对JVM内存结构有一定的了解,这里不作详细分析。

    1.判断对象是否可以被回收(判别算法或搜索算法)

    下面的算法回答了who的问题。

    1.1 引用计数法

    每个对象创建的时候,会分配一个引用计数器,当这个对象被引用的时候计数器就加1,当不被引用或者引用失效的时候计数器就会减1。任何时候,对象的引用计数器值为0就说明这个对象不被使用了,就认为是“垃圾”,可以被GC处理掉。

    评价:

    【优点】算法实现简单。

    【缺点】不能解决对象之间循环引用的问题。有垃圾对象不能被正确识别,这对垃圾回收来说是很致命的,所以GC并没有使用这种搜索算法。

    1.2 根搜索算法

    以一些特定的对象作为基础原始对象,或者称作“根”,不断往下搜索,到达某一个对象的路径称为引用链。

    如果一个对象和根对象之间有引用链,即根对象到这个对象是可到达的,则这个对象是活着的,不是垃圾,还不能回收。例如,假设有根对象O,O引用了A对象,同时A对象引用了B对象,B对象又引用了C对象,那么对象C和根对象O之间的路径的可达的,C对象就不能当做垃圾对象。引用链为O->A->B->C。

    反之,如果一个对象和根对象之间没有引用链,根对象到这个对象的路径是不可达的,那么这个对象就是可回收的垃圾对象。

    评价:

    【优点】可找到所以得垃圾对象,并且完美解决对象之间循环引用的问题。

    【缺点】不可避免地要遍历全局所有对象,导致搜索效率不高。

    根搜索算法是现在GC使用的搜索算法。

    可以当做GC roots的对象有以下几种:

    虚拟机栈中的引用的对象。(java栈的栈帧本地变量表)

    方法区中的类静态属性引用的对象。

    方法区中的常量引用的对象。(声明为final的常量对象)

    本地方法栈中JNI的引用的对象。(本地方法栈的栈帧本地变量表)

    下面是从网上找来的图,将就看看:GC ROOTS就是跟对象节点,蓝色的是可达的引用链,引用链上的对象是活着的,不能被当做垃圾对象回收。相反暗灰色的路径表示不可达的路径,这些对象将会被回收。每个圈圈里面的数字,表示其被引用的次数,没错,就是上面说到的引用计数法的计数值。

    ref_count.png

    2.GC算法

    这里讨论的是oracle的Hotspot VM常见的垃圾回收算法。使用的搜索算法都是基于根搜索算法实现的。

    2.1 标记-清除算法(Mark-Sweep)

    该算法分两步执行:

    1) 标记Mark:从GC ROOTS开始,遍历堆内存区域的所有根对象,对在引用链上的对象都进行标记。这样下来,如果是存活的对象就会被做了标记,反之如果是垃圾对象,则没做有标记。GC很容易根据有没有被做标记就完成了垃圾对象回收。

    2) 清除Sweep:遍历堆中的所有的对象(标记阶段遍历的是所有根节点),找到未被标记的对象,直接回收所占的内存,释放空间。

    评价:

    【优点】没有产生额外的内存空间消耗,内存利用率高。

    【缺点】效率低,清除阶段要遍历所有的对象;回收的垃圾对象是在各个角落的,直接回收垃圾对象,导致存在不连续的内存空间,产生内存碎片。

    标记-清除算法操作的对象是【垃圾对象】,对于活着的对象(被标记的对象),它则直接不理睬。

    2.2 复制算法(Copying)

    复制算法把内存区间一分为二,有对象存在的一半区间称为“活动区间”,没有对象存在处于空闲状态的空间则为“空闲区间”。

    当内存空间不足时触发GC,先采用根搜索算法标记对象,然后把活着的对象全部复制到另一半空闲区间上,复制算法的“复制”就来自这一操作。复制到另一半区间的时候,严格按照内存地址依次排列要存放的对象,然后一次性回收垃圾对象。

    这样原来的空闲区间在GC后就变成活动区间,而且内存顺序齐整美观。原来的活动区间在GC后就变成了完全空的空闲区间,等待下一次GC把活的对象被copy进来。

    评价:

    【优点】GC后的内存齐整,不产生内存碎片。

    【缺点】GC要使用两倍的内存,或者说导致堆只能使用被分配到的内存的一半,这个算法对空间要求太高!如果存活的对象较多,则意味着要复制很多对象并且要维护大量对象的内存地址,所以存活的对象数量不能太多,否则效率也会很低。

    复制算法复制移动的对象是【活着的对象】,对于垃圾对象(不被标记的对象)则直接回收。

    2.3 标记-整理算法(Mark-Compact)

    这个算法则是对上面两个算法的综合结果。也分为两个阶段:

    1)标记:这个阶段和标记-清除Mark-Sweep算法一样,遍历GC ROOTS并标记存活的对象。

    2)整理:移动所有活着的对象到内存区域的一侧(具体在哪一侧则由GC实现),严格按照内存地址次序依次排列活着的对象,然后将最后一个活着的对象地址以后的空间全部回收。

    评价:

    【优点】内存空间利用率高,消除了复制算法内存减半的情况;GC后不会产生内存碎片。

    【缺点】需要遍历标记活着的对象,效率较低;复制移动对象后,还要维护这些活着对象的引用地址列表。

    2.4 分代回收算法(Generational Collecting)

    分代回收算法就是现在JVM使用的GC回收算法。

    2.4.1简要说明

    1)先来看看简单化后的堆的内存结构:

    Java堆 = 年老代 + 年轻代

    (空间大小比例一般是3:1)

    年轻代 = Eden区 + From Space区 + To Space区

    (空间大小比例一般是8:1:1)

    2)按照对象存活时间长短,我们可以把对象简单分为三类:

    短命对象:存活时间较短的对象,如中间变量对象、临时对象、循环体创建的对象等。这也是产生最多数量的对象,GC回收的关注重点。

    长命对象:存活时间较长的对象,如单例模式产生的单例对象、数据库连接对象、缓存对象等。

    长生对象:一旦创建则一直存活,几乎不死的对象。

    3)对象分配区域

    短命对象存在于年轻代,长命对象存在于年老代,而长生对象则存在于方法区中。

    由于GC的主要内存区域是堆,所以GC的对象主要就是短命对象和长命对象这类寿命“有限”的对象。

    2.4.2 分代回收的GC类型

    针对HotSpot VM的的GC其实准确分类只有两大种:

    1)Partial GC:部分回收模式

    Young GC:只收集young gen的GC。和Minor GC一样。

    Old GC:只收集old gen的GC。只有CMS的concurrent - collection是这个模式

    Mixed GC:收集整个young gen以及部分old gen的GC。只有G1有这个模式

    2)Full GC:收集整个堆,包括young gen、old gen,还有永久代perm gen(如果存在的话)等所有部分的模式。同Major GC。

    3)触发时机

    HotSpot VM的串行GC的触发条件是:

    young GC:当young gen中的eden区分配满的时候触发。

    full GC:当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC;或者,如果有perm gen的话,要在perm gen分配空间但已经没有足够空间时,也要触发一次full GC;或者System.gc()、heap dump带GC,默认也是触发full GC。

    并发GC的触发条件就不太一样。以CMS GC为例,它主要是定时去检查old gen的使用量,当使用量超过了触发比例就会启动一次CMS GC,对old gen做并发收集。

    2.4.3 年轻代GC过程

    当需要在堆中创建一个新的对象,而年轻代内存不足时触发一次GC,在年轻代触发的GC称为普通GC,Minor GC。注意到年轻代中的对象都是存活时间较短的对象,所以适合使用复制算法。这里肯定不会使用两倍的内存来实现复制算法了,牛人们是这样解决的,把年轻代内存组成是80%的Eden、10%的From Space和10%的To Space,然后在这些内存区域直接进行复制。

    刚开始创建的对象是在Eden中,此时Eden中有对象,而两个survivor区没有对象,都是空闲区间。第一次Minor GC后,存活的对象被放到其中一个survivor,Eden中的内存空间直接被回收。在下一次GC到来时,Eden和一个survivor中又创建满了对象,这个时候GC清除的就是Eden和这个放满对象的survivor组成的大区域(占90%),Minor GC使用复制算法把活的对象复制到另一个空闲的survivor区间,然后直接回收之前90%的内存。周而复始。始终会有一个10%空闲的survivor区间,作为下一次Minor GC存放对象的准备空间。

    要完成上面的算法,每次Minor GC过程都要满足:

    存活的对象大小都不能超过survivor那10%的内存空间,不然就没有空间复制剩下的对象了。但是,万一超过了呢?前面我们提到过年老代,对,就是把这些大对象放到年老代。

    2.4.4 年老代GC

    什么样的对象可以进入年老代呢?如下:

    在年轻代中,如果一个对象的年龄(GC一次后还存活的对象年岁加1)达到一个阈值(可以配置),就会被移动到年老代。

    Survivor中相同年龄的对象大小总和超过survivor空间的一半,则不小于这个年龄的对象都会直接进入年老代。

    创建的对象的大小超过设定阈值,这个对象会被直接存进年老代。

    年轻代中大于survivor空间的对象,Minor GC时会被移进年老代。

    年老代中的对象特点就是存活时间较长,而且没有备用的空闲空间,所以显然不适合使用复制算法了,这个时候使用标记-清除算法或者标记-整理算法来实现GC。负责年老代中GC操作的是全局GC,Major GC,Full GC。

    什么时候触发Major GC呢?

    在Minor GC时,先检测JVM的统计数据,查看历史上进入老年代的对象平均大小是否大于目前年老代中的剩余空间,如果大于则触发Full GC。

    3.GC执行机制

    3.1串行GC

    在搜索扫描和复制过程都是采用单线程实现,适用于单CPU、新生代空间较小或者要求GC暂停时间要求不高的地方。是client级别的默认方式。

    3.2并行GC

    在搜索扫描和复制过程都是采用多线程实现,适用于多CPU、或者要求GC暂停时间要求高的地方。是server级别的默认方式。

    3.3同步GC

    同时允许多个GC任务,减少GC暂停时间。主要应用在实时性要求重于总体吞吐量要求的中大型应用,即使如此,降低中断时间的技术还是会导致应用程序性能的降低。

    4.内存调优

    JVM内存调优,主要是减少GC的频率和减少Full GC的次数,Full GC的时候会极大地影响系统的性能。所以在此基础上,更加要关注会导致Full GC的情况。

    4.1 容易导致Full GC的情况

    年老代空间不足

    1)分配足够大空间给old gen。

    2)避免直接创建过大对象或者数组,否则会绕过年轻代直接进入年老代。

    3)应该使对象尽量在年轻代就被回收,或待得时间尽量久,避免过早的把对象移进年老代。

    方法区的永久代空间不足

    1)分配足够大空间给。

    2)避免创建过多的静态对象。

    被显示调用System.gc()

    通常情况下不要显示地触发GC,让JVM根据自己的机制实现。

    4.2 JVM堆内存分配问题讨论

    4.2.1 年轻代过小(年老代过大)

    导致频繁发生GC,增大系统消耗

    容易让普通大文件直接进入年老代,从而更容易诱发Full GC。

    4.2.2 年轻代过大(年老大过小)

    导致年老代过小,从而更容易诱发Full GC。

    GC耗时增加,降低GC的效率。

    4.2.3 Eden过大(survivor过小)

    Minor GC时容易让普通大文件直接绕过survivor进入年老代,从而更容易诱发Full GC。

    4.2.4Eden过小(survivor过大)

    导致GC频率升高,影响系统性能。

    4.3 调优策略

    保证系统吞吐量优先

    减少GC暂停时间优先

    5 JVM常见配置选项

    总结一下常见配置。

    堆设置

    -Xms:初始堆大小

    -Xmx:最大堆大小

    -XX:NewSize=n:设置年轻代大小

    -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

    -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5

    -XX:MaxPermSize=n:设置持久代大小

    收集器设置

    -XX:+UseSerialGC:设置串行收集器

    -XX:+UseParallelGC:设置并行收集器

    -XX:+UseParalledlOldGC:设置并行年老代收集器

    -XX:+UseConcMarkSweepGC:设置并发收集器

    垃圾回收统计信息

    -XX:+PrintGC

    -XX:+PrintGCDetails

    -XX:+PrintGCTimeStamps

    -Xloggc:filename

    并行收集器设置

    -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。

    -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

    -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

    并发收集器设置

    -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。

    -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

    展开全文
  • 我们在谈及JVM内存的堆、虚拟机栈和本地方法栈、程序计数器和方法区等名词的时候,有没有想过一个问题。JVM是一个进程,那么天真的以为就该和操作系统进程内存模型结构保持一致,比如C/C++程序就是和操作系统的进程...

    我们在谈及JVM内存的堆、虚拟机栈和本地方法栈、程序计数器和方法区等名词的时候,有没有想过一个问题。JVM是一个进程,那么天真的以为就该和操作系统进程内存模型结构保持一致,比如C/C++程序就是和操作系统的进程的内存模型保持一致。但是JVM内存管理中内存的划分明显和操作系统的进程内存模型有很大出入,那么他们之间的关系究竟是怎样的呢? 这一篇专题来解读这个问题。

    一 操作系统进程的内存模型

    1.1 进程的地址空间

    进程的地址空间,也叫作虚拟地址空间或者逻辑地址空间,指的是这个进程中所有虚拟地址的集合,此时还没有加载到物理内存中。地址空间大小取决于操作系统的位数,即CPU处理数据的能力,如果是32位操作系统,那么地址空间的寻址范围就是0 ~ 2^32-1。

    物理内存资源是有限的,不可能将虚拟地址空间全部放到物理内存中。如果需要将虚拟地址空间的虚拟地址加载到物理内存,需要使用页表,然后按需调页。虚拟地址空间和物理内存将内存划分为大小相等的块,一般而言,一个内存块就叫做一个页,默认4096字节大小。 所谓的页表其实就是进程维护的一个记录虚拟页和物页映射的一张表。程序在运行过程中,首先根据页表判断这个地址所在的虚拟页是否有物理页,如果有直接读取对应的地址上的数据;如果没有则需要找一块页来存储虚拟页,然后构建映射关系。

    1.2 进程的内存模型

    1.2.1 用户地址空间和内核地址空间
    对于计算机的内存,内存除了要存储进程需要的数据,还需要存储操作系统内核的数据,所以内存分为两个部分,用户空间和内核空间。对于32位操作系统4G内存来讲,用户空间占用0x000000000 ~ 0xC00000000 3GB内存,内核空间占用0xC00000000 ~ 0xFFFFFFFF 1GB内存。其中用户空间处于低位,内核空间处于高位。

    1.2.2 内存模型

    用户空间内存主要分为代码段(.text)、数据段(.data)、bss段(.bss)、内存映射区、堆、栈,如图所示:
    在这里插入图片描述

    1.2.2.1 代码段

    代码段: 这块区域主要存放的是当前进程的源代码编译后的机器指令或者一些常量,这部分区域在程序执行之前就已经确定,并且这块区域是只读的,防止被修改。

    1.2.2.2 数据段

    数据段:这块区域主要是用来存放已经初始化的全局变量和静态变量,其中静态变量不区分是在函数内还是外,只要初始化了,都会放在数据段

    1.2.2.3 BSS段

    BSS段:这块区域主要是用来存放未初始化的全局变量和静态变量,其中静态变量不区分是在函数内还是外,只要没有初始化,都会放在BSS段

    1.2.2.4 内存映射区域

    当进行内存映射的时候,会在虚拟地址空间寻找一段连续的空间和文件进行映射,这一段连续的空间就是内存映射区域

    1.2.2.5 栈

    栈: 这块区域存放的是函数调用过程中的参数和局部变量和函数的返回值,由操作系统管理,进行自动负分配和回收。每一个函数执行的啥时候会在栈上占据一定的空间,这部分空间叫做栈帧,这个栈帧中存储的就是形参、本地变量和返回值等。注意,不管本地变量是不是初始化,都是需要放在栈上的。

    1.2.2.6 堆

    堆: 这块区域主要是用来存放通过调用malloc或者new动态分配的内存,存储的都是一些生命周期不限于某个函数的变量等。这块区域一般由程序员分配和释放。如果没有释放,就属于内存泄露,那么就只有程序退出的时候才会释放,比如内存不够用操作系统可能会kill掉一些吃内存比较多的进程。

    1.3 C语言内存分配的例子

    int a = 0; // 初始化的全局变量, 保存在数据段
    char *t1; //未初始化的全局变量,保存在BSS段
    long c; //未初始化的全局变量,保存在BSS段
    static int d = 10; // 初始化的静态变量, 保存在数据段
    static int m; // 未初始化的静态变量, 保存在BSS段
    int main()
    {
        int b; //未初始化的局部变量, 保存在栈上
        char elements[] = "123456"; // "123456"为字符串常量保存在代码段,数组保存在栈上,并将代码区的常量"123456"复制到该数组中。
        char *t2; //t2保存在栈上
        char *t3 = "aaa";//t3保存在栈上,"aaa"是常量保存在代码区
        static int c = 0;//初始化的静态局部变量,保存在数据段
    
        t1 = (char *)malloc(sizeof(char) * 16);// 在堆上分配16字节
        t2 = (char *)malloc(sizeof(char) * 20);// 在堆上分配20字节
    
        return 0;
    }
    

    二 JVM是什么? JVM是由什么组成的?

    2.1 什么是JVM?

    Java虚拟机是一个通过模拟计算机的功能,从而实现用于解释和运行Java程序的C++程序。所以本质上JVM就是一个程序。
    我们知道Java是一款跨平台的编程语言,那么它是如何实现跨平台的呢?就是通过JVM来实现的。一般的高级语言在不同的平台下运行,需要编译成不同的目标代码,但是Java语言通过编译,生成可以被JVM解释执行的字节码文件,就可以在多个平台上直接运行。实现了一次编译,到处运行。

    2.2 JVM主要由哪几部分组成

    JVM主要包括执行引擎、类加载器、以及运行时数据区三大部分。

    2.2.1 类加载器(ClassLoader)

    类加载器主要负责将编译后的字节码文件加载到JVM中的内存里,然后会被执行引擎解释执行。类加载器主要包括三种类型:
    第一:Bootstrap ClassLoader
    主要负责加载核心类库,%JAVA_HOME%\lib\jre下面除了ext包之外的所有的库
    第二:Extension ClassLoader
    它主要负责加载核心库的扩展包,%JAVA_HOME%\lib\jre\ext下的类库
    第三:Application ClassLoader
    主要加载用户类路径(classpath)下指定的类库

    2.2.2 执行引擎(Execution Engine)

    执行引擎主要包括解释字节码、即时编译器(JIT compiler)。

    2.2.2.1 Java解释器

    类加载器会将字节码文件(class文件)加载到JVM内存中,但是class文件此时还是不能执行的,因为机器不认识字节码,只认识机器码,所以Java解释器就是将字节码文件一行一行的翻译,将字节码文件中指令转换成机器指令,这样机器才可以执行。高级语言一般都会有解释器,比如python也有。

    2.2.2.2 JIT编译器

    Java解释器一行一行的解释字节码,速度不快,JIT编译器会把多次调用的方法和多次执行的循环体的字节码编译成本地节机器码并运行。

    2.2.3 运行时数据区

    JVM运行时数据区域主要包括线程共享区和线程独占区。线程共享主要包括方法区、堆;线程独占去主要包括虚拟机栈、本地方法栈和程序计数器。
    在这里插入图片描述

    三JVM和操作系统中进程的内存模型有啥关系? JVM是如何划分内存的?

    3.1 JVM和操作系统中进程的内存模型有啥关系

    我们知道,JVM对于操作系统来说,就是一个程序,JVM本身也是基于C++来写的,所以JVM本身的内存结构就是C/C++的内存模型,如图所示:

    在这里插入图片描述

    但是Java程序中要使用的堆栈却和这个不同,因为Java程序栈和堆都是在JVM堆上分配的。
    第一:为什么不Java的堆栈不在JVM的栈上分配呢?
    因为JVM的栈是受操作系统自动管理的,随时可能被回收,那么有些对象生命周期还没有结束就被回收了,肯定不行的。

    第二:为什么不直接使用JVM堆呢?非要在JVM堆的基础上重新实现堆呢?
    因为C++的堆内存是程序员分配和释放的,不是操作系统自动管理,如果要实现JVM的GC的功能,那么直接使用JVM原生堆是不可能实现GC的

    3.2 JVM是如何划分内存的

    我们知道JVM是虚拟的计算机,那么它在内存布局是模仿了操作系统的一些功能。
    第一:操作系统进程的内存模型是有堆和栈的,那么JVM中也是有的
    第二:操作系统执行进程需要从磁盘加载目标文件到内存,那么JVM也需要一个地方存放加载的字节码信息等,所有JVM中有了方法区
    第三:操作系统进程控制块有程序计数器,用于保存下一条需要执行的指令的地址,那么JVM也是有的,用于保存下一条需要执行的JVM指令的地址
    如图所示:
    在这里插入图片描述

    3.2.1 程序计数器

    程序计数器,和进程控制块的程序计数器功能一样,都是用于指示下一条需要执行的指令的地址,每一个线程对应一个程序计数器,属于线程私有。如果当前线程正在执行一个Java方法,则程序计数器记录正在执行的字节码地址;如果当前线程正在执行本地方法,则寄存器为空。因为这不属于JVM的范畴。

    3.2.2 虚拟机栈

    虚拟机栈类似于进程的内存模型的栈,用于存放方法调用过程中的参数、局部变量和返回值,每一个方法对应一个栈帧,方法结束栈帧销毁

    3.2.3 本地方法栈

    在Java程序中,有时候可能需要调用底层非Java的本地代码,所以需要一个执行本地方法的栈来存放调用过程中的参数、局部变量和返回值等信息

    3.2.4 堆

    堆也是被所有线程共享的一块内存区域,在虚拟机启动的时候创建,主要存放对象实例。JDK1.7字符串常量池从方法区移到了堆中,JDK1.8将静态变量和静态方法移到了堆中。

    3.2.5 方法区

    方法区主要存储加载的字节码元数据信息,JDK1.8之后使用元数据空间代替了方法区,不再使用JVM内存,而是使用直接内存。方法区中字符串常量池移到了堆中;类变量(静态变量)也移到了堆中;符号引用移到了直接内存当中。

    展开全文
  • 问题背景前段时间,某客户的大作业(并行度 200 左右)遇到了 TaskManager JVM 内存超限(实际内存用量 4.1G > 容器设定的最大阈值 4.0G),被 YARN 的...
  • Java设置JVM内存

    2021-02-12 21:26:18
    本文向大家简单介绍一下进行JVM(JVM相关知识)内存设置几种方法,安装Java开发软件时,默认安装包含两个文件夹,一个JDK...这里将为大家介绍设置JVM内存分配的几招。【-Xmx】Java Heap最大值,默认值为物理内存的1/4,...
  • from ...Tomcat的内存溢出本质就是JVM内存溢出,所以在本文开始时,应该先对Java JVM有关内存方面的知识进行详细介绍。一、Java JVM内存介绍JVM管理两种类型的内存,堆和非堆。按照官方...
  • 一、设置JVM内存设置1. 设置JVM内存的参数有四个:-Xmx Java Heap***值,默认值为物理内存的1/4,***设值应该视物理内存大小及计算机内其他内存开销而定;-Xms Java Heap初始值,Server端JVM***将-Xms和-Xmx设为相同...
  • 其实在生产环境中的很多问题都是由 JVM 引发的故障问题,比如 OutOfMemoryError(OOM) 内存溢出问题,虚拟机参数不合理导致频繁的垃圾回收影响系统性能等,开发环境中tomcat容器无法因JVM参数配置不当导致的无法正常...
  • 根据上面两个图可以看出,JVM内存模型是模仿操作系统内存模型构建的,JVM内存模型和操作系统内存模型是可以一一对应起来的,整个JVM内存模型存储在操作系统的堆中。JVM就是个操作系统,而JVM的方法区,也就相当于...
  • JVM内存溢出及配置

    2021-02-26 17:30:20
    一、Java JVM内存介绍JVM管理两种类型的内存,堆和非堆。按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外...
  • JVM内存调优

    2021-05-12 01:35:56
    JVM的常见配置汇总堆设置:-Xms:初始堆大小 一般设置为小于4G-Xmx:最大堆大小 一般设置为小于4G一般-Xms与Xmx的值相等,避免每次垃圾回收完成后JVM重新分配内存-XX:NewSize=n 设置年轻代大小-XX:NewRatio=n 设置...
  • JVM 算是面试中高频问题了,通常情况下总会有人问到:请你讲解下 JVM内存模型,JVM 的性能调优做过吗? 为什么 JVM 在 Java 中如此重 要? 首先你应该指导,运行一个 Java 应用程序,我们必须要先安装 JDK 或者 ...
  • JDK1.8 JVM内存模型

    2021-04-13 01:00:08
    一、JDK1.8 JVM内存模型概览 这里介绍的是JDK1.8 JVM内存模型。1.8同1.7比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于...
  • jvm内存调优经验总结

    2021-02-27 22:37:53
    JVM 调优,是个很简单也很复杂的话题,由于经常遇到这类问题,在这里总结一下。先从解决bug开始,当Java程序申请内存,超出VM可分配内纯的时候,VM首先可能会GC,如果GC完还是不够,或者申请的直接超够VM...
  • JVM内存大小应该如何设置
  • 如何设置jvm内存

    2021-02-12 14:51:39
    2. 如何分配JVM内存设置:(1)当在命令提示符下启动并使用JVM时(只对当前运行的类Test生效): java -Xmx128m -Xms64m -Xmn32m -Xss16m Test (2)当在集成开发环境下(如eclipse)启动并使用JVM时:a. 在eclipse根目录下打开...
  • Tomcat的内存溢出本质就是JVM内存溢出,所以在本文开始时,应该先对Java JVM有关内存方面的知识进行详细介绍。一、Java JVM内存介绍JVM管理两种类型的内存,堆和非堆。按照官方的说法:“Java 虚拟机具有一个堆,堆...
  • JAVA8 JVM内存模型

    2021-01-15 14:24:55
    JAVA8 JVM内存模型 .png&originHeight=829&originWidth=729&size=89375&status=done&style=none&width=729) 内存分配剖析 public class MathTest { public static void main(String[] ...
  • Tomcat 设置JVM内存大小

    2021-02-28 06:23:50
    $var _must_ be set to either true or false.JAVA_OPTS="-Xms1024m -Xmx4096m -Xss1024K -XX:PermSize=512m -XX:MaxPermSize=2048m"正文:常见的内存溢出有以下两种:java.lang.OutOfMemoryError:...
  • 高并发 总目录 】 JVM性能优化面试题 JVM内存区域常见问题 Java 中会存在内存泄漏吗,简述一下? Java 内存分配? Java 堆的结构是什么样子的? 什么是堆中的永久代(Perm Gen space)? 简述各个版本内存区域的变化...
  • JVM 内存分析

    2020-12-22 22:27:37
    一、JVM 与操作系统的关系 1、JVM 把 .class 文件翻译成操作系统能识别的 2 进制数据进行执行。 2、JVM跨平台性:假设我们写一个...1、运行时数据区:Java虚拟机在执行程序的过程中,会把它所管理的内存分为如干个不同
  • 业界有很多强大的java profile的工具,比如Jporfiler,yourkit,这些收费的东西我就不想说了,想说的是,其实java自己就提供了很多内存监控的小工具,下面列举的工具只是一小部分,仔细研究下jdk的工具,还是蛮...
  • JVM内存不足崩溃

    2021-07-17 00:28:32
    But we are seeing the above JVM crashes even when the Runtime.freeMemory is reporting lots of free memory. Is there any way as a java desktop application we can avoid this happening and insulate ...
  • 前言 CPU 是时分的,操作系统里面有很多线程,每个线程...内存溢出: 程序分配的内存超过物理机的内存大小,导致无法继续分配内存,出现OOM报错 内存泄露: 不再使用的对象一直占据着内存不释放,导致这块内存浪费掉,
  • 本文和大家重点讨论一下如何设置Tomcat的JVM内存大小,JAVA程序启动时JVM都会分配一个初始内存和***内存给这个应用程序。这个初始内存和***内存在一定程度都会影响程序的性能。如何设置Tomcat的JVM内存大小Tomcat...
  • 什么是JVM内存模型?

    2021-03-10 06:26:59
    本篇文章带大家初步了解一下JVM内存模型,有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。计算机内存模型在程序运行时,CPU通过访问主存获取数据,但随着CPU的快速发展,CPU访问速度越来越高,...
  • java查看jvm内存使用情况(2012-03-22 15:50:54)标签:jvm内存虚拟机分配itjava.lang.Runtime类提供了查看当前JVM内存的使用情况。每个java实例(即虚拟机实例)只有一个Runtime的实例,通过Runtime.getRuntime()可以...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 546,079
精华内容 218,431
关键字:

jvm内存