JVM概览

 

内存溢出:是指没有足够的空间
内存泄漏:使用完内存空间但是没有释放,导致这一块空间一直占用,如果发生多次泄露会造成内存不足
OutOfMemoryErro-oom
jvm内存划分:
1.堆:
    --GC最频繁的区域
    --线程共享
    --虚拟机启动时创建
    --主要存放对象实例和数组
    --所有new方式产生的对象都在这存放,反射方式产生的应该也是这里
    ----遗留问题:java中的数组
2.栈:可以理解为每个线程产生的时候产生一个栈。栈主要存放栈帧,也就是记录和实现程序具体的操作的过程。
    --每个线程独有,随着线程产生及消失
    --使用操作系统内存-个人觉得是因为线程的实现本质是底层操作系统的实现
    --每个方法执行时产生栈帧,栈中存放的就是这些栈帧
    --方法调用时栈帧入栈,调用结束出栈
    --一个线程当然可以存放多个栈帧,因为它会调用多个方法
    ----栈帧用于存储局部变量表、动态链接、操作数和方法出口等信息
    ------局部变量表存放局部变量,包括基本的数据类型和对象的引用,局部变量表内存空间在编译期间确定且在运行时不在改变

3.方法区:
    --方法区存放所有与类相关的信息,类中的字段及方法,构造函数,定义的静态变量,final定义的变量
    --所有线程共享
    --常量池是方法区的一部分,主要存储编译时定义的常量及引用。一般这里的数据都是编译时产生,string.intern()方法可以在运行时产生一个常量
    ----intern具体的实现需要判断在常量池中是否有相应的字符串,没有的话在常量池新建一个并返回地址,有的话直接返回已有的常量池的地址
    ----常量池中的常量只存在一份
    --方法区只有在大小超过定义的大小时会发生内存溢出,会触发GC
    --方法区触发内存溢出的条件很难达成,一般存放在里的数据不会发生变化,因此叫做持久代
    --JVM方法区的相关参数,最小值:--XX:PermSize;最大值 --XX:MaxPermSize
4.本地方法栈
    --线程私有
    --用于支持native方法的执行
    ----native方法是指其他语言编写的或者是操作系统本地的方法
    ----线程的start方法具体实现就是调用的native start方法,线程的实现就是交给操作系统去实现
5.程序计数器
    --线程私有
    --直接分配在cpu上的很小的一部分区域
    --记录当前线程正在执行的字节码的行号,其实记录本质也是地址,编译好的字节码是存在内存中,去执行的话就是取对应字节码行的地址去执行-和虚拟机相关的就是字节码,虚拟机只执行字节码
    --通过程序计数器获取将要执行的行数
    --执行java方法记录正在执行的字节码的行数,执行native方法,pc为空
    --这个区域是唯一不会抛出OutOfMemoryError


GC机制:
java对象访问的机制:
对于对象的引用是存放在栈中的局部变量表中,当然这个变量是存放的地址,存放的地址不同对于对象的访问方式也就不同了
--1.句柄访问:
----堆中会划分一块句柄池用来存放句柄,句柄中包含指向堆中实例的指针和指向方法区中对象类型数据,而栈中的引用是指向这两个指针的地址
--2.直接指针访问:
----这种方式栈中的引用直接指向堆中的实例的地址,在实例数据中包含指向方法区中的对象类型的指针,这种方式获取对象速度较快,hotspot使用的这种方式


对于堆中的对象需要找到已经不在使用的对象进行回收,常用的查找算法有:
--1.引用计数法:
----对与每一个对象都有一个相应的计数器,当被引用时计数器加1,没有引用时计数器减一,当查找到一段时间内计数器为0的对象时就认为他是可以回收的
----缺点就是当两个对象相互引用且都没有作用的时候,这两个对象不会被回收,这块内存区域会一直被占用
--2.根搜索算法
----这种是相当于定义一个根节点,然后所有的引用从根节点开始延申,在这个根节点树上说明对象还是在被使用,无法找到到根节点的路径就说明可以被回收了
----感觉应该是需要定义多个GC Roots这个后面再看下

 

可以作为根节的对象:

1.方法区中的静态属性及常量引用的对象

2.栈帧中的本地变量引用的对象

3.本地方法栈中引用的对象

什么样的类需要被回收:
-1.所有实例已经被回收
-2.类加载器已经被回收
-3.类的反射类java.lang.class 没有在任何地方被引用

四种引用类型:

强引用:new出来的对象都是强引用,GC无论如何都不会回收,即使抛出OOM异常。
软引用:只有当JVM内存不足时才会被回收。
弱引用:只要GC,就会立马回收,不管内存是否充足。
虚引用:可以忽略不计,JVM完全不会在乎虚引用,你可以理解为它是来凑数的,凑够”四大天王”。它唯一的作用就是做一些跟踪记录,辅助finalize函数的使用

内存分区:
--新生代:
----适合快速创建的对象及生命周期较短的对象
----GC活动频繁
----分为Edan和Surviour区,Surviour分为1区和2区。
----当edan区满之后会放入幸存者区
----此处的GC称为Minor GC(Young GC)
----幸存者两个区交替使用
----回收的具体机制:
------1.当edan触发GC,把eden存活的对象放到s0区,并清空eden
------2.当s0触发GC时,把eaen中和s0中存活的对象加载s1区,并清空eden和s0
------3.当s1触发GC时,把在s1中还存活的对象放到老年代

这里还会有一个担保机制,就是当s0或者s1中的内存已经不足已放下从eden中gc的对象,那这个对象就会直接进入老年代。
------触发GC的条件由具体的算法决定,并不是内存满了或者到达一定的值就会触发
--老年代:
----老年代存放的是从新生代还存活的对象
----当老年代满了需要对老年代进行回收,称为Major GC也称作Full GC
----回收更彻底
-----以上两种应该是在堆内存中
--持久代:持久代就是方法区

内存分配策略:
--对象优先在eden中分配,当eden没有足够的空间时就会触发minor GC
--大对象会直接进入老年代,大对象是指一些很长的字符串或者数组
----Minor GC使用复制回收算法,也就是在GC时会复制存活的对象,如果对象过大复制就会比较耗时因此,为了避免这种现象,将大对象直接放在老年代
--当在幸存者区的相同年龄的对象的总和大于或等于某个幸存者内存空间的一半,大于这个年龄的对象会直接放到老年代
--每进行一次回收,对象的年龄就增加1,默认为15时他就进入老年代,也就是长期存活的对象会进入老年代
--minor gc之后Survivor空间不足直接进入老年代

 

垃圾回收算法:
--1.标记清除法
----这个算法有两个过程:根据可达性分析法查找需要回收的对象,根据查找的对象进行回收
----缺点:
------查找和清除的效率不高
------会产生大量的内存碎片,当产生一个大对象无法找到合适的连续的内存存放的话会进行大量的GC
--2.复制算法
----这种算法是把内存划分为两块只在其中一块区域存放对象,当一块满之后把存活对象放到另一块,清除原有的区域
----缺点:内存的浪费,只使用了一块区域
--3.标记压缩法
----首先使用标记清楚法的方式标记存活的对象
----移动存活对象到另一块区域
----清除边界以外的区域
--4.分代回收法

垃圾回收器划分:从不同角度划分
--按线程数划分,可分为串行垃圾回收器和并行垃圾回收器
--按工作模式分,分为并行和独占式垃圾回收器
--按工作内存区域分,分为老年代和新生代垃圾回收器
--按碎片处理方式,可以分为压缩式和非压缩式垃圾回收器

CMS垃圾回收器:牺牲吞吐量来获取最短垃圾回收停顿时间的垃圾回收器,对于响应速度有要求的服务器使用这种垃圾回收器。内部使用基础算法是标记清除算法。适用于存活时间较长的对象及对性能有要求的服务器

 

G1收集器是jdk7之后默认的垃圾回收器,和其他垃圾回收期不同的是,其他垃圾回收器都是两个回收器分别在年轻代和老年代之间工作,而G1是一个全栈的回收器,回同时工作在老年代和年轻代

 


TPS:系统吞吐量
JVM调优:--参考文章 https://blog.csdn.net/u011683530/article/details/51013219
--一般情况下遇到性能问题一般是从代码,网络或者数据库等方面去查找,当以上都没问题时就需要从jvm层面去排查问题
一般对于性能的影响就是表现在TPS上,在jvm层导致性能问题的原因基本都是垃圾回收引起的,minor gc次数过多,full gc次数过多,full gc时间长

调优参数:
 -XX:+<option> '+'表示启用该选项
 -XX:-<option> '-'表示关闭该选项
 -XX:<option>=<number> 给选项设置一个数字类型值,可跟随单位,例如:'m'或'M'表示兆字节;'k'或'K'千字节;'g'或'G'千兆字节。32K与32768是相同大小的。
 -XX:<option>=<string> 给选项设置一个字符串类型值,通常用于指定一个文件、路径或一系列命令列表。例如:-XX:HeapDumpPath=./dump.core
-Xms12g:初始化堆内存大小为12GB。

-Xmx12g:堆内存最大值为12GB 。

-Xmn2400m:新生代大小为2400MB,包括 Eden区与2个Survivor区。

-XX:SurvivorRatio=1:Eden区与一个Survivor区比值为1:1。

-XX:MaxDirectMemorySize=1G:直接内存。报java.lang.OutOfMemoryError: Direct buffer memory 异常可以上调这个值。

-XX:+DisableExplicitGC:禁止运行期显式地调用 System.gc() 来触发fulll GC。

注意: Java RMI的定时GC触发机制可通过配置-Dsun.rmi.dgc.server.gcInterval=86400来控制触发的时间。

-XX:CMSInitiatingOccupancyFraction=60:老年代内存回收阈值,默认值为68。

-XX:ConcGCThreads=4:CMS垃圾回收器并行线程线,推荐值为CPU核心数。

-XX:ParallelGCThreads=8:新生代并行收集器的线程数。

-XX:MaxTenuringThreshold=10:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

-XX:CMSFullGCsBeforeCompaction=4:指定进行多少次fullGC之后,进行tenured区 内存空间压缩。

-XX:CMSMaxAbortablePrecleanTime=500:当abortable-preclean预清理阶段执行达到这个时间时就会结束。


 调优的情况:
 --1.老年代内存上涨到设置的最大内存值。一般情况下在未达到最大值时应该会发生full gc
 --2.full gc过于频繁。在老年代中的对象应该都是存活周期较长的对象,而且频繁gc占用资源较多
 --3.gc停留时间过长,例如超过1秒,
 --4.出现OOM
 --5.系统性能下降
 --6.使用本地缓存且占用较大内存空间

 调优原则:
 --1.尽量少在JVM上进行调整
 --2.出现JVM问题很可能时代码问题,而非参数问题
 --3.在应用启动时应该就设置好JVM参数
 --4.应该尽可能的少创建对象
 --5.少创建全局变量和大对象
 
 调优目标:
 --1.低GC频率
 --2.高吞吐量
 --3.低GC时间
 --4.少量内存占用


 类的加载过程:

 --类加载器作用时是在编译时把class文件加载到内存中,应用启动时通过类加载器把类加载到JVM中
 --虚拟机把class文件加载到内存,并进行数据检验,解析,初始化过程。这几个步骤是在程序运行期间进行,所以java相比其他语言编译较慢
 --加载过程完成三件事:
 ----1.通过类的全限定名称获取类的二进制字节流
 ----2.把类的静态数据结构也就是获取的字节流转换为方法区的可执行的数据结构,也就是加载类的所有信息到方法区中
 ----3.在堆中创建一个代表此类的java.lang.class对象,作为可以访问方法区类的入口
 --验证过程:
 ----验证其实就是对加载类的class文件的字节流进行合法性校验。

 --准备:

  准备阶段是给类中定义的静态属性分配内存,并赋初值。
 --解析:
 ----解析是把符号引用替换为直接引用的过程。符号引用是指在一个类中可能引用了其他类,但是在加载这个原始类的时候并不知道要加载的这个类的具体位置,用符号来代替这个类
 而解析过程是把这个符号引用直接替换为引用类的地址

 类加载器:
 启动类加载器Bootstrap ClassLoader:<JAVA_HOME>\jre\lib目录下或者是-Xbootclasspath所指定路径下目录以及系统属性sun.boot.class.path制定的目录中特定名称的jar包到虚拟机内存中。
 拓展类加载器Extension ClassLoader负责加载<JAVA_HOME>\lib\ext目录下或是被系统属性java.ext.dirs所指定路径目录下的所有类库。
 应用程序类加载器:负责加载class path路径下或者属性java.class.path制定目录下的所有类库

 

 类的双亲委派机制:
 --也就是当一个类加载器要加载一个类的时候不会加载而是交给她的父类去加载,父类还会继续向上传递直到到启动类加载器,如果启动类加载器不能加载这个类那就由这个刚开始的加载器去加载这个类
 类加载器的命名空间:
 --在虚拟机中要想确定一个类就需要这个类和类的加载器来共同确认。不同类加载器的类一定不相同。确定类是否相同的前提就是类的加载器得一样。

 

对象存储的结构分为对象头,实例数据和补充对齐部分。数组对象还会有一个区域记录数组长度。

对象头分为:

--Markworld:用来存放锁信息,年代信息,GC标识及对象的hashcode

--Kclassworld:用来存放指向方法区中的类的模板。这部分知识主要是和锁有关,后续会继续记录。

另外昨天看到一句话对java对象的存储关系突然更加明朗:java中定义的类是自己定义的一套规则,里面定义了方法和变量。Class对象则是用来对实际对象的一种表征,这个对象只有jvm能看懂。可以说这个是定义的类的一套模板,每个类只有一个模板。实例则是使用模板在内存中分配空间的实际使用。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值