JVM复习笔记

2018-11-27  本文已影响0人  来往穿梭

1. Java内存结构:

Java内存结构

2.类加载机制:

从类被加载到虚拟机内存中开始,到卸御出内存为止,它的整个生命周期分为7个阶段,加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸御(Unloading)。其中验证、准备、解析三个部分统称为连接。

类的生命周期
1. 加载
通过一个类的全限定名来获取定义此类的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2. 验证

3. 准备
准备阶段正式为类变量分配内存并设置初始值阶段。
public static int value=123; 初始后为 value=0;
对于static final类型,在准备阶段会被赋予正确的值
public static final value=123;初始化为 value=123;
如果是boolean值默认赋值为:false
如果是对象引用默认赋值为:null
4. 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:简单的理解就是字符串,比如引用一个类,java.util.ArrayList 这就是一个符号引用,字符串引用的对象不一定被加载。
直接引用:指针或者地址偏移量。引用对象一定在内存(已经加载)。
5. 初始化
执行类构造器<clinit>
初始化静态变量、静态块中的数据等(一个类加载器只会初始化一次)
子类的<clinit>调用前保证父类的<clinit>被调用

什么是类装载器ClassLoader
ClassLoader是一个抽象类
ClassLoader的实例将读入Java字节码将类装载到JVM中
ClassLoader可以定制,满足不同的字节码流获取方式
ClassLoader负责类装载过程中的加载阶段。

JVM中的类加载器

双亲委派机制:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

类加载器双亲委派模型

3.垃圾收集算法:


4.JVM参数:

-XX:+PrintGC 每次触发GC的时候打印相关日志
-XX:+UseSerialGC 串行回收
-XX:+PrintGCDetails 更详细的GC日志
-Xms 堆初始值
-Xmx 堆最大可用值
-Xmn 新生代堆最大可用值
-Xss 每个线程的堆栈大小
-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例
-XX:NewRatio 新生代和老年代的比例
-XX:MaxTenuringThreshold 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代.对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率.该参数只有在串行GC时才有效.

回收器选择
-XX:+UseParallelGC 选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20 配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。


5.JVM调优策略:

核心目标:

将新对象预留在年轻代
众所周知,由于 Full GC 的成本远远高于 Minor GC,因此某些情况下需要尽可能将对象分配在年轻代,这在很多情况下是一个明智的选择。虽然在大部分情况下,JVM 会尝试在 Eden 区分配对象,但是由于空间紧张等问题,很可能不得不将部分年轻对象提前向年老代压缩。因此,在 JVM 参数调优时可以为应用程序分配一个合理的年轻代空间,以最大限度避免新对象直接进入年老代的情况发生。
通过设置一个较大的年轻代预留新对象,设置合理的 Survivor 区并且提供 Survivor 区的使用率,可以将年轻对象保存在年轻代。一般来说,Survivor 区的空间不够,或者占用量达到 50%时,就会使对象进入年老代(不管它的年龄有多大)
我们可以尝试加上-XX:TargetSurvivorRatio=90参数,这样可以提高 from 区的利用率,使 from 区使用到 90%时,再将对象送入年老代

让大对象进入年老代
我们在大部分情况下都会选择将对象分配在年轻代。但是,对于占用内存较多的大对象而言,它的选择可能就不是这样的。因为大对象出现在年轻代很可能扰乱年轻代 GC,并破坏年轻代原有的对象结构。因为尝试在年轻代分配大对象,很可能导致空间不足,为了有足够的空间容纳大对象,JVM 不得不将年轻代中的年轻对象挪到年老代。因为大对象占用空间多,所以可能需要移动大量小的年轻对象进入年老代,这对 GC 相当不利。
基于以上原因,可以将大对象直接分配到年老代,保持年轻代对象结构的完整性,这样可以提高 GC 的效率。如果一个大对象同时又是一个短命的对象,假设这种情况出现很频繁,那对于 GC 来说会是一场灾难。原本应该用于存放永久对象的年老代,被短命的对象塞满,这也意味着对堆空间进行了洗牌,扰乱了分代内存回收的基本思路。因此,在软件开发过程中,应该尽可能避免使用短命的大对象。
可以使用参数-XX:PetenureSizeThreshold
设置大对象直接进入年老代的阈值。当对象的大小超过这个值时,将直接在年老代分配。参数-XX:PetenureSizeThreshold只对串行收集器和年轻代并行收集器有效,并行回收收集器不识别这个参数。

设置对象进入年老代的年龄
如何设置对象进入年老代的年龄
堆中的每一个对象都有自己的年龄。一般情况下,年轻对象存放在年轻代,年老对象存放在年老代。为了做到这点,虚拟机为每个对象都维护一个年龄。如果对象在 Eden 区,经过一次 GC 后依然存活,则被移动到 Survivor 区中,对象年龄加 1。以后,如果对象每经过一次 GC 依然存活,则年龄再加 1。当对象年龄达到阈值时,就移入年老代,成为老年对象。
这个阈值的最大值可以通过参数
-XX:MaxTenuringThreshold
来设置,默认值是 15。虽然-XX:MaxTenuringThreshold的值可能是 15 或者更大,但这不意味着新对象非要达到这个年龄才能进入年老代。事实上,对象实际进入年老代的年龄是虚拟机在运行时根据内存使用情况动态计算的,这个参数指定的是阈值年龄的最大值。即,实际晋升年老代年龄等于动态计算所得的年龄与-XX:MaxTenuringThreshold中较小的那个。

总结:在实际工作中,我们可以直接将初始的堆大小与最大堆大小相等,
这样的好处是可以减少程序运行时垃圾回收次数,从而提高效率。


6.常见OOM问题:

设置堆内存大小

public static void main(String[] args) throws InterruptedException {
        List<Object> list = new ArrayList<>();
        Thread.sleep(3000);
        jvmInfo();
        for (int i = 0; i < 10; i++) {
            System.out.println("i:"+i);
            Byte [] bytes=  new Byte[1*1024*1024];
            list.add(bytes);
            jvmInfo();
        }
        System.out.println("添加成功...");
    }

设置栈内存大小

public class JvmDemo04 {
     private static int count;
     public static void count(){
        try {
             count++;
             count(); 
        } catch (Throwable e) {
            System.out.println("最大深度:"+count);
            e.printStackTrace();
        }
     }
     public static void main(String[] args) {
         count();
    }
}

Tomcat内存溢出在catalina.sh 修改JVM堆内存大小
JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxNewSize=512m"

上一篇 下一篇

猜你喜欢

热点阅读