二、垃圾回收机制算法分析

一、什么是垃圾回收机制

1.垃圾回收机制不定时,向堆内存清理不可达对象,不可达对象就是说对象不再被引用。

二、垃圾回收机制所使用到的算法有

1.引用计数法(新生代的eden区)。

2.标记清除很少使用,标记压缩(老年代)。

3.复制算法(新生代s0、s1)。

4.分代算法(新生代、老年代)。

三、演示一个例子

1. finalize作用 

Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。说白了就是演示不可达对象什么时候被回收掉。

2.具体测试代码

public class demo1 {
    public static void main(String[] args) {
        demo1 demo1 = new demo1();
        demo1=null;//置为null增加会回收的概率
        System.gc();//手动回收垃圾
    }
    //Object基类中的方法
    @Override
    protected void finalize() throws Throwable {
        //gc垃圾回收之前调用
        System.out.println("垃圾被回收了.....");
    }
}

3.结果

垃圾被回收了.....

四、内存溢出和内存泄漏的区别

1.内存溢出:举个例子,比如说你程序需要4g内存,你硬件支持3g内存,这时候就会发生内存溢出。解决方法就是增加内存就行了。

2.内存泄漏:对象已经没有被应用程序使用,但是垃圾回收器没办法移除它们,因为还在被引用着,也可以这么说就是定义了很多静态变量,垃圾回收机制是不会进行回收的,还有就是没有用的对象还在被引用着,这时候就会报内存泄漏的错。解决方法:

2.1.特别注意一些像HashMap、ArrayList的集合对象,它们经常会引发内存泄漏。当它们被声明为static时,它们的生命周期就会和应用程序一样长。

2.2.特别注意事件监听和回调函数。当一个监听器在使用的时候被注册,但不再使用之后却未被反注册。

2.3.“如果一个类自己管理内存,那开发人员就得小心内存泄漏问题了。” 通常一些成员变量引用其他对象,初始化的时候需要置空。

五、具体讲解每一种算法

1.引用计数法(新生代的eden区)

1.1.图解

1.2.解析

当新建的一个对象user的时候,这时候就会被放在堆内存的新生代eden区,当通过使用引用计数法的时候,当gc线程启动回收的时候,默认对象是15次机会不被回收,当对象不被引用的时候,这时候机会就会减1,知道减为0,这时候对象就会被回收了,当对象减去一个机会之后又被引用了,这时候就会增加1次机会,总结下来:给对象中添加一个引用计数器,默认是15次,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。

1.3.弊端:

无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.而且每次加减非常浪费内存。(实际中很少使用)

2.标记清除很少使用,通常使用标记压缩(老年代)

2.2.解析

详细讲解标记压缩算法,比较标记清除算法来说,标记压缩算法不会残留碎片,而标记清除算法虽然清除了不可达对象,但是还是会占用空间,而且标记压缩算法还是连续性的,标记压缩法在标记清除基础之上做了优化,把存活的对象压缩到内存一端,而后进行垃圾清理,主要用在老年代垃圾回收。

3.复制算法(新生代s0、s1)

3.1.解析

当创建user2对象时候,user2对象一直被使用这时候就会晋升到s0或者s1区,当再次创建一个对象user4的时候,而且user4也是被一直使用,这时候就会晋升到同user一样的s0或者s1区,比如user晋升到s0,那么user2就会被晋升到s0,而不会被放到s1中,因为gc去回收的时候,只能保证一个区内放的是存活的对象,当s0中的user2不被使用的时候,这时候user4就会迁移到s1中,然后把user2抹掉,这个就是复制算法。

3.2.优缺点

S0和s1将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。复制算法的缺点显而易见,可使用的内存降为原来一半。

4.分代算法(新生代、老年代)

4.1.根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。

4.2.对于新生代和老年代来说,新生代回收频率很高,但是每次回收耗时很短,而老年代回收频率较低,但是耗时会相对较长,所以应该尽量减少老年代的GC.

六、垃圾收集器

1.串行收集器:就是单线程收集垃圾,效率低

   并行收集器:就是多线程收集垃圾,效率高

2.tomcat配置参数调优测试--串行回收

2.1.准备压力测试工具

2.2.测试串行吞吐量

2.2.1(初始内存是32M)

-XX:+PrintGCDetails -Xmx32M -Xms32M
-XX:+HeapDumpOnOutOfMemoryError
-XX:+UseSerialGC
-XX:PermSize=32M

控制台结果(6次GC,跟初始内存有关,越小回收次数就越多):

[GC (Allocation Failure) [DefNew: 8704K->1088K(9792K), 0.0051689 secs] 8704K->2558K(31680K), 0.0052014 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 9792K->1087K(9792K), 0.0058442 secs] 11262K->4197K(31680K), 0.0058658 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 9791K->1088K(9792K), 0.0058994 secs] 12901K->6171K(31680K), 0.0059221 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 9792K->1088K(9792K), 0.0061104 secs] 14875K->7615K(31680K), 0.0061520 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 9792K->1087K(9792K), 0.0053265 secs] 16319K->9178K(31680K), 0.0053572 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 9791K->1087K(9792K), 0.0042297 secs] 17882K->10351K(31680K), 0.0042559 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

压测工具(吞吐量):

2.2.2(初始内存是512M)

-XX:+PrintGCDetails -Xmx512M -Xms32M
-XX:+HeapDumpOnOutOfMemoryError
-XX:+UseSerialGC
-XX:PermSize=32M

控制台效果(4次GC):

[GC (Allocation Failure) [DefNew: 8704K->1087K(9792K), 0.0063448 secs] 8704K->2558K(31680K), 0.0064245 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 9791K->1088K(9792K), 0.0124570 secs] 11262K->4191K(31680K), 0.0124905 secs] [Times: user=0.05 sys=0.02, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 9792K->1088K(9792K), 0.0056843 secs] 12895K->6206K(31680K), 0.0057094 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 9792K->1087K(9792K), 0.0064387 secs] 14910K->7590K(31680K), 0.0065041 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 9791K->1087K(9792K), 0.0053550 secs] 16294K->9060K(31680K), 0.0053857 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 9791K->1088K(9792K), 0.0042297 secs] 17764K->10255K(31680K), 0.0042593 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

压测工具(吞吐量):

结论:最大内存越大,吞吐量越高。

2.2.3(初始堆内存是512M和最大堆内存都是512M)

-XX:+PrintGCDetails -Xmx512M –Xms512M
-XX:+HeapDumpOnOutOfMemoryError
-XX:+UseSerialGC
-XX:PermSize=32M

控制台效果(0次GC)

没有GC

压测工具(吞吐量,性能理论上能提高一倍):

3.tomcat配置参数调优测试--并行回收

3.1.调优参数(没有开启线程数,默认是单线程)

-XX:+PrintGCDetails -Xmx512M –Xms512M
-XX:+HeapDumpOnOutOfMemoryError
-XX:+UseParNewGC
-XX:PermSize=32M

控制台效果(0次GC)

没有GC

压测工具(吞吐量)和2.2.3结果差不多:

3.2.调优参数(多线程8个测试)

-XX:+PrintGCDetails -Xmx512M -Xms512M
-XX:+HeapDumpOnOutOfMemoryError
-XX:+UseParallelGC
-XX:+UseParallelOldGC
-XX:ParallelGCThreads=8
-XX:PermSize=32M

控制台效果(0次GC)

没有GC

压测工具(吞吐量):

七、总结(上面的数据跑的可能不是太准确)

初始堆值和最大内存内存越大,吞吐量就越高。

最好使用并行收集器,因为并行手机器速度比串行吞吐量高,速度快。

设置堆内存新生的比例和老年代的比例最好1:2或者1:3。

减少GC对老年代的回收。

八、测试代码

1.项目结构图

2.DemoServlet01.java

@WebServlet("/index")
public class DemoServlet01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            System.out.println("处理请求");
    }
}

3.web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         id="WebApp_ID" version="3.1">
  <display-name>web</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>

  <servlet>
    <servlet-name>DemoServlet01</servlet-name>
    <servlet-class>com.ysfj.DemoServlet01</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>DemoServlet01</servlet-name>
    <url-pattern>/DemoServlet01</url-pattern>
  </servlet-mapping>
</web-app>

 九、结束

Always keep the faith!!!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值