(原创)关于Tomcat默认最大占用内存的一系列问题

今天舍友问我tomcat啥都不改的话,内存最大能占用到多少,我脱口而出四分之一物理内存,然而事实真的是这样吗?

1、查阅官方说明文档

JDK文档传送门,当然全是英文看起来需要一点时间,所以我找来了别人的大概翻译

client模式下,JVM初始和最大堆大小为:
在物理内存达到192MB之前,JVM最大堆大小为物理内存的一半,否则,在物理内存大于192MB,在到达1GB之前,JVM最大堆大小为物理内存的1/4,大于1GB的物理内存也按1GB计算,举个例子,如果你的电脑内存是128MB,那么最大堆大小就是64MB,如果你的物理内存大于或等于1GB,那么最大堆大小为256MB。
Java初始堆大小是物理内存的1/64,但最小是8MB。

server模式下:
与client模式类似,区别就是默认值可以更大,比如在32位JVM下,如果物理内存在4G或更高,最大堆大小可以提升至1GB,,如果是在64位JVM下,如果物理内存在128GB或更高,最大堆大小可以提升至32GB。

2、实践是校验真理的唯一标准

在Linux下,我们可以通过以下命令来查看堆内存的默认最大值

java -XX:+PrintFlagsFinal -version | grep MaxHeapSize

输出如下:

上图是我在2G RAM的虚拟机里的结果,可以看到522190848 Byte = 498 MB,而这虚拟机除了给硬件保留的内存外总内存空间为 2039552 KB ,其四分之一也就是509888KB= 497MB,可以说理论和实际值非常接近,误差可忽略。

然而在Windows上,我没有通过命令行去找出最大堆,而是选择了写个Java类来获取。

package test;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;

public class MaxHeapTest {
    public static void main(String[] args) {
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();  
        MemoryUsage heapMemory= memoryMXBean.getHeapMemoryUsage();  
        long maxHeap = heapMemory.getMax();
        // 单位:字节
        System.out.println(maxHeap +" byte");
    }
}

输出为:

有趣的地方来了,根据换算,3771203584 byte = 3596.5 MB,而我windows的机器上除去硬件保留的内存,可用的有 16178MB,其四分一为 4044.5 MB,和上图的输出值存在不可忽略的大小差异,难道是windows系统特别些?我们继续探索。

3、探索、求真

上述的获取方式不一样,我们先来统一下方式,

在CMD下以同样的方式打印JVM参数,找MaxHeapSize字样:

其中,4242538496 byte = 4046MB,嗯,这个值就才对嘛。

那么现在的问题转变成了,为啥通过java编程方式获取到的最大堆内存会和真实值有出入的?小弟不才,没有了进一步探索的毅力,只想站在前辈的肩膀上看看风景。

这里是别人留下的宝贵经验:传送门2
经过计算和检验,的确差了一个Survivor区的大小,加上这个也就对得上了。

引用别人的结论:通过MemoryMXBean 获取的最大堆内存,应该是JVM可以使用的最大堆内存。因为两个幸存区,采用的是标记拷贝的GC算法,实际分配时,永远有一个幸存区是不能被使用的,所以最大堆内存没有包含这一部分

结尾:

以上结论仅针对Hot Spot虚拟机,其它JVM请自行实验。

本文参考链接:https://segmentfault.com/q/1010000007235579?_ea=1280245

Spring声明式事务管理

在 spring 中一共定义了六种事务传播属性

  • PROPAGATION_REQUIRED — 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  • PROPAGATION_SUPPORTS — 支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY — 支持当前事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW — 新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED — 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER — 以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED — 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)

配置静态常量成SpringBean的几种方法

有时候为了方便,我们会统一在一些类中放置一些固定不变的常量,成为常量公共类。或者我们仅仅是需要将一些某个类的某个静态方法的返回值或者静态属性注入到Spring容器中以供其他Bean使用。

下面来记录一些在网络上找到的方法。

对于传统Spring项目,使用XML配置式

<bean class="cn.ultrasoftware.util.Speaker">
   <property name="printStream" ref="TARGET" />
</bean>

这里就开始有点意思了,Spring提供了以下几种写法


<bean id="TARGET" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
      <property name="staticField" value="java.lang.System.out"/>
</bean>

还有这种写法

<util:constant id="TARGET" static-field="java.lang.System.out" />

最后这种写法我就很少见了

<bean class="cn.ultrasoftware.util.Speaker">
 <property name="printStream" value="#{T(System).out}"/>
</bean>

至于Springboot的写法,没啥特别的,就不说了。

JVM内存分布

本文内容参考《深入理解Java虚拟机》(第2版)一书,内容仅对 JDK1.7及以前版本负责

jvm中内存划分:

线程共享区域为:
1. java堆
2. 方法区
线程私有区域为:
3. JVM栈
4. 本地方法栈
5. 程序计数器
    java堆是jvm内存管理中最大的一块,线程共享。在jvm启动的时候创建。此区域唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。但是随着JIT编译器(即时编译器)的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙变化(对象可能会分配到栈上),所以这种所有对象都分配在堆上也不是那么绝对的。
    java堆细分为新生代和老年代,新生代又分为Eden空间、From Survivor空间、To Survivor空间,新生代中垃圾回收算法为复制算法,复制算法是先将内存分为连个部分,一部分用来放入对象,而另一部分暂时不用,当使用的一部分内存要进行垃圾回收的时候会将不需要回收的对象复制保存在另一个空间中,然后再对使用过的那部分区域进行垃圾回收,这样虽然效率很高,但是很浪费空间,所以一般将新生代分为Eden空间和两个Survivor空间,其大小在HotSpot虚拟机中默认比例为8:1:1,这样在新生代中采用复制算法回收垃圾效率就很高了,具体回收过程是将Eden区域和From Survivor区域作为对象的存储空间,当要进行垃圾回收的时候先将这两个区域中不需要回收的对象复制保存在To Survivor区域中,然后再进行垃圾回收。另外有一点是当一个对象在Eden区域和From Survivor区域中存储的时候发现内存不足,这时会进行内存分配担保,就是将此对象直接存入在老年代中。
    老年代中采用的GC算法为标记-清除算法或者标记-整理算法。标记-清除算法为:首先标记出要进行GC的对象,标记完成后再进行GC。这种算法效率不高,并且会产生很多内存碎片。标记-整理算法:同样是先对要进行GC的对象进行标记,但是不同的是在标记完成后不是立刻执行GC,而是先将不需要GC的对象移动到一端,然后在边界外再对要回收的对象进行GC。
    关于对象的分配:对象优先在Eden区域分配,大对象会直接进入老年代,长期存活的对象会进入老年代,这里的长期存活是根据新生代中的对象年龄阈值来定义的,对象刚分配到新生代的时候年龄为1,每进行一次GC对象的年龄会加1,HotSpot中默认的阈值是15,也就是说对象年龄达到15岁的时候会被分配到老年区,这个值是可以通过参数配置的。
    在进行垃圾回收的时候新生代GC又叫minor GC,老年代GC可以设置内存容量达到百分比的多少的时候进行GC,老年代的GC又叫Full GC,minor GC时间短,频率高,而Full GC时间长,频率低。
2、方法区
    方法区又被称为永久区,线程共享,是用来存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区为堆的一个逻辑部分,但是在JDK1.7的HotSpot中已经将方法区中的字符串常量池移出,JDK1.8已经去除了方法区。
    这个区域很少进行垃圾回收,回收目标主要是针对常量池的回收和对类型的卸载。
3、JVM栈
    JVM栈是线程私有的,它的生命周期与线程相同。JVM栈描述的是java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
    局部变量表中存放了编译期可知的各种基本数据类型、对象的引用类型。局部变量表中需要的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
4、本地方法栈
    本地方法栈和JVM栈非常相似,它们之间的区别不过是jvm栈是为执行java方法服务,而本地方法栈是为jvm使用到对的本地方法服务。HotSpot虚拟机中直接把本地方法栈和JVM栈合二为一了。
5、程序计数器
    程序计数器是一块较小的内存空间,线程私有。它可以看作是当前线程所执行的字节码的行号指示器。在jvm的概念模型里,字节码解释器工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的jvm字节码指令的地址;如果正在执行的是本地方法,这个计数器值则为空。
总结:
    在jvm划分的内存区域中JVM栈和本地方法栈可能会抛出StackOverflowError异常和OutOfMemoryError异常。java堆和方法区可能会抛出OutOfMemoryError异常。程序计数器中没有地方规定会抛出这两个异常。
扩展:
    在jvm规范中,StackOverflowError异常为:如果线程请求的栈深度大于JVM允许的栈深度,将抛出StackOverflowError异常。OutOfMemoryError异常:如果jvm可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
    HotSpot虚拟机中标记要清除的对象方法不是使用引用计数器(有引用的时候计数器+1,引用失效-1,应用为0时回收),而使用的是可达性分析算法:以“GC Roots“的对象为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明这个对象不可达,即这个对象不可用,所以这个对象会被判定为是可回收对象。

(转载)11种将InputStream转换成String的方法以及性能分析

作者:Viacheslav Vedenin
原文链接:Read/convert an InputStream to a String

从其他回答中总结出了11种能将InputStream转换成String的方法(如下),并且对所有方法进行了性能测试(对比结果如下):

将InputStream转换成String的方法:

1.使用 IOUtils.toString (Apache Utils)

String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);

2.使用 CharStreams (guava)

String result = CharStreams.toString(new InputStreamReader(
      inputStream, Charsets.UTF_8));

3.使用 Scanner (JDK)

Scanner s = new Scanner(inputStream).useDelimiter("\\A");
String result = s.hasNext() ? s.next() : "";

4.使用 Stream Api (Java 8)

String result = new BufferedReader(new InputStreamReader(inputStream))
  .lines().collect(Collectors.joining("\n"));

5.使用 parallel Stream Api (Java 8)

String result = new BufferedReader(new InputStreamReader(inputStream)).lines()
   .parallel().collect(Collectors.joining("\n"));

6.使用 InputStreamReader 和 StringBuilder (JDK)

final int bufferSize = 1024;
final char[] buffer = new char[bufferSize];
final StringBuilder out = new StringBuilder();
Reader in = new InputStreamReader(inputStream, "UTF-8");
for (; ; ) {
    int rsz = in.read(buffer, 0, buffer.length);
    if (rsz < 0)
        break;
    out.append(buffer, 0, rsz);
}
return out.toString();

7.使用 StringWriter 和 IOUtils.copy (Apache Commons)

StringWriter writer = new StringWriter();
IOUtils.copy(inputStream, writer, "UTF-8");
return writer.toString();

8.使用 ByteArrayOutputStream 和 inputStream.read (JDK)

ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
    result.write(buffer, 0, length);
}
return result.toString("UTF-8");

9.使用 BufferedReader (JDK)

String newLine = System.getProperty("line.separator");
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder result = new StringBuilder();
String line; boolean flag = false;
while ((line = reader.readLine()) != null) {
    result.append(flag? newLine: "").append(line);
    flag = true;
}
return result.toString();

10.使用 BufferedInputStream 和 ByteArrayOutputStream (JDK)

BufferedInputStream bis = new BufferedInputStream(inputStream);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
int result = bis.read();
while(result != -1) {
    buf.write((byte) result);
    result = bis.read();
}
return buf.toString();

11.使用 inputStream.read() 和 StringBuilder (JDK)

int ch;
StringBuilder sb = new StringBuilder();
while((ch = inputStream.read()) != -1)
    sb.append((char)ch);
reset();
return sb.toString();

注意:
1.方法4、5和9会将不同的换行符(例如:\r\n)全转换成\n。
2.方法11在Unicode编码下不能正确地运行。

性能测试

1.针对较短String的性能测试(长度为175)如下所示,代码贴在了github上(模式是测试平均时间,系统是Linux,其中最好的分数是1,343 )

              Benchmark                        Mode  Cnt   Score   Error  Units
8. ByteArrayOutputStream and read (JDK)        avgt   10   1,343 ± 0,028  us/op
6. InputStreamReader and StringBuilder (JDK)   avgt   10   6,980 ± 0,404  us/op
10.BufferedInputStream, ByteArrayOutputStream  avgt   10   7,437 ± 0,735  us/op
11.InputStream.read() and StringBuilder (JDK)  avgt   10   8,977 ± 0,328  us/op
7. StringWriter and IOUtils.copy (Apache)      avgt   10  10,613 ± 0,599  us/op
1. IOUtils.toString (Apache Utils)             avgt   10  10,605 ± 0,527  us/op
3. Scanner (JDK)                               avgt   10  12,083 ± 0,293  us/op
2. CharStreams (guava)                         avgt   10  12,999 ± 0,514  us/op
4. Stream Api (Java 8)                         avgt   10  15,811 ± 0,605  us/op
9. BufferedReader (JDK)                        avgt   10  16,038 ± 0,711  us/op
5. parallel Stream Api (Java 8)                avgt   10  21,544 ± 0,583  us/op

2.针对较长String的性能测试(长度为50100)如下所示,代码贴在了github上(模式是测试平均时间,系统是Linux,其中最好的分数是200,715 )

              Benchmark                        Mode  Cnt   Score        Error  Units
8. ByteArrayOutputStream and read (JDK)        avgt   10   200,715 ±   18,103  us/op
1. IOUtils.toString (Apache Utils)             avgt   10   300,019 ±    8,751  us/op
6. InputStreamReader and StringBuilder (JDK)   avgt   10   347,616 ±  130,348  us/op
7. StringWriter and IOUtils.copy (Apache)      avgt   10   352,791 ±  105,337  us/op
2. CharStreams (guava)                         avgt   10   420,137 ±   59,877  us/op
9. BufferedReader (JDK)                        avgt   10   632,028 ±   17,002  us/op
5. parallel Stream Api (Java 8)                avgt   10   662,999 ±   46,199  us/op
4. Stream Api (Java 8)                         avgt   10   701,269 ±   82,296  us/op
10.BufferedInputStream, ByteArrayOutputStream  avgt   10   740,837 ±    5,613  us/op
3. Scanner (JDK)                               avgt   10   751,417 ±   62,026  us/op
11.InputStream.read() and StringBuilder (JDK)  avgt   10  2919,350 ± 1101,942  us/op

3.性能测试图(下图主要描述了在Windows7系统下,随着Input Stream长度变化各个方法平均时间的变化)

enter image description here

4.性能测试数据(同样是在Windows7系统下,随着Input Stream长度变化各个方法平均时间的变化)

 length  182    546     1092    3276    9828    29484   58968

 test8  0.38    0.938   1.868   4.448   13.412  36.459  72.708
 test4  2.362   3.609   5.573   12.769  40.74   81.415  159.864
 test5  3.881   5.075   6.904   14.123  50.258  129.937 166.162
 test9  2.237   3.493   5.422   11.977  45.98   89.336  177.39
 test6  1.261   2.12    4.38    10.698  31.821  86.106  186.636
 test7  1.601   2.391   3.646   8.367   38.196  110.221 211.016
 test1  1.529   2.381   3.527   8.411   40.551  105.16  212.573
 test3  3.035   3.934   8.606   20.858  61.571  118.744 235.428
 test2  3.136   6.238   10.508  33.48   43.532  118.044 239.481
 test10 1.593   4.736   7.527   20.557  59.856  162.907 323.147
 test11 3.913   11.506  23.26   68.644  207.591 600.444 1211.545